"""Escalation Engine services — business logic.

All write operations go through these functions; views must NOT call ORM
save/delete directly on Escalation Engine models.
"""

from __future__ import annotations

from datetime import datetime

from django.db import models, transaction
from django.utils import timezone

from simorgh.apps.escalation_engine.models import (
    EscalationInstance,
    EscalationLog,
    EscalationRule,
    EscalationStatus,
    EscalationTriggerType,
)

# ── Escalation Rule ─────────────────────────────────────────────────────────


def create_escalation_rule(
    *,
    tenant_id: int,
    organization_node_id: int,
    name: str,
    trigger_type: str,
    escalation_type: str,
    description: str = "",
    trigger_config: dict | None = None,
    escalation_config: dict | None = None,
    max_levels: int = 3,
    content_type_id: int | None = None,
    conditions: dict | None = None,
    priority: str | None = None,
    is_active: bool = True,
) -> EscalationRule:
    return EscalationRule.objects.create(
        tenant_id=tenant_id,
        organization_node_id=organization_node_id,
        name=name.strip(),
        description=description,
        trigger_type=trigger_type,
        escalation_type=escalation_type,
        trigger_config=trigger_config or {},
        escalation_config=escalation_config or {},
        max_levels=max_levels,
        content_type_id=content_type_id,
        conditions=conditions or {},
        priority=priority,
        is_active=is_active,
    )


def update_escalation_rule(
    rule: EscalationRule,
    *,
    name: str | None = None,
    description: str | None = None,
    trigger_type: str | None = None,
    escalation_type: str | None = None,
    trigger_config: dict | None = None,
    escalation_config: dict | None = None,
    max_levels: int | None = None,
    conditions: dict | None = None,
    priority: str | None = None,
    is_active: bool | None = None,
) -> EscalationRule:
    if name is not None:
        rule.name = name.strip()
    if description is not None:
        rule.description = description
    if trigger_type is not None:
        rule.trigger_type = trigger_type
    if escalation_type is not None:
        rule.escalation_type = escalation_type
    if trigger_config is not None:
        rule.trigger_config = trigger_config
    if escalation_config is not None:
        rule.escalation_config = escalation_config
    if max_levels is not None:
        rule.max_levels = max_levels
    if conditions is not None:
        rule.conditions = conditions
    if priority is not None:
        rule.priority = priority
    if is_active is not None:
        rule.is_active = is_active

    rule.save()
    return rule


def delete_escalation_rule(rule: EscalationRule) -> None:
    rule.delete()


# ── Escalation Instance ─────────────────────────────────────────────────────


def trigger_escalation(
    *,
    rule: EscalationRule,
    tenant_id: int,
    source_content_type_id: int,
    source_object_id: int,
    triggered_at: datetime | None = None,
    reason: str = "",
    escalated_from_user_id: int | None = None,
) -> EscalationInstance:
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    now = triggered_at or timezone.now()

    with transaction.atomic():
        instance = EscalationInstance.objects.create(
            rule=rule,
            tenant_id=tenant_id,
            source_content_type_id=source_content_type_id,
            source_object_id=source_object_id,
            status=EscalationStatus.PENDING,
            escalation_level=1,
            escalated_from_user_id=escalated_from_user_id,
            triggered_at=now,
            reason=reason,
        )

        _write_log(
            instance=instance,
            rule=rule,
            tenant_id=tenant_id,
            action="triggered",
            actor_id=escalated_from_user_id,
            note=reason or "Escalation triggered.",
        )

    record_service_event(
        "escalation_engine.instance.triggered",
        resource=instance,
        after={"rule_id": rule.pk, "tenant_id": tenant_id},
    )
    dispatch(
        "escalation.triggered",
        {
            "escalation_instance_id": str(instance.public_id),
            "rule_id": str(rule.public_id),
            "tenant_id": tenant_id,
            "entity_type": str(source_content_type_id),
            "entity_id": source_object_id,
            "escalation_level": 1,
        },
    )
    return instance


def escalate_instance(
    instance: EscalationInstance,
    *,
    escalated_to_user_id: int | None = None,
    escalated_to_role_id: int | None = None,
    escalated_at: datetime | None = None,
    note: str = "",
    actor_id: int | None = None,
) -> EscalationInstance:
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    if instance.status in (EscalationStatus.RESOLVED, EscalationStatus.CANCELLED):
        raise ValueError(f"Cannot escalate an instance with status {instance.status}.")

    if instance.escalation_level >= instance.rule.max_levels:
        raise ValueError(f"Maximum escalation level ({instance.rule.max_levels}) reached.")

    now = escalated_at or timezone.now()
    next_level = instance.escalation_level + 1

    with transaction.atomic():
        instance.status = EscalationStatus.IN_PROGRESS
        instance.escalation_level = next_level
        instance.escalated_to_user_id = escalated_to_user_id
        instance.escalated_to_role_id = escalated_to_role_id
        instance.escalated_at = now
        instance.save(
            update_fields=[
                "status",
                "escalation_level",
                "escalated_to_user_id",
                "escalated_to_role_id",
                "escalated_at",
                "updated_at",
            ]
        )

        _write_log(
            instance=instance,
            rule=instance.rule,
            tenant_id=instance.tenant_id,
            action="escalated",
            actor_id=actor_id,
            note=note or f"Escalated to level {next_level}.",
            metadata={
                "escalation_level": next_level,
                "escalated_to_user_id": escalated_to_user_id,
                "escalated_to_role_id": escalated_to_role_id,
            },
        )

    record_service_event(
        "escalation_engine.instance.escalated",
        resource=instance,
        after={"escalation_level": next_level},
    )
    dispatch(
        "escalation.escalated",
        {
            "escalation_instance_id": str(instance.public_id),
            "rule_id": str(instance.rule.public_id),
            "tenant_id": instance.tenant_id,
            "escalated_to_user_id": escalated_to_user_id,
            "escalation_level": next_level,
        },
    )
    return instance


