"""
Invoice event listener.

Handles domain events related to invoices:

  transaction.completed  → create Invoice from PaymentRequest (idempotent)
                         → emit invoice.prepared
  invoice.prepared       → emit notification.email_requested for delivery
  transaction.refunded   → update invoice status
  invoice.*              → write InvoiceActivity record (activity log)
"""

from __future__ import annotations

import logging
from typing import Any, Dict

from src.core.utils.enums import InvoiceActivityTypes
from src.events.base import BaseEvent
from src.events.dispatcher import on_event_async

logger = logging.getLogger(__name__)

# ── event_type → InvoiceActivityTypes mapping ─────────────────────────────────
_EVENT_ACTIVITY_MAP: Dict[str, str] = {
    # invoice.prepared is excluded: activity is written directly in prepare_invoice_from_payment_request
    "invoice.created": InvoiceActivityTypes.INVOICE_CREATED,
    "invoice.draft_saved": InvoiceActivityTypes.INVOICE_DRAFT_SAVED,
    "invoice.updated": InvoiceActivityTypes.INVOICE_UPDATED,
    "invoice.published": InvoiceActivityTypes.INVOICE_PUBLISHED,
    "invoice.sent": InvoiceActivityTypes.INVOICE_SENT,
    "invoice.viewed": InvoiceActivityTypes.INVOICE_VIEWED,
    "invoice.paid": InvoiceActivityTypes.INVOICE_PAID,
    "invoice.partially_paid": InvoiceActivityTypes.INVOICE_PARTIALLY_PAID,
    "invoice.overdue": InvoiceActivityTypes.INVOICE_OVERDUE,
    "invoice.cancelled": InvoiceActivityTypes.INVOICE_CANCELLED,
    "invoice.closed": InvoiceActivityTypes.INVOICE_CLOSED,
    "invoice.duplicated": InvoiceActivityTypes.INVOICE_DUPLICATED,
    "invoice.reminder_sent": InvoiceActivityTypes.INVOICE_REMINDER_SENT,
    "invoice.reminder_failed": InvoiceActivityTypes.INVOICE_REMINDER_FAILED,
    "invoice.attachment_added": InvoiceActivityTypes.INVOICE_ATTACHMENT_ADDED,
    "invoice.attachment_removed": InvoiceActivityTypes.INVOICE_ATTACHMENT_REMOVED,
    "invoice.authorized": InvoiceActivityTypes.INVOICE_AUTHORIZED,
    "invoice.pdf_generated": InvoiceActivityTypes.INVOICE_PDF_GENERATED,
}


@on_event_async("transaction.completed")
async def prepare_invoice(event: BaseEvent) -> None:
    """
    Create an Invoice record from a completed transaction's PaymentRequest.

    Idempotent: skips if this (event_id, handler) pair has already been
    processed, or if an Invoice for this payment_request_id already exists.
    """
    from sqlalchemy import select

    from src.apps.invoices.services.invoice_services import prepare_invoice_from_payment_request
    from src.core.database import SessionCelery
    from src.events.dispatcher import EventDispatcher
    from src.events.utils import is_event_processed, mark_event_processed

    data: Any = event.data
    payment_request_id = data.get("payment_request_id")
    if not payment_request_id:
        return

    transaction_id = data.get("transaction_id")

    try:
        with SessionCelery() as db:
            if await is_event_processed(db, str(event.event_id), "prepare_invoice"):
                return

            invoice = await prepare_invoice_from_payment_request(payment_request_id, db)
            if invoice is None:
                logger.warning(
                    "prepare_invoice: could not create invoice for PR %s — PR not found or missing customer",
                    payment_request_id,
                )
                await mark_event_processed(db, str(event.event_id), event.event_type, "prepare_invoice")
                return

            # Link the transaction to the invoice via the M2M table so that the
            # Associations tab can find it. Skip if already linked.
            if transaction_id:
                from src.apps.transactions.models.transactions import Transactions
                txn = db.execute(
                    select(Transactions).where(Transactions.id == transaction_id)
                ).scalar_one_or_none()
                if txn and txn not in invoice.transactions:
                    invoice.transactions.append(txn)
                    logger.info(
                        "prepare_invoice: linked transaction %s to invoice %s",
                        transaction_id,
                        invoice.invoice_literal,
                    )

            logger.info(
                "prepare_invoice: invoice %s ready for PR %s",
                invoice.invoice_literal,
                payment_request_id,
            )

            await mark_event_processed(db, str(event.event_id), event.event_type, "prepare_invoice")

        # Emit invoice.prepared so downstream listeners can react
        await EventDispatcher.dispatch(
            BaseEvent(
                event_type="invoice.prepared",
                data={
                    "invoice_id": invoice.id,
                    "invoice_literal": invoice.invoice_literal,
                    "payment_request_id": payment_request_id,
                    "merchant_id": invoice.merchant_id,
                    "customer_id": invoice.customer_id,
                    "amount": float(invoice.amount or 0),
                    "currency": data.get("currency", "USD"),
                    "due_date": invoice.due_date.isoformat() if invoice.due_date else None,
                },
                correlation_id=event.correlation_id,
            )
        )
    except Exception as exc:
        logger.error("prepare_invoice failed for PR %s: %s", payment_request_id, exc)


