"""Notification plugin — multi-channel alerts with throttling."""

from __future__ import annotations

import asyncio
import logging
import smtplib
import time
from email.message import EmailMessage
from typing import Any

import httpx

from agent_scout.plugins.base import Plugin, PluginConfig, PluginParam, ToolSpec

logger = logging.getLogger(__name__)


class NotificationChannel:
    """Base class for notification delivery channels."""

    name: str = ""

    async def send(self, title: str, message: str) -> bool:
        """Send a notification. Returns True on success."""
        raise NotImplementedError

    def is_configured(self) -> bool:
        """Whether this channel has valid configuration."""
        return False


class TerminalChannel(NotificationChannel):
    """Print notifications to the terminal (always available)."""

    name = "terminal"

    async def send(self, title: str, message: str) -> bool:
        # In real usage this would go through Rich console
        logger.info("[NOTIFICATION] %s: %s", title, message)
        return True

    def is_configured(self) -> bool:
        return True


class DashboardChannel(NotificationChannel):
    """Push notifications to the web dashboard (always available)."""

    name = "dashboard"

    def __init__(self, event_bus: Any = None) -> None:
        self._bus = event_bus

    async def send(self, title: str, message: str) -> bool:
        if self._bus:
            from agent_scout.events import Event, EventType

            await self._bus.emit(
                Event(
                    event_type=EventType.TASK_STATE_CHANGE,
                    data={"notification": True, "title": title, "message": message},
                )
            )
        return True

    def is_configured(self) -> bool:
        return True


class EmailChannel(NotificationChannel):
    """Send notifications via SMTP email."""

    name = "email"

    def __init__(
        self,
        smtp_host: str = "",
        smtp_port: int = 587,
        username: str = "",
        password: str = "",
        from_addr: str = "",
        to_addr: str = "",
    ) -> None:
        self._host = smtp_host
        self._port = smtp_port
        self._username = username
        self._password = password
        self._from = from_addr
        self._to = to_addr

    def is_configured(self) -> bool:
        return bool(self._host and self._from and self._to)

    async def send(self, title: str, message: str) -> bool:
        if not self.is_configured():
            return False
        try:
            msg = EmailMessage()
            msg["Subject"] = f"[AgentScout] {title}"
            msg["From"] = self._from
            msg["To"] = self._to
            msg.set_content(message)

            await asyncio.to_thread(self._send_smtp, msg)
            return True
        except Exception:
            logger.exception("Email notification failed")
            return False

    def _send_smtp(self, msg: EmailMessage) -> None:
        with smtplib.SMTP(self._host, self._port) as server:
            server.starttls()
            if self._username and self._password:
                server.login(self._username, self._password)
            server.send_message(msg)


class WebhookChannel(NotificationChannel):
    """Send notifications via webhook (Slack, Discord, Telegram)."""

    def __init__(self, name: str, webhook_url: str = "", payload_builder: Any = None) -> None:
        self.name = name
        self._url = webhook_url
        self._payload_builder = payload_builder

    def is_configured(self) -> bool:
        return bool(self._url)

    async def send(self, title: str, message: str) -> bool:
        if not self.is_configured():
            return False
        try:
            if self._payload_builder:
                payload = self._payload_builder(title, message)
            else:
                payload = self._default_payload(title, message)

            async with httpx.AsyncClient() as client:
                resp = await client.post(self._url, json=payload, timeout=10)
                return resp.status_code < 400
        except Exception:
            logger.exception("Webhook notification failed for %s", self.name)
            return False

    def _default_payload(self, title: str, message: str) -> dict[str, Any]:
        """Default payload format (works for Slack/Discord)."""
        return {"content": f"**{title}**\n{message}"}


def _slack_payload(title: str, message: str) -> dict[str, Any]:
    return {"text": f"*{title}*\n{message}"}


def _discord_payload(title: str, message: str) -> dict[str, Any]:
    return {"content": f"**{title}**\n{message}"}


def _telegram_payload(title: str, message: str) -> dict[str, Any]:
    return {"text": f"<b>{title}</b>\n{message}", "parse_mode": "HTML"}


