"""Helpdesk domain events.

All events follow the ``helpdesk.<entity>.<action>`` naming convention.
Subscribers are registered in the same module (end of file) and in
application-level handlers (e.g. notifications, SLA timers, automations).
"""

from __future__ import annotations

import structlog

from simorgh.apps.events.bus import register_event, subscribe

try:
    from simorgh.apps.app_settings.registry import SettingsError
except ImportError:
    SettingsError = Exception  # graceful fallback

_log = structlog.get_logger("simorgh.helpdesk.events")


# ---------------------------------------------------------------------------
# SMS notification helpers
# ---------------------------------------------------------------------------

def _sms_channels(tenant_id: int, base: tuple) -> tuple:
    """Append 'sms' to base channels when helpdesk.notify_sms is enabled."""
    try:
        from simorgh.apps.app_settings.services import resolve
        if resolve("helpdesk.notify_sms", tenant_id=tenant_id):
            return (*base, "sms")
    except SettingsError:
        pass
    return base


def _with_mobile(context: dict, user_id: int | None) -> dict:
    """Inject recipient mobile into notification context for SMS delivery."""
    if not user_id:
        return context
    try:
        from django.contrib.auth import get_user_model
        User = get_user_model()
        row = User.objects.filter(pk=user_id).values("mobile").first()
        if row and row["mobile"]:
            return {**context, "mobile": row["mobile"]}
    except (User.DoesNotExist, AttributeError):
        pass
    return context

# ---------------------------------------------------------------------------
# Event declarations
# ---------------------------------------------------------------------------

TICKET_CREATED = register_event(
    "helpdesk.ticket.created",
    description="A new ticket was opened.",
    payload_keys=("ticket_id", "tenant_id", "org_node_id", "queue_id", "created_by"),
)

TICKET_UPDATED = register_event(
    "helpdesk.ticket.updated",
    description="A ticket's non-status fields were changed.",
    payload_keys=("ticket_id", "tenant_id", "changed_fields", "actor_id"),
)

TICKET_STATUS_CHANGED = register_event(
    "helpdesk.ticket.status_changed",
    description="A ticket moved to a new status.",
    payload_keys=("ticket_id", "tenant_id", "from_status", "to_status", "actor_id"),
)

TICKET_ASSIGNED = register_event(
    "helpdesk.ticket.assigned",
    description="A ticket was assigned to an agent.",
    payload_keys=("ticket_id", "tenant_id", "assigned_to_id", "assigned_by_id"),
)

TICKET_ESCALATED = register_event(
    "helpdesk.ticket.escalated",
    description="A ticket was escalated.",
    payload_keys=("ticket_id", "tenant_id", "escalated_to_id", "reason", "actor_id"),
)

TICKET_SOLVED = register_event(
    "helpdesk.ticket.solved",
    description="A ticket was marked as solved.",
    payload_keys=("ticket_id", "tenant_id", "resolved_at", "actor_id"),
)

TICKET_CLOSED = register_event(
    "helpdesk.ticket.closed",
    description="A ticket was closed.",
    payload_keys=("ticket_id", "tenant_id", "closed_at", "actor_id"),
)

TICKET_REOPENED = register_event(
    "helpdesk.ticket.reopened",
    description="A closed/solved ticket was reopened.",
    payload_keys=("ticket_id", "tenant_id", "actor_id"),
)

REPLY_ADDED = register_event(
    "helpdesk.reply.added",
    description="A public reply was added to a ticket.",
    payload_keys=("ticket_id", "reply_id", "tenant_id", "author_id", "is_internal"),
)

SLA_BREACHED = register_event(
    "helpdesk.sla.breached",
    description="An SLA timer passed its deadline without resolution.",
    payload_keys=("ticket_id", "tenant_id", "breach_type", "due_at"),
)

AUTOMATION_FIRED = register_event(
    "helpdesk.automation.fired",
    description="An automation rule executed its action set.",
    payload_keys=("automation_id", "ticket_id", "tenant_id", "actions_fired"),
)

CSAT_SURVEY_DUE = register_event(
    "helpdesk.csat.survey_due",
    description="A CSAT survey is due for a recently-solved ticket.",
    payload_keys=("ticket_id", "tenant_id", "queue_id", "requester_id", "resolved_at"),
)

CSAT_RECEIVED = register_event(
    "helpdesk.csat.received",
    description="A satisfaction rating was submitted for a ticket.",
    payload_keys=("ticket_id", "tenant_id", "rating", "comment", "submitted_by"),
)


