from datetime import datetime, timedelta
from django.core.exceptions import ValidationError
from django.db.models import Case, When, Value, CharField, Q, F, OuterRef, Subquery
from django.utils import timezone

from apps.access.authorization import authorized_queryset
from apps.organization.models import Company, ServiceCenter
from apps.service.models import ServiceCase, ServiceEngineerAssignment
from .models import ServiceSla

STATES = ("ON_TRACK", "DUE_SOON", "OVERDUE", "COMPLETED_ON_TIME", "COMPLETED_LATE", "CANCELLED")


def aware(value=None):
    value = timezone.now() if value is None else value
    if not isinstance(value, datetime) or timezone.is_naive(value):
        raise ValidationError("An aware observation timestamp is required.")
    return value


def centers(actor, permission="sla.view_sla"):
    return authorized_queryset(user=actor, permission=permission, queryset=ServiceCenter.objects.all())


def companies(actor):
    return authorized_queryset(user=actor, permission="sla.manage_slapolicy", queryset=Company.objects.all())


def cases(actor, permission="sla.view_sla"):
    return ServiceCase.objects.filter(service_center__in=centers(actor, permission), company_id=F("service_center__company_id"))


def annotated(rows, at=None):
    at = aware(at)
    done = Q(service_case__delivery_release__readied_at__lte=at)
    engineer = ServiceEngineerAssignment.objects.filter(service_case_id=OuterRef("service_case_id"), ended_at=None)
    return rows.select_related("service_case__customer", "service_case__service_center", "service_case__device__product_model",
                               "service_case__delivery_release", "policy").annotate(
        engineer_name=Subquery(engineer.values("engineer__username")[:1]),
        sla_state=Case(
            When(service_case__cancelled_at__lte=at, then=Value("CANCELLED")),
            When(done & Q(service_case__delivery_release__readied_at__lte=F("due_at")), then=Value("COMPLETED_ON_TIME")),
            When(done, then=Value("COMPLETED_LATE")),
            When(due_at__lt=at, then=Value("OVERDUE")),
            When(warning_at__lte=at, then=Value("DUE_SOON")),
            default=Value("ON_TRACK"), output_field=CharField()))


def records(actor, at=None):
    return annotated(ServiceSla.objects.filter(service_case__in=cases(actor)), at)


def describe(row, at=None):
    at = aware(at)
    if at < row.started_at:
        raise ValidationError("Observation precedes service acceptance.")
    case = row.service_case
    release = getattr(case, "delivery_release", None)
    if case.status in ("READY_FOR_DELIVERY", "DELIVERED", "CLOSED") and release is None:
        raise ValidationError("Authoritative ready-for-delivery evidence is missing.")
    if case.status == "CANCELLED" and case.cancelled_at is None:
        raise ValidationError("Authoritative cancellation evidence is missing.")
    stop = release.readied_at if release and release.readied_at <= at else None
    cancelled = case.cancelled_at if case.cancelled_at and case.cancelled_at <= at else None
    end = cancelled or stop or at
    return dict(record=row, state=row.sla_state, completed_at=stop, cancelled_at=cancelled,
                elapsed=end - row.started_at, remaining=max(row.due_at - end, timedelta()),
                overdue=max(end - row.due_at, timedelta()), observed_at=at)


def observation(*, actor, sla, at=None):
    at = aware(at)
    row = records(actor, at).get(pk=sla.pk)
    return describe(row, at)
