"""Explicit release, verified handover and terminal closure; never ownership transfer."""
from contextlib import contextmanager

from django.core.exceptions import ValidationError
from django.utils import timezone

from apps.access.authorization import require_permission
from apps.customers.models import Customer, normalize_mobile
from apps.devices.locking import persisted_pk, locked_catalog, require_catalog_active
from apps.devices.models import Device
from apps.organization.models import ServiceCenter
from .engineer_services import _actors, _eligibility_dependencies, _close
from .locking import share
from .models import (ServiceCase, ServiceEngineerAssignment, ServiceDiagnosticAssessment, ServiceRepairExecution, ServiceQualityControl,
    ServiceCaseAccessory, ServiceCaseDeliveryRelease, ServiceCaseHandover, ServiceCaseHandoverAccessory, ServiceCaseClosure)
from .services import _UNSET, _write, _locked_case
from .handover_queries import device_identity_fingerprint, HANDOVER_PERMISSION, CLOSURE_PERMISSION


def _expected(obj, value):
    return obj.updated_at if value is _UNSET else value


def _revision(obj, value):
    if obj.updated_at != value:
        raise ValidationError("Delivery details changed; reload and review them.")


@contextmanager
def _authorized_case(service_case, actor, permission):
    case_id = persisted_pk(service_case, ServiceCase)
    with _write():
        actor = _actors(actor)
        _eligibility_dependencies(case_id, actor)
        snapshot = ServiceCase.objects.get(pk=case_id)
        center = ServiceCenter.objects.select_related("company", "region").get(pk=snapshot.service_center_id)
        require_permission(user=actor, permission=permission, target=center)
        if not (center.is_active and center.company.is_active and center.region.is_active and
                center.company_id == snapshot.company_id == center.region.company_id):
            raise ValidationError("An active consistent service-center hierarchy is required.")
        yield snapshot, actor


@contextmanager
def _delivery(service_case, actor, expected_device, expected_device_identity):
    expected_id = persisted_pk(expected_device, Device)
    with _authorized_case(service_case, actor, HANDOVER_PERMISSION) as (snapshot, actor):
        customer = share(Customer, snapshot.customer_id)
        if not customer.is_active or customer.company_id != snapshot.company_id:
            raise ValidationError("An active Customer in this Company is required for delivery.")
        device = Device.objects.get(pk=snapshot.device_id)
        with locked_catalog(device.product_model_id, device.product_variant_id) as (model, variant):
            require_catalog_active(model, variant)
            device = share(Device, device.pk)
            if not device.is_active or device.pk != expected_id or device_identity_fingerprint(device) != expected_device_identity:
                raise ValidationError("Device identity changed or does not match the physical device confirmation.")
            case = _locked_case(service_case)
            if case.device_id != device.pk or case.customer_id != customer.pk:
                raise ValidationError("Case Device/Customer relationship changed.")
            yield case, actor, customer, device


def _technical_history(case, *, delivered=False):
    assignment = ServiceEngineerAssignment.objects.select_for_update().filter(service_case=case, ended_at=None).first()
    if (assignment is None) != delivered:
        raise ValidationError("Engineer assignment does not match the delivery lifecycle.")
    if ServiceDiagnosticAssessment.objects.filter(service_case=case, completed_at=None, abandoned_at=None).exists() or ServiceRepairExecution.objects.filter(
        service_case=case, status="OPEN").exists() or ServiceQualityControl.objects.filter(service_case=case, status="IN_PROGRESS").exists():
        raise ValidationError("Open technical work prevents delivery or closure.")
    repair = ServiceRepairExecution.objects.filter(service_case=case).order_by("-started_at", "-pk").first()
    if repair is None:
        raise ValidationError("Successful repair and QC are required.")
    diagnosis = ServiceDiagnosticAssessment.objects.select_for_update().filter(pk=repair.diagnostic_assessment_id, service_case=case,
        completed_at__isnull=False, abandoned_at=None, engineer_assignment_id=repair.engineer_assignment_id).first()
    repair = ServiceRepairExecution.objects.select_for_update().get(pk=repair.pk)
    qc = ServiceQualityControl.objects.select_for_update().filter(service_case=case).order_by("-started_at", "-pk").first()
    if diagnosis is None or repair.status != "COMPLETED" or repair.outcome != "REPAIRED" or (assignment and repair.engineer_assignment_id != assignment.pk) or (
        qc is None or qc.status != "COMPLETED" or qc.outcome != "PASSED" or qc.repair_execution_id != repair.pk):
        raise ValidationError("Latest repair/QC must be successfully completed and not superseded.")
    return assignment, qc


def mark_service_case_ready_for_delivery(*, service_case, actor, expected_device, expected_device_identity, note="", expected_updated_at=_UNSET):
    expected = _expected(service_case, expected_updated_at)
    with _delivery(service_case, actor, expected_device, expected_device_identity) as (case, actor, customer, device):
        _revision(case, expected)
        if case.status != "QC_PASSED":
            raise ValidationError("Only QC_PASSED cases can be released for delivery.")
        assignment, qc = _technical_history(case)
        release = ServiceCaseDeliveryRelease(service_case=case, quality_control=qc, device=device,
            device_identity=expected_device_identity, readied_by=actor, readied_at=timezone.now(), note=note)
        release._persist()
        case.status = "READY_FOR_DELIVERY"
        case._persist()
        return release