# ---------------------------------------------------------------------------
# Internal subscribers — cross-cutting concerns
# ---------------------------------------------------------------------------

@subscribe("helpdesk.ticket.created")
def _on_ticket_created_start_sla(payload: dict) -> None:
    """Start SLA timers for newly created tickets."""
    from simorgh.apps.helpdesk.services import TicketService
    TicketService.start_sla_timers(ticket_id=payload["ticket_id"])


@subscribe("helpdesk.ticket.created")
def _on_ticket_created_run_automations(payload: dict) -> None:
    """Evaluate TICKET_CREATED process definitions via the workflow engine."""
    from simorgh.apps.helpdesk.models import Ticket
    from simorgh.apps.workflow.process_engine import ProcessEngine
    try:
        ticket = Ticket.objects.select_related("queue", "category", "sla_policy").get(
            pk=payload["ticket_id"]
        )
    except Ticket.DoesNotExist:
        return
    ProcessEngine.run(
        "helpdesk.ticket.created",
        subject=ticket,
        tenant_id=payload["tenant_id"],
        payload=payload,
    )


@subscribe("helpdesk.ticket.status_changed")
def _on_status_changed_sla(payload: dict) -> None:
    """Pause/resume SLA timers on status transitions."""
    from simorgh.apps.helpdesk.services import TicketService
    TicketService.handle_sla_status_change(
        ticket_id=payload["ticket_id"],
        new_status=payload["to_status"],
    )


@subscribe("helpdesk.ticket.status_changed")
def _on_status_changed_automations(payload: dict) -> None:
    from simorgh.apps.helpdesk.models import Ticket
    from simorgh.apps.workflow.process_engine import ProcessEngine
    try:
        ticket = Ticket.objects.select_related("queue", "category", "sla_policy").get(
            pk=payload["ticket_id"]
        )
    except Ticket.DoesNotExist:
        return
    ProcessEngine.run(
        "helpdesk.ticket.status_changed",
        subject=ticket,
        tenant_id=payload["tenant_id"],
        payload=payload,
    )


@subscribe("helpdesk.reply.added")
def _on_reply_added_sla(payload: dict) -> None:
    """Mark first-response SLA as resolved on first public reply."""
    if not payload.get("is_internal"):
        from simorgh.apps.helpdesk.services import TicketService
        TicketService.mark_first_response(ticket_id=payload["ticket_id"])


@subscribe("helpdesk.reply.added")
def _on_reply_added_automations(payload: dict) -> None:
    from simorgh.apps.helpdesk.models import Ticket
    from simorgh.apps.workflow.process_engine import ProcessEngine
    try:
        ticket = Ticket.objects.select_related("queue", "category", "sla_policy").get(
            pk=payload["ticket_id"]
        )
    except Ticket.DoesNotExist:
        return
    ProcessEngine.run(
        "helpdesk.reply.added",
        subject=ticket,
        tenant_id=payload["tenant_id"],
        payload=payload,
    )


# ---------------------------------------------------------------------------
# Notification subscribers
# ---------------------------------------------------------------------------

@subscribe("helpdesk.ticket.created")
def _notify_ticket_created(payload: dict) -> None:
    """Notify queue agents about a newly created ticket."""
    try:
        from simorgh.apps.helpdesk.models import Ticket
        from simorgh.apps.notifications import services as notif_svc

        ticket_id = payload["ticket_id"]
        tenant_id = payload["tenant_id"]

        ticket = Ticket.objects.select_related("queue").get(pk=ticket_id)
        queue = ticket.queue
        # Notify all active members of this queue who are agents.
        # For simplicity, notify the queue's members field if it exists,
        # otherwise fall back to no automatic broadcast (supervisor seeds agents).
        recipients = []
        if hasattr(queue, "members"):
            recipients = list(queue.members.values_list("pk", flat=True))

        if recipients:
            _base_ctx = {
                "ticket_id": str(ticket.public_id),
                "subject": ticket.subject,
                "priority": ticket.priority,
                "queue_name": queue.name,
            }
            _chans = _sms_channels(tenant_id, ("inbox",))
            for rid in recipients:
                notif_svc.dispatch(
                    "helpdesk.ticket.created.agent",
                    recipients=[rid],
                    context=_with_mobile(_base_ctx, rid),
                    channels=_chans,
                    tenant_id=tenant_id,
                    organization_node_id=ticket.organization_node_id,
                )
    except Exception:
        _log.exception("helpdesk.notify_ticket_created.error", payload=payload)