@on_event_async("invoice.prepared")
async def dispatch_invoice_notification(event: BaseEvent) -> None:
    """
    Emit notification.email_requested for invoice delivery after an invoice
    is prepared from a completed transaction.
    """
    from sqlalchemy import select

    from src.apps.customers.models.customer import Customer
    from src.core.database import SessionCelery
    from src.events.dispatcher import EventDispatcher

    data: Any = event.data
    invoice_id = data.get("invoice_id")
    customer_id = data.get("customer_id")
    merchant_id = data.get("merchant_id")

    if not invoice_id or not customer_id:
        return

    try:
        with SessionCelery() as db:
            stmt = select(Customer).where(
                Customer.id == customer_id,
                Customer.merchant_id == merchant_id,
                Customer.deleted_at.is_(None),
            )
            customer = db.execute(stmt).scalar_one_or_none()
            if not customer:
                logger.warning(
                    "dispatch_invoice_notification: customer %s not found for invoice %s",
                    customer_id,
                    invoice_id,
                )
                return

            customer_email = getattr(customer, "email", None)
            if not customer_email:
                logger.warning(
                    "dispatch_invoice_notification: no email for customer %s",
                    customer_id,
                )
                return

        await EventDispatcher.dispatch(
            BaseEvent(
                event_type="notification.email_requested",
                data={
                    "to_email": customer_email,
                    "template_key": "invoice_prepared",
                    "template_vars": {
                        "invoice_id": data.get("invoice_literal") or str(invoice_id),
                        "merchant_name": "",
                        "amount": f"{float(data.get('amount', 0)):.2f}",
                        "due_date": data.get("due_date") or "",
                    },
                    "merchant_id": merchant_id,
                },
                correlation_id=event.correlation_id,
            )
        )
    except Exception as exc:
        logger.error(
            "dispatch_invoice_notification failed for invoice %s: %s", invoice_id, exc
        )


@on_event_async("transaction.refunded")
async def handle_invoice_refund(event: BaseEvent) -> None:
    """
    Update invoice status when a transaction refund is processed.
    """
    from sqlalchemy import select

    from src.apps.invoices.models.invoice import Invoice
    from src.core.database import SessionCelery
    from src.core.utils.enums import InvoiceStatusTypes

    data: Any = event.data
    payment_request_id = data.get("payment_request_id")
    if not payment_request_id:
        return

    try:
        with SessionCelery() as db:
            stmt = select(Invoice).where(
                Invoice.payment_request_id == payment_request_id,
                Invoice.deleted_at.is_(None),
            )
            invoice = db.execute(stmt).scalar_one_or_none()
            if not invoice:
                logger.warning(
                    "handle_invoice_refund: no invoice found for PR %s", payment_request_id
                )
                return

            refund_amount = float(data.get("refund_amount", 0))
            if refund_amount >= float(invoice.amount or 0):
                invoice.status = InvoiceStatusTypes.CANCELLED
            else:
                invoice.status = InvoiceStatusTypes.PARTIALLY_PAID

            db.commit()
            logger.info(
                "handle_invoice_refund: invoice %s status updated to %s for PR %s",
                invoice.invoice_literal,
                invoice.status,
                payment_request_id,
            )
    except Exception as exc:
        logger.error(
            "handle_invoice_refund failed for PR %s: %s", payment_request_id, exc
        )