class Throttle:
    """Simple notification throttle — max N messages per period."""

    def __init__(self, max_per_period: int = 10, period_seconds: float = 60) -> None:
        self._max = max_per_period
        self._period = period_seconds
        self._sent: list[float] = []

    def allow(self) -> bool:
        now = time.monotonic()
        self._sent = [t for t in self._sent if now - t < self._period]
        if len(self._sent) < self._max:
            self._sent.append(now)
            return True
        return False


class NotifierPlugin(Plugin):
    """Multi-channel notification plugin with throttling.

    Primary channels (always on): terminal, dashboard.
    Optional channels: email (SMTP), Slack, Discord, Telegram webhooks.
    """

    def __init__(
        self,
        config: PluginConfig | None = None,
        event_bus: Any = None,
    ) -> None:
        super().__init__(config)
        self._bus = event_bus
        self._channels: list[NotificationChannel] = []
        self._throttle = Throttle()

    @property
    def name(self) -> str:
        return "notifier"

    @property
    def description(self) -> str:
        return "Send notifications through multiple channels"

    def get_tools(self) -> list[ToolSpec]:
        return [
            ToolSpec(
                name="notify",
                description=(
                    "Send a notification to the user through all configured channels. "
                    "Use this for important alerts, task completions, or price drops."
                ),
                parameters=[
                    PluginParam(name="title", description="Notification title", required=True),
                    PluginParam(name="message", description="Notification body", required=True),
                    PluginParam(
                        name="channels",
                        description="Specific channels to use (comma-separated). Defaults to all.",
                    ),
                ],
            ),
        ]

    async def on_load(self) -> None:
        settings = self._config.settings

        # Primary channels (always on)
        self._channels.append(TerminalChannel())
        self._channels.append(DashboardChannel(self._bus))

        # Optional: Email
        if settings.get("smtp_host"):
            self._channels.append(
                EmailChannel(
                    smtp_host=settings.get("smtp_host", ""),
                    smtp_port=int(settings.get("smtp_port", 587)),
                    username=settings.get("smtp_username", ""),
                    password=settings.get("smtp_password", ""),
                    from_addr=settings.get("smtp_from", ""),
                    to_addr=settings.get("smtp_to", ""),
                )
            )

        # Optional: Slack
        if settings.get("slack_webhook_url"):
            self._channels.append(
                WebhookChannel("slack", settings["slack_webhook_url"], _slack_payload)
            )

        # Optional: Discord
        if settings.get("discord_webhook_url"):
            self._channels.append(
                WebhookChannel("discord", settings["discord_webhook_url"], _discord_payload)
            )

        # Optional: Telegram
        if settings.get("telegram_bot_token") and settings.get("telegram_chat_id"):
            token = settings["telegram_bot_token"]
            chat_id = settings["telegram_chat_id"]
            url = f"https://api.telegram.org/bot{token}/sendMessage"

            def tg_payload(title: str, message: str) -> dict[str, Any]:
                payload = _telegram_payload(title, message)
                payload["chat_id"] = chat_id
                return payload

            self._channels.append(WebhookChannel("telegram", url, tg_payload))

        # Throttle config
        max_per_min = int(settings.get("max_notifications_per_minute", 10))
        self._throttle = Throttle(max_per_period=max_per_min)

    def set_channels(self, channels: list[NotificationChannel]) -> None:
        """Override channels (useful for testing)."""
        self._channels = channels

    async def execute(self, tool_name: str, tool_input: dict[str, Any]) -> str:
        if tool_name != "notify":
            return f"Unknown tool: {tool_name}"

        title = tool_input.get("title", "")
        message = tool_input.get("message", "")
        channel_filter = tool_input.get("channels", "")

        if not title or not message:
            return "Error: title and message are required"

        if not self._throttle.allow():
            return "Notification throttled — too many notifications sent recently"

        # Determine which channels to use
        channels = self._channels
        if channel_filter:
            names = {n.strip() for n in channel_filter.split(",")}
            channels = [c for c in channels if c.name in names]

        if not channels:
            return "No notification channels available"

        # Send to all channels concurrently
        results = await asyncio.gather(
            *[c.send(title, message) for c in channels if c.is_configured()],
            return_exceptions=True,
        )

        sent = sum(1 for r in results if r is True)
        failed = len(results) - sent

        if sent == 0:
            return "Failed to send notification to any channel"
        if failed > 0:
            return f"Notification sent to {sent} channel(s), {failed} failed"
        return f"Notification sent to {sent} channel(s)"