def _reconcile(case, accessories):
    if not isinstance(accessories, (list, tuple)):
        raise ValidationError("Supply an explicit accessory reconciliation list, including [] when none were received.")
    received = {row.pk: row for row in ServiceCaseAccessory.objects.select_for_update().filter(service_case=case).order_by("pk")}
    results, seen = [], set()
    for item in accessories:
        if not isinstance(item, dict) or set(item) - {"intake_accessory", "returned_quantity", "condition_note"}:
            raise ValidationError("Invalid accessory acknowledgement.")
        supplied = item.get("intake_accessory")
        pk = persisted_pk(supplied, ServiceCaseAccessory)
        quantity = item.get("returned_quantity")
        note = item.get("condition_note", "")
        if pk not in received or pk in seen or type(quantity) is not int or not 0 <= quantity <= received[pk].quantity:
            raise ValidationError("Each accessory must belong to this case and have one valid returned quantity.")
        _revision(received[pk], supplied.updated_at)
        if not isinstance(note, str) or (quantity < received[pk].quantity and not note.strip()):
            raise ValidationError("Accessory shortage requires an explanation.")
        seen.add(pk)
        results.append((received[pk], quantity, note))
    if seen != set(received):
        raise ValidationError("Every intake accessory must be explicitly reconciled.")
    return results


def handover_service_case(*, service_case, actor, expected_device, expected_device_identity, recipient_type, verification_method,
    accessories, recipient_name="", recipient_mobile="", recipient_relationship="", recipient_identity_type="",
    recipient_identity_reference="", verification_reference="", note="", expected_updated_at=_UNSET, expected_customer_updated_at=_UNSET):
    if any(not isinstance(value, str) for value in (recipient_type, verification_method, recipient_name, recipient_mobile,
            recipient_relationship, recipient_identity_type, recipient_identity_reference, verification_reference, note)):
        raise ValidationError("Recipient and verification details must be text.")
    expected = _expected(service_case, expected_updated_at)
    customer_expected = _expected(service_case.customer, expected_customer_updated_at)
    with _delivery(service_case, actor, expected_device, expected_device_identity) as (case, actor, customer, device):
        _revision(case, expected)
        _revision(customer, customer_expected)
        if case.status != "READY_FOR_DELIVERY":
            raise ValidationError("Handover requires READY_FOR_DELIVERY.")
        assignment, qc = _technical_history(case)
        release = ServiceCaseDeliveryRelease.objects.select_for_update().filter(service_case=case).first()
        if release is None or release.quality_control_id != qc.pk or release.device_id != device.pk or release.device_identity != expected_device_identity:
            raise ValidationError("Delivery release or confirmed Device identity is no longer valid.")
        from apps.commercial.payment_services import require_delivery_financial_clearance
        require_delivery_financial_clearance(service_case=case)
        recipient_customer = None
        if recipient_type == "CUSTOMER":
            if recipient_name.strip() and recipient_name.strip() != customer.display_name or recipient_mobile.strip() and normalize_mobile(recipient_mobile) != customer.primary_mobile:
                raise ValidationError("Customer details changed; confirm the current Customer record.")
            recipient_customer, recipient_name, recipient_mobile = customer, customer.display_name, customer.primary_mobile
        row = ServiceCaseHandover(service_case=case, delivery_release=release, recipient_type=recipient_type,
            recipient_customer=recipient_customer, recipient_name=recipient_name, recipient_mobile=recipient_mobile,
            recipient_relationship=recipient_relationship, recipient_identity_type=recipient_identity_type,
            recipient_identity_reference=recipient_identity_reference, verification_method=verification_method,
            verification_reference=verification_reference, handed_over_by=actor, delivered_at=timezone.now(), note=note)
        row._persist()
        for accessory, quantity, condition_note in _reconcile(case, accessories):
            ServiceCaseHandoverAccessory(handover=row, intake_accessory=accessory, returned_quantity=quantity, condition_note=condition_note)._persist()
        _close(assignment, actor, row.delivered_at, "Service delivered; technical assignment completed.")
        case.status = "DELIVERED"
        case._persist()
        return row


def close_service_case(*, service_case, actor, note="", expected_updated_at=_UNSET):
    expected = _expected(service_case, expected_updated_at)
    with _authorized_case(service_case, actor, CLOSURE_PERMISSION) as (snapshot, actor):
        case = _locked_case(service_case)
        _revision(case, expected)
        if case.status != "DELIVERED":
            raise ValidationError("Only DELIVERED cases may be closed; CLOSED is terminal.")
        from apps.inventory.usage_services import require_inventory_resolved
        require_inventory_resolved(case)
        assignment, qc = _technical_history(case, delivered=True)
        release = ServiceCaseDeliveryRelease.objects.select_for_update().filter(service_case=case, quality_control=qc).first()
        handover = ServiceCaseHandover.objects.select_for_update().filter(service_case=case, delivery_release=release).first() if release else None
        if handover is None:
            raise ValidationError("Immutable handover evidence is required before closure.")
        handover.full_clean()
        received = set(ServiceCaseAccessory.objects.select_for_update().filter(service_case=case).order_by("pk").values_list("pk", flat=True))
        returned = list(ServiceCaseHandoverAccessory.objects.select_for_update().filter(handover=handover).order_by("pk"))
        if received != {row.intake_accessory_id for row in returned}:
            raise ValidationError("Complete accessory acknowledgements are required before closure.")
        for row in returned:
            row.full_clean()
        closure = ServiceCaseClosure(service_case=case, handover=handover, closed_by=actor, closed_at=timezone.now(), note=note)
        closure._persist()
        case.status = "CLOSED"
        case._persist()
        return closure