# ── Activity logging for all invoice.* events ─────────────────────────────────

@on_event_async("invoice.prepared")
async def log_invoice_activity_prepared(event: BaseEvent) -> None:
    await _log_invoice_activity(event)


@on_event_async("invoice.sent")
async def log_invoice_activity_sent(event: BaseEvent) -> None:
    await _log_invoice_activity(event)


@on_event_async("invoice.paid")
async def log_invoice_activity_paid(event: BaseEvent) -> None:
    await _log_invoice_activity(event)


@on_event_async("invoice.overdue")
async def handle_overdue_notification(event: BaseEvent) -> None:
    """
    Send an overdue notification email to the customer.

    The mark_overdue_invoices Celery task already writes the
    InvoiceActivity(INVOICE_OVERDUE) record directly, so this handler
    only handles the customer-facing email dispatch.
    """
    from sqlalchemy import select

    from src.apps.customers.models.customer import Customer
    from src.core.database import SessionCelery
    from src.events.dispatcher import EventDispatcher

    data: Any = event.data
    invoice_id = data.get("invoice_id")
    customer_id = data.get("customer_id")
    merchant_id = data.get("merchant_id")

    if not invoice_id or not customer_id:
        return

    try:
        with SessionCelery() as db:
            stmt = select(Customer).where(
                Customer.id == customer_id,
                Customer.merchant_id == merchant_id,
                Customer.deleted_at.is_(None),
            )
            customer = db.execute(stmt).scalar_one_or_none()
            if not customer:
                logger.warning(
                    "handle_overdue_notification: customer %s not found for invoice %s",
                    customer_id,
                    invoice_id,
                )
                return

            customer_email = getattr(customer, "email", None)
            if not customer_email:
                logger.warning(
                    "handle_overdue_notification: no email for customer %s",
                    customer_id,
                )
                return

        await EventDispatcher.dispatch(
            BaseEvent(
                event_type="notification.email_requested",
                data={
                    "to_email": customer_email,
                    "template_key": "invoice_overdue",
                    "template_vars": {
                        "invoice_id": data.get("invoice_literal") or str(invoice_id),
                        "amount": f"{float(data.get('amount', 0)):.2f}",
                        "due_date": data.get("due_date") or "",
                    },
                    "merchant_id": merchant_id,
                },
                correlation_id=event.correlation_id,
            )
        )
        logger.info(
            "handle_overdue_notification: queued overdue email for invoice %s → %s",
            invoice_id,
            customer_email,
        )
    except Exception as exc:
        logger.error(
            "handle_overdue_notification failed for invoice %s: %s", invoice_id, exc
        )


async def _log_invoice_activity(event: BaseEvent) -> None:
    """Write an InvoiceActivity record for any invoice.* event."""
    from src.apps.invoices import crud as invoice_crud
    from src.core.database import SessionCelery

    data: Any = event.data
    invoice_id = data.get("invoice_id")
    if not invoice_id:
        return

    activity_type = _EVENT_ACTIVITY_MAP.get(event.event_type)
    if not activity_type:
        logger.debug(
            "_log_invoice_activity: no mapping for event_type=%s, skipping",
            event.event_type,
        )
        return

    try:
        with SessionCelery() as db:
            invoice_crud.write_activity(
                db=db,
                invoice_id=invoice_id,
                activity_type=activity_type,
                description=data.get("description"),
                actor_type=data.get("actor_type", "system"),
                actor_id=data.get("actor_id"),
                metadata=data.get("metadata"),
            )
            db.commit()
            logger.info(
                "_log_invoice_activity: wrote activity %s for invoice_id=%s",
                activity_type,
                invoice_id,
            )
    except Exception as exc:
        logger.error(
            "_log_invoice_activity: failed for invoice_id=%s event=%s: %s",
            invoice_id,
            event.event_type,
            exc,
            exc_info=True,
        )