def resolve_instance(
    instance: EscalationInstance,
    *,
    resolution_note: str = "",
    actor_id: int | None = None,
    resolved_at: datetime | None = None,
) -> EscalationInstance:
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    if instance.status == EscalationStatus.RESOLVED:
        raise ValueError("Escalation instance is already resolved.")

    now = resolved_at or timezone.now()

    with transaction.atomic():
        instance.status = EscalationStatus.RESOLVED
        instance.resolved_at = now
        instance.resolution_note = resolution_note
        instance.save(
            update_fields=[
                "status",
                "resolved_at",
                "resolution_note",
                "updated_at",
            ]
        )

        _write_log(
            instance=instance,
            rule=instance.rule,
            tenant_id=instance.tenant_id,
            action="resolved",
            actor_id=actor_id,
            note=resolution_note or "Escalation resolved.",
        )

    record_service_event(
        "escalation_engine.instance.resolved",
        resource=instance,
    )
    dispatch(
        "escalation.resolved",
        {
            "escalation_instance_id": str(instance.public_id),
            "rule_id": str(instance.rule.public_id),
            "tenant_id": instance.tenant_id,
        },
    )
    return instance


def cancel_instance(
    instance: EscalationInstance,
    *,
    note: str = "",
    actor_id: int | None = None,
) -> EscalationInstance:
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    if instance.status == EscalationStatus.CANCELLED:
        raise ValueError("Escalation instance is already cancelled.")

    with transaction.atomic():
        instance.status = EscalationStatus.CANCELLED
        instance.save(update_fields=["status", "updated_at"])

        _write_log(
            instance=instance,
            rule=instance.rule,
            tenant_id=instance.tenant_id,
            action="cancelled",
            actor_id=actor_id,
            note=note or "Escalation cancelled.",
        )

    record_service_event(
        "escalation_engine.instance.cancelled",
        resource=instance,
    )
    dispatch(
        "escalation.cancelled",
        {
            "escalation_instance_id": str(instance.public_id),
            "rule_id": str(instance.rule.public_id),
            "tenant_id": instance.tenant_id,
        },
    )
    return instance


# ── Internal helpers ─────────────────────────────────────────────────────────


def _write_log(
    *,
    instance: EscalationInstance,
    rule: EscalationRule,
    tenant_id: int,
    action: str,
    actor_id: int | None = None,
    note: str = "",
    metadata: dict | None = None,
) -> EscalationLog:
    return EscalationLog.objects.create(
        instance=instance,
        rule=rule,
        tenant_id=tenant_id,
        action=action,
        actor_id=actor_id,
        note=note,
        metadata=metadata or {},
    )


# ── Bulk checking ────────────────────────────────────────────────────────────


def check_all_pending_escalations() -> dict[str, int]:
    """Check all active escalation rules for time-based triggers.

    Finds escalation rules with time_based triggers where the threshold has
    been exceeded. Intended to be called from a Celery periodic task.

    Returns a dict with counts, e.g. {'triggered': 5, 'errors': 1}.
    """
    from datetime import timedelta

    counts: dict[str, int] = {"triggered": 0, "errors": 0}
    now = timezone.now()

    time_rules = EscalationRule.objects.filter(
        is_active=True,
        trigger_type=EscalationTriggerType.TIME_BASED,
    ).select_related("content_type")

    for rule in time_rules:
        try:
            threshold_hours = rule.trigger_config.get("time_threshold_hours")
            if not threshold_hours or float(threshold_hours) <= 0:
                continue

            # Find entities for this rule's content type that are overdue
            if not rule.content_type:
                continue

            content_type = rule.content_type
            model_class = content_type.model_class()
            if not model_class:
                continue

            threshold_dt = now - timedelta(hours=float(threshold_hours))

            # Query entities where no existing active escalation
            existing_ids = set(
                EscalationInstance.objects.filter(
                    source_content_type=content_type,
                    status__in=(EscalationStatus.PENDING, EscalationStatus.IN_PROGRESS),
                ).values_list("source_object_id", flat=True)
            )

            # Look for entities that have a `created_at` field and are past threshold
            candidates = (
                model_class.objects.filter(
                    tenant_id=rule.tenant_id,
                    created_at__lte=threshold_dt,
                )
                .exclude(pk__in=existing_ids)
                .values_list("pk", flat=True)[:50]  # limit per batch
            )

            for entity_id in candidates:
                try:
                    trigger_escalation(
                        rule=rule,
                        tenant_id=rule.tenant_id,
                        source_content_type_id=content_type.pk,
                        source_object_id=entity_id,
                        reason=f"Auto-triggered: {rule.name} ({threshold_hours}h threshold).",
                    )
                    counts["triggered"] += 1
                except Exception:
                    counts["errors"] += 1
        except Exception:
            counts["errors"] += 1

    return counts


# ── Rule matching ────────────────────────────────────────────────────────────


def find_matching_rules(
    *,
    tenant_id: int,
    content_type_id: int | None = None,
    priority: str | None = None,
) -> list[EscalationRule]:
    """Find active escalation rules matching the given criteria."""
    qs = EscalationRule.objects.filter(
        tenant_id=tenant_id,
        is_active=True,
    )
    if content_type_id is not None:
        qs = qs.filter(
            models.Q(content_type_id=content_type_id) | models.Q(content_type__isnull=True)
        )
    else:
        qs = qs.filter(content_type__isnull=True)

    if priority is not None:
        qs = qs.filter(models.Q(priority=priority) | models.Q(priority=""))

    return list(qs.order_by("priority", "name"))
