"""
Servizio Notifiche Multi-canale – CRUD canali, regole, log + dispatch.

Cifratura: AES-256-GCM via libreria cryptography.
  Formato config_enc: base64( nonce(12) || ciphertext+tag )
  Chiave: ENCRYPTION_KEY (hex 64 char = 32 byte) dalla config.

Backward compatibility: se il valore non decrittografa come GCM, viene
tentato il fallback a base64 grezzo (configurazioni precedenti al fix).
"""

import base64
import json
import logging
import os
import uuid
from datetime import datetime, timezone

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.config import get_settings
from app.core.exceptions import NotFoundError
from app.models.notification import NotificationChannel, NotificationLog, NotificationRule
from app.schemas.notification import (
    ChannelTestResult,
    NotificationChannelCreate,
    NotificationChannelUpdate,
    NotificationRuleCreate,
    NotificationRuleUpdate,
)

logger = logging.getLogger(__name__)
settings = get_settings()


# ─── Cifratura AES-256-GCM ────────────────────────────────────────────────────

def _encrypt(data: dict, key: bytes | None = None) -> str:
    """
    Cifra un dict JSON con AES-256-GCM.

    Formato output: base64( nonce(12 byte) || ciphertext+tag(16 byte) )
    """
    if key is None:
        key = settings.encryption_key_bytes
    nonce = os.urandom(12)
    aesgcm = AESGCM(key)
    plaintext = json.dumps(data, ensure_ascii=False).encode("utf-8")
    ciphertext = aesgcm.encrypt(nonce, plaintext, None)  # include tag
    return base64.b64encode(nonce + ciphertext).decode("ascii")


def _decrypt(enc: str, key: bytes | None = None) -> dict:
    """
    Decifra il valore prodotto da _encrypt.

    Backward compatible: se il dato non e' GCM valido, prova il
    vecchio base64 grezzo (usato prima del fix di sicurezza).
    """
    if key is None:
        key = settings.encryption_key_bytes
    try:
        raw = base64.b64decode(enc.encode("ascii"))
        if len(raw) > 28:  # 12 nonce + 16 tag minimo
            nonce = raw[:12]
            ciphertext = raw[12:]
            aesgcm = AESGCM(key)
            plaintext = aesgcm.decrypt(nonce, ciphertext, None)
            return json.loads(plaintext.decode("utf-8"))
    except Exception:
        pass

    # Fallback: base64 grezzo (configurazioni create prima del fix GCM)
    try:
        raw = base64.b64decode(enc.encode("ascii"))
        return json.loads(raw.decode("utf-8"))
    except Exception:
        return {}


