"""SLA Engine services — business logic.

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

from __future__ import annotations

from datetime import datetime, timedelta

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

from simorgh.apps.sla_engine.models import (
    SLABreach,
    SLABreachStatus,
    SLAPolicy,
    SLATimer,
)

# ── SLA Timer Lifecycle ─────────────────────────────────────────────────────

def start_timer(
    *,
    policy: SLAPolicy,
    tenant_id: int,
    content_type_id: int,
    object_id: int,
    started_at: datetime | None = None,
) -> SLATimer:
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    now = started_at or timezone.now()
    target_at = now + timedelta(hours=float(policy.target_hours))
    warning_at = None
    if policy.warning_hours is not None and float(policy.warning_hours) > 0:
        warning_at = now + timedelta(hours=float(policy.warning_hours))

    with transaction.atomic():
        timer = SLATimer.objects.create(
            policy=policy,
            tenant_id=tenant_id,
            content_type_id=content_type_id,
            object_id=object_id,
            started_at=now,
            target_at=target_at,
            warning_at=warning_at,
            warning_sent=False,
            is_breached=False,
            is_recovered=False,
            is_stopped=False,
        )

    record_service_event("sla_engine.timer.started", resource=timer, after={
        "policy_id": policy.pk,
        "tenant_id": tenant_id,
    })
    dispatch("sla.timer.started", {
        "timer_id":    str(timer.public_id),
        "policy_id":   str(policy.public_id),
        "tenant_id":   tenant_id,
        "entity_type": str(content_type_id),
        "entity_id":   object_id,
    })
    return timer


def pause_timer(timer: SLATimer) -> SLATimer:
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    if timer.is_stopped:
        raise ValueError("Cannot pause a stopped SLA timer.")
    if timer.paused_at is not None:
        raise ValueError("SLA timer is already paused.")

    now = timezone.now()
    timer.paused_at = now
    timer.save(update_fields=["paused_at", "updated_at"])

    record_service_event("sla_engine.timer.paused", resource=timer)
    dispatch("sla.timer.paused", {
        "timer_id":  str(timer.public_id),
        "policy_id": str(timer.policy.public_id),
        "tenant_id": timer.tenant_id,
    })
    return timer


def resume_timer(timer: SLATimer) -> SLATimer:
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    if timer.is_stopped:
        raise ValueError("Cannot resume a stopped SLA timer.")
    if timer.paused_at is None:
        raise ValueError("SLA timer is not paused.")

    now = timezone.now()
    pause_duration = (now - timer.paused_at).total_seconds()
    timer.accumulated_seconds += int(pause_duration)
    timer.paused_at = None
    timer.save(update_fields=["accumulated_seconds", "paused_at", "updated_at"])

    record_service_event("sla_engine.timer.resumed", resource=timer)
    dispatch("sla.timer.resumed", {
        "timer_id":  str(timer.public_id),
        "policy_id": str(timer.policy.public_id),
        "tenant_id": timer.tenant_id,
    })
    return timer


def stop_timer(timer: SLATimer, *, stopped_at: datetime | None = None) -> SLATimer:
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    if timer.is_stopped:
        return timer

    now = stopped_at or timezone.now()
    if timer.paused_at is not None:
        resume_timer(timer)

    timer.is_stopped = True
    timer.stopped_at = now
    timer.save(update_fields=["is_stopped", "stopped_at", "updated_at"])

    record_service_event("sla_engine.timer.stopped", resource=timer)
    dispatch("sla.timer.stopped", {
        "timer_id":  str(timer.public_id),
        "policy_id": str(timer.policy.public_id),
        "tenant_id": timer.tenant_id,
    })
    return timer


# ── SLA Breach Detection ─────────────────────────────────────────────────────

def check_breaches(timer: SLATimer) -> list[str]:
    """Check an SLA timer for warnings and breaches.

    Returns a list of triggered statuses: {'warning', 'breached'}.
    """
    if timer.is_stopped or timer.is_breached:
        return []

    now = timezone.now()
    results: list[str] = []

    if timer.warning_at and not timer.warning_sent and now >= timer.warning_at:
        _record_breach(timer, SLABreachStatus.WARNING, now)
        timer.warning_sent = True
        timer.save(update_fields=["warning_sent", "updated_at"])
        results.append("warning")

    if timer.target_at and now >= timer.target_at:
        _record_breach(timer, SLABreachStatus.BREACHED, now)
        timer.is_breached = True
        timer.breached_at = now
        timer.save(update_fields=["is_breached", "breached_at", "updated_at"])
        results.append("breached")

    return results


def recover_timer(timer: SLATimer) -> SLATimer:
    """Mark a breached SLA timer as recovered."""
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    if not timer.is_breached:
        raise ValueError("SLA timer is not breached.")
    if timer.is_recovered:
        raise ValueError("SLA timer is already recovered.")

    now = timezone.now()
    timer.is_recovered = True
    timer.recovered_at = now
    timer.save(update_fields=["is_recovered", "recovered_at", "updated_at"])

    _record_breach(timer, SLABreachStatus.RECOVERED, now)

    record_service_event("sla_engine.breach.recovered", resource=timer)
    dispatch("sla.recovered", {
        "timer_id":   str(timer.public_id),
        "policy_id":  str(timer.policy.public_id),
        "tenant_id":  timer.tenant_id,
        "breach_type": "resolution",
    })
    return timer


def _record_breach(timer: SLATimer, status: str, occurred_at: datetime) -> SLABreach:
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    breach = SLABreach.objects.create(
        timer=timer,
        policy=timer.policy,
        tenant_id=timer.tenant_id,
        content_type_id=timer.content_type_id,
        object_id=timer.object_id,
        status=status,
        occurred_at=occurred_at,
    )

    record_service_event("sla_engine.breach.recorded", resource=breach, after={"status": status})

    if status == SLABreachStatus.WARNING:
        dispatch("sla.warning", {
            "timer_id":    str(timer.public_id),
            "policy_id":   str(timer.policy.public_id),
            "tenant_id":   timer.tenant_id,
            "entity_type": str(timer.content_type_id),
            "entity_id":   timer.object_id,
        })
    elif status == SLABreachStatus.BREACHED:
        dispatch("sla.breached", {
            "timer_id":    str(timer.public_id),
            "policy_id":   str(timer.policy.public_id),
            "tenant_id":   timer.tenant_id,
            "entity_type": str(timer.content_type_id),
            "entity_id":   timer.object_id,
            "breach_type": timer.policy.sla_type,
        })

    return breach


# ── SLA Policy ───────────────────────────────────────────────────────────────

def create_sla_policy(
    *,
    tenant_id: int,
    organization_node_id: int,
    name: str,
    sla_type: str,
    target_hours: float,
    description: str = "",
    warning_hours: float | None = None,
    business_hours_only: bool = True,
    content_type_id: int | None = None,
    conditions: dict | None = None,
    priority: str | None = None,
    is_active: bool = True,
) -> SLAPolicy:
    return SLAPolicy.objects.create(
        tenant_id=tenant_id,
        organization_node_id=organization_node_id,
        name=name.strip(),
        description=description,
        sla_type=sla_type,
        target_hours=target_hours,
        warning_hours=warning_hours,
        business_hours_only=business_hours_only,
        content_type_id=content_type_id,
        conditions=conditions or {},
        priority=priority,
        is_active=is_active,
    )


def update_sla_policy(
    policy: SLAPolicy,
    *,
    name: str | None = None,
    description: str | None = None,
    target_hours: float | None = None,
    warning_hours: float | None = ...,
    business_hours_only: bool | None = None,
    conditions: dict | None = None,
    priority: str | None = None,
    is_active: bool | None = None,
) -> SLAPolicy:
    _SENTINEL = ...  # type: ignore[assignment]

    if name is not None:
        policy.name = name.strip()
    if description is not None:
        policy.description = description
    if target_hours is not None:
        policy.target_hours = target_hours
    if warning_hours is not _SENTINEL:
        policy.warning_hours = warning_hours  # type: ignore[assignment]
    if business_hours_only is not None:
        policy.business_hours_only = business_hours_only
    if conditions is not None:
        policy.conditions = conditions
    if priority is not None:
        policy.priority = priority
    if is_active is not None:
        policy.is_active = is_active

    policy.save()
    return policy


def delete_sla_policy(policy: SLAPolicy) -> None:
    policy.delete()


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

def check_all_active_breaches() -> dict[str, int]:
    """Check all active SLA timers for warnings and breaches.

    Returns a dict of status counts, e.g. {'warning': 5, 'breached': 3}.
    """
    now = timezone.now()
    counts: dict[str, int] = {"warning": 0, "breached": 0}

    warning_timers = SLATimer.objects.filter(
        is_stopped=False,
        is_breached=False,
        warning_sent=False,
        warning_at__lte=now,
        warning_at__isnull=False,
    ).select_related("policy")

    for timer in warning_timers:
        try:
            results = check_breaches(timer)
            if "warning" in results:
                counts["warning"] += 1
            if "breached" in results:
                counts["breached"] += 1
        except Exception:
            continue

    breached_timers = SLATimer.objects.filter(
        is_stopped=False,
        is_breached=False,
        target_at__lte=now,
    ).select_related("policy")

    for timer in breached_timers:
        try:
            results = check_breaches(timer)
            if "breached" in results:
                counts["breached"] += 1
        except Exception:
            continue

    return counts


# ── Policy matching ──────────────────────────────────────────────────────────

def find_matching_policies(
    *,
    tenant_id: int,
    sla_type: str,
    content_type_id: int | None = None,
    priority: str | None = None,
) -> list[SLAPolicy]:
    """Find active SLA policies matching the given criteria."""
    qs = SLAPolicy.objects.filter(
        tenant_id=tenant_id,
        sla_type=sla_type,
        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__isnull=True))

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



