"""Chat notifications for job events.

This module provides webhook-based notifications for Discord and Slack,
with configurable triggers, rate limiting, and rich embeds.

Example:
    >>> from oclawma.notifications.chat import ChatNotifier, ChatNotificationConfig
    >>> config = ChatNotificationConfig(
    ...     discord_webhook="https://discord.com/api/webhooks/...",
    ...     triggers={"on_fail": True, "on_complete": True}
    ... )
    >>> notifier = ChatNotifier(config)
    >>> notifier.notify_job_completed(job)
"""

from __future__ import annotations

import logging
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Any

import httpx

from oclawma.queue.models import Job, JobStatus

logger = logging.getLogger(__name__)


class NotificationTrigger(str, Enum):
    """Notification trigger types."""

    ON_FAIL = "on_fail"
    ON_COMPLETE = "on_complete"
    ON_RETRY = "on_retry"


@dataclass
class ChatNotificationConfig:
    """Configuration for chat notifications.

    Attributes:
        discord_webhook: Discord webhook URL
        slack_webhook: Slack webhook URL
        triggers: Which events trigger notifications
        rate_limit_interval: Minimum seconds between notifications
        mention_on_critical: Whether to @mention on critical priority jobs
        discord_user_id: Discord user ID to mention
        slack_user_id: Slack user ID to mention
        channel_routing: Per-job-type routing configuration
        enabled: Whether notifications are enabled
        timeout: HTTP request timeout in seconds
    """

    discord_webhook: str | None = None
    slack_webhook: str | None = None
    triggers: dict[str, bool] = field(
        default_factory=lambda: {
            NotificationTrigger.ON_FAIL.value: True,
            NotificationTrigger.ON_COMPLETE.value: False,
            NotificationTrigger.ON_RETRY.value: False,
        }
    )
    rate_limit_interval: float = 60.0  # Minimum seconds between notifications
    mention_on_critical: bool = True
    discord_user_id: str | None = None
    slack_user_id: str | None = None
    channel_routing: dict[str, dict[str, Any]] = field(default_factory=dict)
    enabled: bool = True
    timeout: float = 10.0

    def to_dict(self) -> dict[str, Any]:
        """Convert config to dictionary."""
        return {
            "discord_webhook": self.discord_webhook,
            "slack_webhook": self.slack_webhook,
            "triggers": self.triggers,
            "rate_limit_interval": self.rate_limit_interval,
            "mention_on_critical": self.mention_on_critical,
            "discord_user_id": self.discord_user_id,
            "slack_user_id": self.slack_user_id,
            "channel_routing": self.channel_routing,
            "enabled": self.enabled,
            "timeout": self.timeout,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> ChatNotificationConfig:
        """Create config from dictionary."""
        return cls(
            discord_webhook=data.get("discord_webhook"),
            slack_webhook=data.get("slack_webhook"),
            triggers=data.get(
                "triggers",
                {
                    NotificationTrigger.ON_FAIL.value: True,
                    NotificationTrigger.ON_COMPLETE.value: False,
                    NotificationTrigger.ON_RETRY.value: False,
                },
            ),
            rate_limit_interval=data.get("rate_limit_interval", 60.0),
            mention_on_critical=data.get("mention_on_critical", True),
            discord_user_id=data.get("discord_user_id"),
            slack_user_id=data.get("slack_user_id"),
            channel_routing=data.get("channel_routing", {}),
            enabled=data.get("enabled", True),
            timeout=data.get("timeout", 10.0),
        )

    def should_trigger(self, trigger: NotificationTrigger) -> bool:
        """Check if a trigger is enabled."""
        return self.triggers.get(trigger.value, False)


@dataclass
class WebhookTestResult:
    """Result of a webhook test.

    Attributes:
        success: Whether the test succeeded
        platform: Platform tested (discord, slack)
        status_code: HTTP status code
        response: Response body or error message
        latency_ms: Request latency in milliseconds
    """

    success: bool
    platform: str
    status_code: int | None = None
    response: str | None = None
    latency_ms: float | None = None


def _get_status_color(status: JobStatus) -> int:
    """Get Discord embed color for job status.

    Args:
        status: Job status

    Returns:
        Integer color code
    """
    colors = {
        JobStatus.COMPLETED: 0x00FF00,  # Green
        JobStatus.FAILED: 0xFF0000,  # Red
        JobStatus.PENDING: 0xFFA500,  # Orange
        JobStatus.RUNNING: 0x0099FF,  # Blue
        JobStatus.PAUSED: 0x808080,  # Gray
    }
    return colors.get(status, 0x808080)


def _get_status_emoji(status: JobStatus) -> str:
    """Get emoji for job status.

    Args:
        status: Job status

    Returns:
        Emoji string
    """
    emojis = {
        JobStatus.COMPLETED: "✅",
        JobStatus.FAILED: "❌",
        JobStatus.PENDING: "⏳",
        JobStatus.RUNNING: "🔄",
        JobStatus.PAUSED: "⏸️",
    }
    return emojis.get(status, "❓")


class ChatNotifier:
    """Chat notification manager for job events.

    Features:
    - Discord webhook support with rich embeds
    - Slack webhook support with formatted blocks
    - Configurable triggers for different events
    - Rate limiting to prevent spam
    - @mention support for critical alerts
    - Per-job-type channel routing
    - Test webhook endpoints

    Example:
        >>> config = ChatNotificationConfig(
        ...     discord_webhook="https://discord.com/api/webhooks/...",
        ...     triggers={"on_fail": True, "on_complete": True}
        ... )
        >>> notifier = ChatNotifier(config)
        >>> notifier.notify_job_completed(job)
    """

    def __init__(self, config: ChatNotificationConfig | None = None) -> None:
        """Initialize the chat notifier.

        Args:
            config: Notification configuration
        """
        self.config = config or ChatNotificationConfig()
        self._last_notification_time: dict[str, float] = {}
        self._http_client: httpx.Client | None = None

    def _get_client(self) -> httpx.Client:
        """Get or create HTTP client."""
        if self._http_client is None:
            self._http_client = httpx.Client(timeout=self.config.timeout)
        return self._http_client

    def close(self) -> None:
        """Close the HTTP client."""
        if self._http_client:
            self._http_client.close()
            self._http_client = None

    def __enter__(self) -> ChatNotifier:
        """Context manager entry."""
        return self

    def __exit__(self, *args: Any) -> None:
        """Context manager exit."""
        self.close()

    def _is_rate_limited(self, key: str) -> bool:
        """Check if notification is rate limited.

        Args:
            key: Rate limit key (e.g., "discord:fail")

        Returns:
            True if rate limited
        """
        now = time.time()
        last_time = self._last_notification_time.get(key, 0)
        return (now - last_time) < self.config.rate_limit_interval

    def _record_notification(self, key: str) -> None:
        """Record a notification was sent.

        Args:
            key: Rate limit key
        """
        self._last_notification_time[key] = time.time()

    def _get_routing_config(self, job: Job) -> dict[str, Any]:
        """Get routing configuration for a job.

        Args:
            job: Job to get routing for

        Returns:
            Routing configuration dict
        """
        return self.config.channel_routing.get(job.job_type, {})

    def _should_mention(self, job: Job) -> bool:
        """Check if should mention for this job.

        Args:
            job: Job to check

        Returns:
            True if should mention
        """
        if not self.config.mention_on_critical:
            return False
        from oclawma.queue.models import JobPriority

        return job.priority == JobPriority.CRITICAL

    def _build_discord_embed(self, job: Job, title: str, description: str) -> dict[str, Any]:
        """Build Discord embed for job notification.

        Args:
            job: Job to build embed for
            title: Embed title
            description: Embed description

        Returns:
            Discord embed dictionary
        """
        fields = [
            {"name": "Job ID", "value": str(job.id), "inline": True},
            {"name": "Type", "value": job.job_type, "inline": True},
            {"name": "Priority", "value": job.priority.name, "inline": True},
        ]

        if job.retry_count > 0:
            fields.append(
                {"name": "Retry", "value": f"{job.retry_count}/{job.max_retries}", "inline": True}
            )

        if job.error:
            # Truncate error message if too long
            error_msg = job.error[:1000] + "..." if len(job.error) > 1000 else job.error
            fields.append({"name": "Error", "value": f"```{error_msg}```", "inline": False})

        embed = {
            "title": title,
            "description": description,
            "color": _get_status_color(job.status),
            "fields": fields,
            "timestamp": job.updated_at.isoformat() if job.updated_at else None,
            "footer": {"text": f"Job {job.id} • {job.job_type}"},
        }

        return embed

    def _build_slack_blocks(self, job: Job, title: str, description: str) -> list[dict[str, Any]]:
        """Build Slack blocks for job notification.

        Args:
            job: Job to build blocks for
            title: Message title
            description: Message description

        Returns:
            List of Slack block dictionaries
        """
        emoji = _get_status_emoji(job.status)

        blocks: list[dict[str, Any]] = [
            {"type": "header", "text": {"type": "plain_text", "text": f"{emoji} {title}"}},
            {"type": "section", "text": {"type": "mrkdwn", "text": description}},
            {
                "type": "section",
                "fields": [
                    {"type": "mrkdwn", "text": f"*Job ID:*\n{job.id}"},
                    {"type": "mrkdwn", "text": f"*Type:*\n{job.job_type}"},
                    {"type": "mrkdwn", "text": f"*Priority:*\n{job.priority.name}"},
                ],
            },
        ]

        if job.retry_count > 0:
            blocks[2]["fields"].append(
                {"type": "mrkdwn", "text": f"*Retry:*\n{job.retry_count}/{job.max_retries}"}
            )

        if job.error:
            error_msg = job.error[:1000] + "..." if len(job.error) > 1000 else job.error
            blocks.append(
                {
                    "type": "section",
                    "text": {"type": "mrkdwn", "text": f"*Error:*\n```{error_msg}```"},
                }
            )

        blocks.append(
            {
                "type": "context",
                "elements": [{"type": "mrkdwn", "text": f"Job {job.id} • {job.job_type}"}],
            }
        )

        return blocks

    def _send_discord_notification(
        self, job: Job, title: str, description: str, mention: bool = False
    ) -> bool:
        """Send notification to Discord.

        Args:
            job: Job to notify about
            title: Notification title
            description: Notification description
            mention: Whether to include @mention

        Returns:
            True if sent successfully
        """
        # Check routing config for job type
        routing = self._get_routing_config(job)
        webhook_url = routing.get("discord_webhook", self.config.discord_webhook)

        if not webhook_url:
            return False

        # Check rate limit
        rate_key = f"discord:{job.job_type}:{job.status.value}"
        if self._is_rate_limited(rate_key):
            logger.debug(f"Discord notification rate limited for {rate_key}")
            return False

        embed = self._build_discord_embed(job, title, description)
        payload: dict[str, Any] = {"embeds": [embed]}

        # Add mention if configured
        if mention and self.config.discord_user_id:
            payload["content"] = f"<@{self.config.discord_user_id}>"

        try:
            client = self._get_client()
            response = client.post(webhook_url, json=payload)
            response.raise_for_status()
            self._record_notification(rate_key)
            logger.debug(f"Discord notification sent for job {job.id}")
            return True
        except httpx.HTTPError as e:
            logger.warning(f"Failed to send Discord notification: {e}")
            return False
        except Exception as e:
            logger.error(f"Unexpected error sending Discord notification: {e}")
            return False

    def _send_slack_notification(
        self, job: Job, title: str, description: str, mention: bool = False
    ) -> bool:
        """Send notification to Slack.

        Args:
            job: Job to notify about
            title: Notification title
            description: Notification description
            mention: Whether to include @mention

        Returns:
            True if sent successfully
        """
        # Check routing config for job type
        routing = self._get_routing_config(job)
        webhook_url = routing.get("slack_webhook", self.config.slack_webhook)

        if not webhook_url:
            return False

        # Check rate limit
        rate_key = f"slack:{job.job_type}:{job.status.value}"
        if self._is_rate_limited(rate_key):
            logger.debug(f"Slack notification rate limited for {rate_key}")
            return False

        blocks = self._build_slack_blocks(job, title, description)
        payload: dict[str, Any] = {"blocks": blocks}

        # Add mention if configured
        if mention and self.config.slack_user_id:
            payload["text"] = f"<@{self.config.slack_user_id}> Critical job alert!"

        try:
            client = self._get_client()
            response = client.post(webhook_url, json=payload)
            response.raise_for_status()
            self._record_notification(rate_key)
            logger.debug(f"Slack notification sent for job {job.id}")
            return True
        except httpx.HTTPError as e:
            logger.warning(f"Failed to send Slack notification: {e}")
            return False
        except Exception as e:
            logger.error(f"Unexpected error sending Slack notification: {e}")
            return False

    def notify_job_completed(self, job: Job) -> bool:
        """Notify that a job completed successfully.

        Args:
            job: The completed job

        Returns:
            True if notification was sent
        """
        if not self.config.enabled:
            return False

        if not self.config.should_trigger(NotificationTrigger.ON_COMPLETE):
            return False

        title = f"Job Completed - {job.job_type}"
        description = f"Job **{job.id}** completed successfully."

        mention = self._should_mention(job)

        discord_sent = self._send_discord_notification(job, title, description, mention)
        slack_sent = self._send_slack_notification(job, title, description, mention)

        return discord_sent or slack_sent

    def notify_job_failed(self, job: Job) -> bool:
        """Notify that a job failed.

        Args:
            job: The failed job

        Returns:
            True if notification was sent
        """
        if not self.config.enabled:
            return False

        if not self.config.should_trigger(NotificationTrigger.ON_FAIL):
            return False

        title = f"Job Failed - {job.job_type}"
        description = f"Job **{job.id}** has failed."

        mention = self._should_mention(job)

        discord_sent = self._send_discord_notification(job, title, description, mention)
        slack_sent = self._send_slack_notification(job, title, description, mention)

        return discord_sent or slack_sent

    def notify_job_retry(self, job: Job) -> bool:
        """Notify that a job is being retried.

        Args:
            job: The job being retried

        Returns:
            True if notification was sent
        """
        if not self.config.enabled:
            return False

        if not self.config.should_trigger(NotificationTrigger.ON_RETRY):
            return False

        title = f"Job Retry - {job.job_type}"
        description = f"Job **{job.id}** is being retried ({job.retry_count}/{job.max_retries})."

        mention = self._should_mention(job)

        discord_sent = self._send_discord_notification(job, title, description, mention)
        slack_sent = self._send_slack_notification(job, title, description, mention)

        return discord_sent or slack_sent

    def test_discord_webhook(self, webhook_url: str | None = None) -> WebhookTestResult:
        """Test Discord webhook connectivity.

        Args:
            webhook_url: Webhook URL to test (uses config if not provided)

        Returns:
            WebhookTestResult with test details
        """
        url = webhook_url or self.config.discord_webhook
        if not url:
            return WebhookTestResult(
                success=False, platform="discord", response="No webhook URL configured"
            )

        payload = {
            "content": "🔔 Test notification from Oclawma",
            "embeds": [
                {
                    "title": "Webhook Test",
                    "description": "This is a test notification to verify webhook connectivity.",
                    "color": 0x0099FF,
                    "fields": [
                        {"name": "Status", "value": "✅ Connected", "inline": True},
                        {"name": "Timestamp", "value": f"<t:{int(time.time())}:R>", "inline": True},
                    ],
                }
            ],
        }

        start = time.time()
        try:
            client = self._get_client()
            response = client.post(url, json=payload)
            response.raise_for_status()
            latency = (time.time() - start) * 1000
            return WebhookTestResult(
                success=True,
                platform="discord",
                status_code=response.status_code,
                response="Webhook test successful",
                latency_ms=latency,
            )
        except httpx.HTTPStatusError as e:
            latency = (time.time() - start) * 1000
            return WebhookTestResult(
                success=False,
                platform="discord",
                status_code=e.response.status_code,
                response=f"HTTP error: {e.response.status_code}",
                latency_ms=latency,
            )
        except httpx.RequestError as e:
            latency = (time.time() - start) * 1000
            return WebhookTestResult(
                success=False,
                platform="discord",
                response=f"Request error: {str(e)}",
                latency_ms=latency,
            )
        except Exception as e:
            latency = (time.time() - start) * 1000
            return WebhookTestResult(
                success=False,
                platform="discord",
                response=f"Unexpected error: {str(e)}",
                latency_ms=latency,
            )

    def test_slack_webhook(self, webhook_url: str | None = None) -> WebhookTestResult:
        """Test Slack webhook connectivity.

        Args:
            webhook_url: Webhook URL to test (uses config if not provided)

        Returns:
            WebhookTestResult with test details
        """
        url = webhook_url or self.config.slack_webhook
        if not url:
            return WebhookTestResult(
                success=False, platform="slack", response="No webhook URL configured"
            )

        payload = {
            "text": "🔔 Test notification from Oclawma",
            "blocks": [
                {"type": "header", "text": {"type": "plain_text", "text": "Webhook Test"}},
                {
                    "type": "section",
                    "text": {
                        "type": "mrkdwn",
                        "text": "This is a test notification to verify webhook connectivity.",
                    },
                },
                {
                    "type": "section",
                    "fields": [
                        {"type": "mrkdwn", "text": "*Status:*\n✅ Connected"},
                        {"type": "mrkdwn", "text": f"*Time:*\n{int(time.time())}"},
                    ],
                },
            ],
        }

        start = time.time()
        try:
            client = self._get_client()
            response = client.post(url, json=payload)
            response.raise_for_status()
            latency = (time.time() - start) * 1000
            return WebhookTestResult(
                success=True,
                platform="slack",
                status_code=response.status_code,
                response="Webhook test successful",
                latency_ms=latency,
            )
        except httpx.HTTPStatusError as e:
            latency = (time.time() - start) * 1000
            return WebhookTestResult(
                success=False,
                platform="slack",
                status_code=e.response.status_code,
                response=f"HTTP error: {e.response.status_code}",
                latency_ms=latency,
            )
        except httpx.RequestError as e:
            latency = (time.time() - start) * 1000
            return WebhookTestResult(
                success=False,
                platform="slack",
                response=f"Request error: {str(e)}",
                latency_ms=latency,
            )
        except Exception as e:
            latency = (time.time() - start) * 1000
            return WebhookTestResult(
                success=False,
                platform="slack",
                response=f"Unexpected error: {str(e)}",
                latency_ms=latency,
            )

    def test_all_webhooks(self) -> list[WebhookTestResult]:
        """Test all configured webhooks.

        Returns:
            List of WebhookTestResult for each test
        """
        results: list[WebhookTestResult] = []

        if self.config.discord_webhook:
            results.append(self.test_discord_webhook())

        if self.config.slack_webhook:
            results.append(self.test_slack_webhook())

        # Test routed webhooks
        for job_type, routing in self.config.channel_routing.items():
            if "discord_webhook" in routing:
                result = self.test_discord_webhook(routing["discord_webhook"])
                result.platform = f"discord:{job_type}"
                results.append(result)
            if "slack_webhook" in routing:
                result = self.test_slack_webhook(routing["slack_webhook"])
                result.platform = f"slack:{job_type}"
                results.append(result)

        return results
