"""
Notification Service - Multi-channel alert delivery.

Handles sending alerts via Slack, email, webhook, and in-app notifications
with support for throttling, aggregation, and quiet hours.
"""

from __future__ import annotations

import hashlib
import logging
import os
from datetime import datetime, timedelta, timezone
from typing import Any

import httpx

from lightwave.schema.pydantic.models.alerts import (
    Alert,
    AlertDelivery,
    AlertsConfig,
)

logger = logging.getLogger(__name__)


# =============================================================================
# THROTTLE CACHE (in-memory, reset on restart)
# =============================================================================

_throttle_cache: dict[str, datetime] = {}


def _get_throttle_key(alert: Alert) -> str:
    """Generate throttle key for an alert."""
    # Throttle by alert_type + entity_id if present
    key_parts = [alert.alert_type]
    if alert.entity_id:
        key_parts.append(alert.entity_id)
    return hashlib.md5(":".join(key_parts).encode()).hexdigest()


def _is_throttled(alert: Alert) -> bool:
    """Check if alert is throttled."""
    throttle_minutes = alert.get_throttle_minutes()
    if throttle_minutes == 0:
        return False

    key = _get_throttle_key(alert)
    last_sent = _throttle_cache.get(key)

    if last_sent is None:
        return False

    threshold = datetime.now(timezone.utc) - timedelta(minutes=throttle_minutes)
    return last_sent > threshold


def _update_throttle(alert: Alert) -> None:
    """Update throttle cache after sending."""
    key = _get_throttle_key(alert)
    _throttle_cache[key] = datetime.now(timezone.utc)


# =============================================================================
# QUIET HOURS
# =============================================================================


def _is_in_quiet_hours() -> bool:
    """Check if current time is within quiet hours."""
    if not AlertsConfig.is_quiet_hours_enabled():
        return False

    start_hour, end_hour = AlertsConfig.get_quiet_hours()

    # Get current hour in configured timezone
    # For simplicity, using UTC - in production would use configured timezone
    now = datetime.now(timezone.utc)
    current_hour = now.hour

    # Handle overnight quiet hours (e.g., 22:00 - 07:00)
    if start_hour > end_hour:
        return current_hour >= start_hour or current_hour < end_hour
    else:
        return start_hour <= current_hour < end_hour


def _should_suppress_for_quiet_hours(alert: Alert) -> bool:
    """Check if alert should be suppressed during quiet hours."""
    if not _is_in_quiet_hours():
        return False

    rules = AlertsConfig.get_rules().get("quiet_hours", {})
    suppress_below = rules.get("suppress_below_severity", "high")

    severity_order = AlertsConfig.get_severity_levels()
    if suppress_below not in severity_order or alert.severity not in severity_order:
        return False

    alert_idx = severity_order.index(alert.severity)
    suppress_idx = severity_order.index(suppress_below)

    # Higher index = lower severity, so suppress if alert_idx > suppress_idx
    return alert_idx > suppress_idx


# =============================================================================
# CHANNEL HANDLERS
# =============================================================================


def send_slack_message(
    message: str,
    channel: str | None = None,
    webhook_url: str | None = None,
    blocks: list[dict[str, Any]] | None = None,
) -> bool:
    """
    Send a message to Slack.

    Args:
        message: The message text
        channel: Override channel (if webhook supports it)
        webhook_url: Override webhook URL (defaults to env var)
        blocks: Optional Slack blocks for rich formatting

    Returns:
        True if sent successfully, False otherwise
    """
    url = webhook_url or os.environ.get("SLACK_WEBHOOK_URL")
    if not url:
        logger.warning("No Slack webhook URL configured")
        return False

    payload: dict[str, Any] = {"text": message}

    if channel:
        payload["channel"] = channel

    if blocks:
        payload["blocks"] = blocks

    try:
        with httpx.Client(timeout=30) as client:
            response = client.post(url, json=payload)
            if response.status_code == 200:
                logger.info("Slack message sent successfully")
                return True
            else:
                logger.error(f"Slack webhook failed: {response.status_code} - {response.text}")
                return False
    except Exception as e:
        logger.exception(f"Failed to send Slack message: {e}")
        return False