@subscribe("helpdesk.ticket.assigned")
def _notify_ticket_assigned(payload: dict) -> None:
    """Notify the newly assigned agent."""
    try:
        assigned_to_id = payload.get("assigned_to_id")
        if not assigned_to_id:
            return

        from simorgh.apps.helpdesk.models import Ticket
        from simorgh.apps.notifications import services as notif_svc

        ticket = Ticket.objects.get(pk=payload["ticket_id"])

        _tenant_id = payload["tenant_id"]
        _ctx = {
            "ticket_id": str(ticket.public_id),
            "subject": ticket.subject,
            "priority": ticket.priority,
        }
        notif_svc.dispatch(
            "helpdesk.ticket.assigned",
            recipients=[assigned_to_id],
            context=_with_mobile(_ctx, assigned_to_id),
            channels=_sms_channels(_tenant_id, ("inbox",)),
            tenant_id=_tenant_id,
            organization_node_id=ticket.organization_node_id,
        )
    except Exception:
        _log.exception("helpdesk.notify_ticket_assigned.error", payload=payload)


@subscribe("helpdesk.reply.added")
def _notify_reply_added(payload: dict) -> None:
    """Notify the assigned agent and the requester when a reply is posted."""
    try:
        if payload.get("is_internal"):
            # Internal notes are not forwarded to the requester.
            return

        from simorgh.apps.helpdesk.models import Ticket
        from simorgh.apps.notifications import services as notif_svc

        ticket = Ticket.objects.select_related("assigned_to").get(pk=payload["ticket_id"])
        tenant_id = payload["tenant_id"]
        author_id = payload.get("author_id")

        _chans = _sms_channels(tenant_id, ("inbox",))
        _base = {"ticket_id": str(ticket.public_id), "subject": ticket.subject}

        # Notify assigned agent (if they didn't write it themselves).
        if ticket.assigned_to_id and ticket.assigned_to_id != author_id:
            notif_svc.dispatch(
                "helpdesk.reply.added.agent",
                recipients=[ticket.assigned_to_id],
                context=_with_mobile(_base, ticket.assigned_to_id),
                channels=_chans,
                tenant_id=tenant_id,
                organization_node_id=ticket.organization_node_id,
            )

        # Notify the requester (registered user only).
        if ticket.requester_id and ticket.requester_id != author_id:
            notif_svc.dispatch(
                "helpdesk.reply.added.requester",
                recipients=[ticket.requester_id],
                context=_with_mobile(_base, ticket.requester_id),
                channels=_sms_channels(tenant_id, ("inbox", "email")),
                tenant_id=tenant_id,
                organization_node_id=ticket.organization_node_id,
            )
    except Exception:
        _log.exception("helpdesk.notify_reply_added.error", payload=payload)


@subscribe("helpdesk.ticket.solved")
def _notify_ticket_solved(payload: dict) -> None:
    """Notify the requester that their ticket was solved."""
    try:
        from simorgh.apps.helpdesk.models import Ticket
        from simorgh.apps.notifications import services as notif_svc

        ticket = Ticket.objects.get(pk=payload["ticket_id"])
        if not ticket.requester_id:
            return

        _tenant_id = payload["tenant_id"]
        _ctx = {"ticket_id": str(ticket.public_id), "subject": ticket.subject}
        notif_svc.dispatch(
            "helpdesk.ticket.solved",
            recipients=[ticket.requester_id],
            context=_with_mobile(_ctx, ticket.requester_id),
            channels=_sms_channels(_tenant_id, ("inbox",)),
            tenant_id=_tenant_id,
            organization_node_id=ticket.organization_node_id,
        )
    except Exception:
        _log.exception("helpdesk.notify_ticket_solved.error", payload=payload)