class NotificationService:
    def __init__(self, db: AsyncSession) -> None:
        self.db = db

    # ─── Channels ────────────────────────────────────────────────────────────

    async def create_channel(
        self,
        tenant_id: uuid.UUID,
        data: NotificationChannelCreate,
        created_by: uuid.UUID,
    ) -> NotificationChannel:
        config_enc = None
        if data.config_secret:
            config_enc = _encrypt(data.config_secret)

        channel = NotificationChannel(
            tenant_id=tenant_id,
            name=data.name,
            channel_type=data.channel_type,
            config=data.config,
            config_enc=config_enc,
            created_by=created_by,
        )
        self.db.add(channel)
        await self.db.flush()
        return channel

    async def list_channels(
        self,
        tenant_id: uuid.UUID,
        page: int = 1,
        page_size: int = 20,
    ) -> tuple[list[NotificationChannel], int]:
        query = select(NotificationChannel).where(
            NotificationChannel.tenant_id == tenant_id
        ).order_by(NotificationChannel.created_at.desc())

        count_result = await self.db.execute(
            select(func.count()).select_from(query.subquery())
        )
        total = count_result.scalar_one()

        query = query.offset((page - 1) * page_size).limit(page_size)
        result = await self.db.execute(query)
        return list(result.scalars().all()), total

    async def get_channel(
        self, channel_id: uuid.UUID, tenant_id: uuid.UUID
    ) -> NotificationChannel:
        channel = await self.db.get(NotificationChannel, channel_id)
        if not channel or channel.tenant_id != tenant_id:
            raise NotFoundError("canale di notifica")
        return channel

    async def update_channel(
        self,
        channel_id: uuid.UUID,
        tenant_id: uuid.UUID,
        data: NotificationChannelUpdate,
    ) -> NotificationChannel:
        channel = await self.get_channel(channel_id, tenant_id)

        if data.name is not None:
            channel.name = data.name
        if data.is_active is not None:
            channel.is_active = data.is_active
        if data.config is not None:
            channel.config = data.config
        if data.config_secret is not None:
            # Re-cifra sempre con AES-256-GCM (aggiorna anche i vecchi base64)
            channel.config_enc = _encrypt(data.config_secret)

        await self.db.flush()
        return channel

    async def delete_channel(
        self, channel_id: uuid.UUID, tenant_id: uuid.UUID
    ) -> None:
        channel = await self.get_channel(channel_id, tenant_id)
        await self.db.delete(channel)

    async def test_channel(
        self, channel_id: uuid.UUID, tenant_id: uuid.UUID
    ) -> ChannelTestResult:
        """
        Invia un messaggio di test reale al canale configurato.

        Esegue invio effettivo per Telegram, Webhook, Email SMTP e WhatsApp.
        """
        channel = await self.get_channel(channel_id, tenant_id)

        if not channel.is_active:
            return ChannelTestResult(
                success=False,
                message="Il canale e' disabilitato",
            )

        if channel.circuit_open_until and channel.circuit_open_until > datetime.now(timezone.utc):
            return ChannelTestResult(
                success=False,
                message=f"Circuit breaker aperto fino a {channel.circuit_open_until.isoformat()}",
            )

        config = channel.config or {}
        secret = {}
        if channel.config_enc:
            try:
                secret = _decrypt(channel.config_enc)
            except Exception as e:
                return ChannelTestResult(
                    success=False,
                    message=f"Errore decifratura configurazione sensibile: {e}",
                )

        channel_type = channel.channel_type

        # ── Telegram ──────────────────────────────────────────────────────────
        if channel_type == "telegram":
            if not config.get("chat_id"):
                return ChannelTestResult(success=False, message="Chat ID Telegram non configurato")
            bot_token = secret.get("bot_token")
            if not bot_token:
                return ChannelTestResult(success=False, message="Bot token Telegram non configurato")
            try:
                from app.notifications.telegram import TelegramError, send_test_message
                result = await send_test_message(
                    bot_token=bot_token,
                    chat_id=str(config["chat_id"]),
                    channel_name=channel.name,
                )
                msg_id = result.get("message_id")
                return ChannelTestResult(
                    success=True,
                    message=f"Messaggio Telegram inviato (message_id={msg_id}).",
                    http_status=200,
                )
            except Exception as exc:
                return ChannelTestResult(
                    success=False,
                    message=f"Errore Telegram: {exc}",
                )

        # ── Webhook ───────────────────────────────────────────────────────────
        elif channel_type == "webhook":
            url = config.get("url")
            if not url:
                return ChannelTestResult(success=False, message="URL webhook non configurato")
            webhook_secret = secret.get("webhook_secret")
            try:
                from app.notifications.webhook import WebhookError, send_test_webhook
                result = await send_test_webhook(
                    url=url,
                    webhook_secret=webhook_secret,
                    channel_name=channel.name,
                )
                return ChannelTestResult(
                    success=True,
                    message=(
                        f"Webhook raggiunto con successo "
                        f"(HTTP {result['http_status']}, delivery={result['delivery_id']})."
                    ),
                    http_status=result["http_status"],
                )
            except Exception as exc:
                http_status = getattr(exc, "http_status", None)
                return ChannelTestResult(
                    success=False,
                    message=f"Errore webhook: {exc}",
                    http_status=http_status,
                )

        # ── Email SMTP ────────────────────────────────────────────────────────
        elif channel_type == "email":
            smtp_host = config.get("smtp_host")
            smtp_port = config.get("smtp_port", 465)
            from_email = config.get("from_email")
            to_email = config.get("to_email")
            smtp_user = config.get("smtp_user") or from_email
            use_tls = config.get("smtp_use_tls", True)
            use_starttls = config.get("smtp_use_starttls", False)
            from_name = config.get("from_name", "PEChub Notifiche")
            smtp_password = secret.get("smtp_password", "")

            if not smtp_host:
                return ChannelTestResult(success=False, message="Host SMTP non configurato")
            if not from_email:
                return ChannelTestResult(success=False, message="Email mittente non configurata")
            if not to_email:
                return ChannelTestResult(success=False, message="Email destinatario non configurata")
            if not smtp_password:
                return ChannelTestResult(success=False, message="Password SMTP non configurata")

            try:
                from app.notifications.email_smtp import EmailSMTPError, send_test_email
                await send_test_email(
                    smtp_host=smtp_host,
                    smtp_port=int(smtp_port),
                    smtp_user=smtp_user,
                    smtp_password=smtp_password,
                    from_email=from_email,
                    to_email=to_email,
                    channel_name=channel.name,
                    from_name=from_name,
                    use_tls=use_tls,
                    use_starttls=use_starttls,
                )
                return ChannelTestResult(
                    success=True,
                    message=f"Email di test inviata con successo a {to_email}.",
                    http_status=200,
                )
            except Exception as exc:
                return ChannelTestResult(
                    success=False,
                    message=f"Errore email: {exc}",
                )

        # ── WhatsApp ──────────────────────────────────────────────────────────
        elif channel_type == "whatsapp":
            phone_number_id = config.get("phone_number_id")
            to_phone = config.get("to_phone")
            access_token = secret.get("access_token")

            if not phone_number_id:
                return ChannelTestResult(success=False, message="phone_number_id non configurato")
            if not to_phone:
                return ChannelTestResult(success=False, message="Numero WhatsApp destinatario non configurato")
            if not access_token:
                return ChannelTestResult(success=False, message="Access token Meta non configurato")

            try:
                from app.notifications.whatsapp import WhatsAppError, send_test_whatsapp
                result = await send_test_whatsapp(
                    phone_number_id=phone_number_id,
                    to_phone=to_phone,
                    access_token=access_token,
                    channel_name=channel.name,
                )
                return ChannelTestResult(
                    success=True,
                    message=f"Messaggio WhatsApp inviato (message_id={result.get('message_id')}).",
                    http_status=200,
                )
            except Exception as exc:
                http_status = getattr(exc, "http_status", None)
                return ChannelTestResult(
                    success=False,
                    message=f"Errore WhatsApp: {exc}",
                    http_status=http_status,
                )

        # ── Tipo sconosciuto ──────────────────────────────────────────────────
        return ChannelTestResult(
            success=False,
            message=f"Tipo canale '{channel_type}' non supportato",
        )

    # ─── Rules ───────────────────────────────────────────────────────────────

    async def create_rule(
        self,
        tenant_id: uuid.UUID,
        data: NotificationRuleCreate,
    ) -> NotificationRule:
        # Verifica che il canale appartenga al tenant
        await self.get_channel(data.channel_id, tenant_id)

        rule = NotificationRule(
            tenant_id=tenant_id,
            channel_id=data.channel_id,
            name=data.name,
            event_type=data.event_type,
            filter=data.filter,
        )
        self.db.add(rule)
        await self.db.flush()
        return rule

    async def list_rules(
        self,
        tenant_id: uuid.UUID,
        channel_id: uuid.UUID | None = None,
        page: int = 1,
        page_size: int = 50,
    ) -> tuple[list[NotificationRule], int]:
        query = select(NotificationRule).where(
            NotificationRule.tenant_id == tenant_id
        ).order_by(NotificationRule.created_at.desc())

        if channel_id:
            query = query.where(NotificationRule.channel_id == channel_id)

        count_result = await self.db.execute(
            select(func.count()).select_from(query.subquery())
        )
        total = count_result.scalar_one()

        query = query.offset((page - 1) * page_size).limit(page_size)
        result = await self.db.execute(query)
        return list(result.scalars().all()), total

    async def get_rule(
        self, rule_id: uuid.UUID, tenant_id: uuid.UUID
    ) -> NotificationRule:
        rule = await self.db.get(NotificationRule, rule_id)
        if not rule or rule.tenant_id != tenant_id:
            raise NotFoundError("regola di notifica")
        return rule

    async def update_rule(
        self,
        rule_id: uuid.UUID,
        tenant_id: uuid.UUID,
        data: NotificationRuleUpdate,
    ) -> NotificationRule:
        rule = await self.get_rule(rule_id, tenant_id)

        if data.name is not None:
            rule.name = data.name
        if data.event_type is not None:
            rule.event_type = data.event_type
        if data.filter is not None:
            rule.filter = data.filter
        if data.is_active is not None:
            rule.is_active = data.is_active

        await self.db.flush()
        return rule

    async def delete_rule(
        self, rule_id: uuid.UUID, tenant_id: uuid.UUID
    ) -> None:
        rule = await self.get_rule(rule_id, tenant_id)
        await self.db.delete(rule)

    # ─── Logs ────────────────────────────────────────────────────────────────

    async def list_logs(
        self,
        tenant_id: uuid.UUID,
        channel_id: uuid.UUID | None = None,
        page: int = 1,
        page_size: int = 50,
    ) -> tuple[list[NotificationLog], int]:
        query = select(NotificationLog).where(
            NotificationLog.tenant_id == tenant_id
        ).order_by(NotificationLog.created_at.desc())

        if channel_id:
            query = query.where(NotificationLog.channel_id == channel_id)

        count_result = await self.db.execute(
            select(func.count()).select_from(query.subquery())
        )
        total = count_result.scalar_one()

        query = query.offset((page - 1) * page_size).limit(page_size)
        result = await self.db.execute(query)
        return list(result.scalars().all()), total