def send_email(
    to: str | list[str],
    subject: str,
    body: str,
    html_body: str | None = None,
    from_address: str | None = None,
) -> bool:
    """
    Send an email.

    Uses Django's email backend if available, otherwise logs a warning.

    Args:
        to: Recipient email(s)
        subject: Email subject
        body: Plain text body
        html_body: Optional HTML body
        from_address: Override from address

    Returns:
        True if sent successfully, False otherwise
    """
    try:
        from django.conf import settings
        from django.core.mail import EmailMultiAlternatives

        sender = from_address or getattr(settings, "DEFAULT_FROM_EMAIL", "noreply@lightwave.com")
        recipients = [to] if isinstance(to, str) else to

        email = EmailMultiAlternatives(
            subject=subject,
            body=body,
            from_email=sender,
            to=recipients,
        )

        if html_body:
            email.attach_alternative(html_body, "text/html")

        email.send(fail_silently=False)
        logger.info(f"Email sent to {recipients}")
        return True

    except ImportError:
        logger.warning("Django not available for email sending")
        return False
    except Exception as e:
        logger.exception(f"Failed to send email: {e}")
        return False


def send_webhook(
    url: str,
    payload: dict[str, Any],
    headers: dict[str, str] | None = None,
) -> bool:
    """
    Send a webhook POST request.

    Args:
        url: Webhook URL
        payload: JSON payload
        headers: Optional additional headers

    Returns:
        True if sent successfully (2xx response), False otherwise
    """
    default_headers = {"Content-Type": "application/json"}
    if headers:
        default_headers.update(headers)

    try:
        with httpx.Client(timeout=30) as client:
            response = client.post(url, json=payload, headers=default_headers)
            if 200 <= response.status_code < 300:
                logger.info(f"Webhook sent to {url}")
                return True
            else:
                logger.error(f"Webhook failed: {response.status_code} - {response.text}")
                return False
    except Exception as e:
        logger.exception(f"Failed to send webhook: {e}")
        return False


# =============================================================================
# NOTIFICATION SERVICE
# =============================================================================