@subscribe("helpdesk.ticket.escalated")
def _notify_ticket_escalated(payload: dict) -> None:
    """Notify the escalation target."""
    try:
        escalated_to_id = payload.get("escalated_to_id")
        if not escalated_to_id:
            return

        from simorgh.apps.helpdesk.models import Ticket
        from simorgh.apps.notifications import services as notif_svc

        ticket = Ticket.objects.get(pk=payload["ticket_id"])

        _tenant_id = payload["tenant_id"]
        _ctx = {
            "ticket_id": str(ticket.public_id),
            "subject": ticket.subject,
            "reason": payload.get("reason", ""),
        }
        notif_svc.dispatch(
            "helpdesk.ticket.escalated",
            recipients=[escalated_to_id],
            context=_with_mobile(_ctx, escalated_to_id),
            channels=_sms_channels(_tenant_id, ("inbox",)),
            tenant_id=_tenant_id,
            organization_node_id=ticket.organization_node_id,
        )
    except Exception:
        _log.exception("helpdesk.notify_ticket_escalated.error", payload=payload)


@subscribe("helpdesk.sla.breached")
def _notify_sla_breached(payload: dict) -> None:
    """Notify the assigned agent and queue supervisor on SLA breach."""
    try:
        from simorgh.apps.helpdesk.models import Ticket
        from simorgh.apps.notifications import services as notif_svc

        ticket = Ticket.objects.select_related("queue").get(pk=payload["ticket_id"])
        tenant_id = payload["tenant_id"]
        breach_type = payload.get("breach_type", "")

        recipients = []
        if ticket.assigned_to_id:
            recipients.append(ticket.assigned_to_id)

        if recipients:
            notif_svc.dispatch(
                "helpdesk.sla.breached",
                recipients=recipients,
                context={
                    "ticket_id": str(ticket.public_id),
                    "subject": ticket.subject,
                    "breach_type": breach_type,
                    "due_at": payload.get("due_at", ""),
                },
                tenant_id=tenant_id,
                organization_node_id=ticket.organization_node_id,
            )
    except Exception:
        _log.exception("helpdesk.notify_sla_breached.error", payload=payload)


# ---------------------------------------------------------------------------
# Realtime subscribers — push live updates to connected clients
# ---------------------------------------------------------------------------

@subscribe("helpdesk.ticket.created")
def _realtime_ticket_created(payload: dict) -> None:
    """Broadcast ticket creation to the org node room."""
    try:
        from simorgh.apps.helpdesk.models import Ticket
        from simorgh.core.realtime import publish_to_org_node

        ticket = Ticket.objects.get(pk=payload["ticket_id"])
        publish_to_org_node(
            ticket.organization_node_id,
            "helpdesk.ticket.created",
            {
                "ticket_id": str(ticket.public_id),
                "subject": ticket.subject,
                "status": ticket.status,
                "priority": ticket.priority,
            },
        )
    except Exception:
        _log.exception("helpdesk.realtime_ticket_created.error", payload=payload)


@subscribe("helpdesk.ticket.status_changed")
def _realtime_status_changed(payload: dict) -> None:
    """Broadcast status change to assigned agent and requester."""
    try:
        from simorgh.apps.helpdesk.models import Ticket
        from simorgh.core.realtime import publish_to_user

        ticket = Ticket.objects.get(pk=payload["ticket_id"])
        event_payload = {
            "ticket_id": str(ticket.public_id),
            "from_status": payload.get("from_status"),
            "to_status": payload.get("to_status"),
        }
        if ticket.assigned_to_id:
            publish_to_user(ticket.assigned_to_id, "helpdesk.ticket.status_changed", event_payload)
        if ticket.requester_id:
            publish_to_user(ticket.requester_id, "helpdesk.ticket.status_changed", event_payload)
    except Exception:
        _log.exception("helpdesk.realtime_status_changed.error", payload=payload)


@subscribe("helpdesk.reply.added")
def _realtime_reply_added(payload: dict) -> None:
    """Push new reply to assigned agent and requester in real-time."""
    try:
        from simorgh.apps.helpdesk.models import Ticket
        from simorgh.core.realtime import publish_to_user

        is_internal = payload.get("is_internal", False)
        ticket = Ticket.objects.get(pk=payload["ticket_id"])
        event_payload = {
            "ticket_id": str(ticket.public_id),
            "reply_id": payload.get("reply_id"),
            "is_internal": is_internal,
        }
        if ticket.assigned_to_id:
            publish_to_user(ticket.assigned_to_id, "helpdesk.reply.added", event_payload)
        # Only forward non-internal replies to the requester.
        if not is_internal and ticket.requester_id:
            publish_to_user(ticket.requester_id, "helpdesk.reply.added", event_payload)
    except Exception:
        _log.exception("helpdesk.realtime_reply_added.error", payload=payload)