class NotificationService:
    """
    Multi-channel notification service for alerts.

    Handles routing alerts to appropriate channels with throttling,
    quiet hours, and delivery tracking.
    """

    def __init__(
        self,
        respect_throttle: bool = True,
        respect_quiet_hours: bool = True,
        dry_run: bool = False,
    ):
        """
        Initialize the notification service.

        Args:
            respect_throttle: Whether to apply throttling
            respect_quiet_hours: Whether to respect quiet hours
            dry_run: If True, don't actually send notifications
        """
        self.respect_throttle = respect_throttle
        self.respect_quiet_hours = respect_quiet_hours
        self.dry_run = dry_run

    def send(self, alert: Alert) -> list[AlertDelivery]:
        """
        Send an alert to all configured channels.

        Args:
            alert: The alert to send

        Returns:
            List of delivery records for each channel
        """
        deliveries: list[AlertDelivery] = []

        # Check throttle
        if self.respect_throttle and _is_throttled(alert):
            logger.info(f"Alert {alert.alert_type} throttled")
            for channel in alert.channels:
                deliveries.append(
                    AlertDelivery(
                        alert_id=str(id(alert)),
                        channel=channel,
                        status="skipped",
                        metadata={"reason": "throttled"},
                    )
                )
            return deliveries

        # Check quiet hours
        if self.respect_quiet_hours and _should_suppress_for_quiet_hours(alert):
            logger.info(f"Alert {alert.alert_type} suppressed for quiet hours")
            for channel in alert.channels:
                deliveries.append(
                    AlertDelivery(
                        alert_id=str(id(alert)),
                        channel=channel,
                        status="skipped",
                        metadata={"reason": "quiet_hours"},
                    )
                )
            return deliveries

        # Send to each channel
        for channel in alert.channels:
            if not AlertsConfig.is_channel_enabled(channel):
                deliveries.append(
                    AlertDelivery(
                        alert_id=str(id(alert)),
                        channel=channel,
                        status="skipped",
                        metadata={"reason": "channel_disabled"},
                    )
                )
                continue

            delivery = self._send_to_channel(alert, channel)
            deliveries.append(delivery)

        # Update throttle cache if at least one delivery succeeded
        if any(d.status in ("sent", "delivered") for d in deliveries):
            _update_throttle(alert)

        return deliveries

    def _send_to_channel(self, alert: Alert, channel: str) -> AlertDelivery:
        """Send alert to a specific channel."""
        delivery = AlertDelivery(
            alert_id=str(id(alert)),
            channel=channel,
            status="pending",
        )

        if self.dry_run:
            logger.info(f"[DRY RUN] Would send {alert.alert_type} to {channel}")
            delivery.status = "skipped"
            delivery.metadata = {"reason": "dry_run"}
            return delivery

        try:
            message = alert.render_message()
            success = False

            if channel == "slack":
                success = self._send_slack(alert, message)
            elif channel == "email":
                success = self._send_email(alert, message)
            elif channel == "webhook":
                success = self._send_webhook(alert, message)
            elif channel == "in_app":
                success = self._send_in_app(alert)
            else:
                logger.warning(f"Unknown channel: {channel}")
                delivery.status = "failed"
                delivery.error_message = f"Unknown channel: {channel}"
                return delivery

            delivery.status = "sent" if success else "failed"
            delivery.delivered_at = datetime.now(timezone.utc)

            if not success:
                delivery.error_message = f"Failed to send to {channel}"

        except Exception as e:
            logger.exception(f"Error sending to {channel}: {e}")
            delivery.status = "failed"
            delivery.error_message = str(e)

        return delivery

    def _send_slack(self, alert: Alert, message: str) -> bool:
        """Send alert to Slack."""
        # Format message with severity indicator
        severity_emoji = {
            "critical": ":rotating_light:",
            "high": ":warning:",
            "medium": ":large_blue_circle:",
            "low": ":information_source:",
            "info": ":speech_balloon:",
        }

        emoji = severity_emoji.get(alert.severity, ":bell:")
        formatted = f"{emoji} *{alert.title}*\n\n{message}"

        return send_slack_message(formatted)

    def _send_email(self, alert: Alert, message: str) -> bool:
        """Send alert via email."""
        # Would need recipient configuration - for now just log
        logger.info(f"Would send email for alert: {alert.title}")
        # In production, would look up alert subscribers
        return True

    def _send_webhook(self, alert: Alert, message: str) -> bool:
        """Send alert to webhook."""
        # Would need webhook URL configuration
        logger.info(f"Would send webhook for alert: {alert.title}")
        return True

    def _send_in_app(self, alert: Alert) -> bool:
        """Create in-app notification."""
        # Would persist to database
        logger.info(f"Would create in-app notification for: {alert.title}")
        return True


# =============================================================================
# CONVENIENCE FUNCTIONS
# =============================================================================


def send_alert(
    alert_type: str,
    title: str,
    data: dict[str, Any] | None = None,
    severity: str | None = None,
    channels: list[str] | None = None,
    entity_type: str | None = None,
    entity_id: str | None = None,
    dry_run: bool = False,
) -> list[AlertDelivery]:
    """
    Convenience function to create and send an alert.

    Args:
        alert_type: Type of alert from alerts.yaml
        title: Alert title
        data: Template data for message rendering
        severity: Override severity (uses config default if None)
        channels: Override channels (uses config default if None)
        entity_type: Related entity type
        entity_id: Related entity ID
        dry_run: If True, don't actually send

    Returns:
        List of delivery records
    """
    config = AlertsConfig.get_alert_type_config(alert_type)

    alert = Alert(
        alert_type=alert_type,
        title=title,
        data=data or {},
        severity=severity or config.get("severity", "medium"),
        channels=channels or [],
        entity_type=entity_type,
        entity_id=entity_id,
    )

    service = NotificationService(dry_run=dry_run)
    return service.send(alert)


def clear_throttle_cache() -> None:
    """Clear the throttle cache (for testing)."""
    _throttle_cache.clear()
