"""Explicit repair planning/performance and immutable attempt outcomes."""
from contextlib import contextmanager

from django.core.exceptions import ValidationError
from django.utils import timezone

from apps.access.locks import share_rows
from apps.devices.locking import persisted_pk, locked_catalog, require_catalog_active
from apps.devices.models import Device
from apps.service_catalog.models import RepairAction
from apps.service_catalog.queries import repair_action_applies_to_category
from .engineer_queries import eligible_engineers_for_service_case
from .engineer_services import _actors, _eligibility_dependencies, _expect
from .locking import share
from .models import ServiceCase, ServiceEngineerAssignment, ServiceDiagnosticAssessment, ServiceRepairExecution, ServiceRepairAction
from .services import _UNSET, _write, _locked_case


def _revision(row, expected):
    if expected is not _UNSET and row.updated_at != expected:
        raise ValidationError("Repair changed; reload and review it.")


def _case_for(execution):
    return ServiceRepairExecution.objects.get(pk=persisted_pk(execution, ServiceRepairExecution)).service_case


@contextmanager
def _technical(service_case, actor, taxonomy_ids=()):
    case_id = persisted_pk(service_case, ServiceCase)
    with _write():
        actor = _actors(actor)
        _eligibility_dependencies(case_id, actor)
        device = Device.objects.get(pk=ServiceCase.objects.values_list("device_id", flat=True).get(pk=case_id))
        with locked_catalog(device.product_model_id, device.product_variant_id) as (model, variant):
            require_catalog_active(model, variant)
            fresh = share(Device, device.pk)
            if not fresh.is_active or (fresh.product_model_id, fresh.product_variant_id) != (device.product_model_id, device.product_variant_id):
                raise ValidationError("Device is inactive or its catalog identity changed.")
            share_rows(RepairAction, taxonomy_ids)
            case = _locked_case(service_case)
            current = ServiceEngineerAssignment.objects.select_for_update().filter(service_case=case, ended_at=None).first()
            if case.status not in ("DIAGNOSED", "REPAIRING") or current is None or current.engineer_id != actor.pk or not eligible_engineers_for_service_case(case).filter(pk=actor.pk).exists():
                raise ValidationError("Only the currently eligible assigned engineer can perform repair.")
            if ServiceDiagnosticAssessment.objects.filter(service_case=case, completed_at=None, abandoned_at=None).exists():
                raise ValidationError("Open diagnosis prevents repair.")
            yield case, current, actor, model.category


def _diagnosis(pk, case, assignment):
    row = ServiceDiagnosticAssessment.objects.select_for_update().filter(pk=pk, service_case=case,
        engineer_assignment=assignment, completed_at__isnull=False, abandoned_at=None).first()
    if row is None:
        raise ValidationError("Completed diagnosis on the responsible assignment is required.")
    return row


def _open_execution(execution, case, assignment, expected=_UNSET):
    snapshot = ServiceRepairExecution.objects.get(pk=persisted_pk(execution, ServiceRepairExecution))
    _diagnosis(snapshot.diagnostic_assessment_id, case, assignment)
    row = ServiceRepairExecution.objects.select_for_update().get(pk=snapshot.pk)
    if case.status != "REPAIRING" or row.status != "OPEN" or row.service_case_id != case.pk or row.engineer_assignment_id != assignment.pk:
        raise ValidationError("Repair is not open on the current assignment.")
    _revision(row, expected)
    return row


def _validate_taxonomy(pk, category):
    item = RepairAction.objects.filter(pk=pk).first()
    if item is None or not repair_action_applies_to_category(repair_action=item, product_category=category):
        raise ValidationError("RepairAction must exist, be active and apply to this ProductCategory.")


def begin_service_case_repair(*, service_case, actor, note="", expected_engineer_assignment_id=_UNSET, expected_updated_at=_UNSET):
    with _technical(service_case, actor) as (case, assignment, actor, category):
        _revision(case, expected_updated_at)
        _expect(assignment, expected_engineer_assignment_id)
        if case.status != "DIAGNOSED" or ServiceRepairExecution.objects.filter(service_case=case, status="OPEN").exists():
            raise ValidationError("Repair can begin only from DIAGNOSED with no open attempt.")
        assessment_id = ServiceDiagnosticAssessment.objects.filter(service_case=case, engineer_assignment=assignment,
            completed_at__isnull=False, abandoned_at=None).order_by("-completed_at", "-pk").values_list("pk", flat=True).first()
        assessment = _diagnosis(assessment_id, case, assignment)
        row = ServiceRepairExecution(service_case=case, engineer_assignment=assignment, diagnostic_assessment=assessment, started_by=actor, note=note)
        row._persist()
        case.status = "REPAIRING"
        case._persist()
        return row


def update_repair_execution(*, repair_execution, actor, note, expected_updated_at=_UNSET):
    with _technical(_case_for(repair_execution), actor) as (case, assignment, actor, category):
        row = _open_execution(repair_execution, case, assignment, expected_updated_at)
        row.note = note
        row._persist()
        return row


def add_repair_action(*, repair_execution, repair_action, actor, note="", expected_updated_at=_UNSET):
    taxonomy_id = persisted_pk(repair_action, RepairAction)
    with _technical(_case_for(repair_execution), actor, [taxonomy_id]) as (case, assignment, actor, category):
        execution = _open_execution(repair_execution, case, assignment, expected_updated_at)
        _validate_taxonomy(taxonomy_id, category)
        row = ServiceRepairAction(repair_execution=execution, repair_action_id=taxonomy_id, note=note)
        row._persist()
        execution._persist()
        return row


def update_repair_action(*, action, actor, repair_action=_UNSET, note=_UNSET, expected_updated_at=_UNSET, expected_execution_updated_at=_UNSET):
    snapshot = ServiceRepairAction.objects.get(pk=persisted_pk(action, ServiceRepairAction))
    taxonomy_id = snapshot.repair_action_id if repair_action is _UNSET else persisted_pk(repair_action, RepairAction)
    with _technical(_case_for(snapshot.repair_execution), actor, [taxonomy_id]) as (case, assignment, actor, category):
        execution = _open_execution(snapshot.repair_execution, case, assignment, expected_execution_updated_at)
        row = ServiceRepairAction.objects.select_for_update().get(pk=snapshot.pk)
        _revision(row, snapshot.updated_at)
        _revision(row, expected_updated_at)
        if not row.is_active or row.performed_at is not None:
            raise ValidationError("Only active, unperformed plan rows can be edited.")
        _validate_taxonomy(taxonomy_id, category)
        row.repair_action_id = taxonomy_id
        if note is not _UNSET:
            row.note = note
        row._persist()
        execution._persist()
        return row


def _action_transition(action, actor, expected_updated_at, expected_execution_updated_at, *, perform):
    snapshot = ServiceRepairAction.objects.get(pk=persisted_pk(action, ServiceRepairAction))
    with _technical(_case_for(snapshot.repair_execution), actor, [snapshot.repair_action_id] if perform else []) as (case, assignment, actor, category):
        execution = _open_execution(snapshot.repair_execution, case, assignment, expected_execution_updated_at)
        row = ServiceRepairAction.objects.select_for_update().get(pk=snapshot.pk)
        _revision(row, snapshot.updated_at)
        _revision(row, expected_updated_at)
        if not row.is_active:
            raise ValidationError("Removed actions cannot change.")
        if perform:
            if row.performed_at is not None:
                raise ValidationError("Action is already performed.")
            _validate_taxonomy(row.repair_action_id, category)
            from apps.commercial.gating import require_commercial_authorization
            require_commercial_authorization(service_case=case, actor=actor, operation="PERFORM", repair_execution=execution, action=row)
            row.performed_at, row.performed_by = timezone.now(), actor
        else:
            row.is_active = False  # Retain any performed evidence and its timestamp.
        row._persist()
        execution._persist()
        return row


def remove_repair_action(*, action, actor, expected_updated_at=_UNSET, expected_execution_updated_at=_UNSET):
    return _action_transition(action, actor, expected_updated_at, expected_execution_updated_at, perform=False)


def mark_repair_action_performed(*, action, actor, expected_updated_at=_UNSET, expected_execution_updated_at=_UNSET):
    return _action_transition(action, actor, expected_updated_at, expected_execution_updated_at, perform=True)


def _action_facts(execution, *, lock=False):
    rows = ServiceRepairAction.objects.filter(repair_execution=execution, is_active=True).order_by("pk")
    if lock:
        rows = rows.select_for_update()
    return list(rows.values_list(
        "pk", "repair_action_id", "performed_at", "updated_at"))


def complete_service_case_repair(*, repair_execution, actor, outcome, note=_UNSET, expected_updated_at=_UNSET):
    if outcome not in ServiceRepairExecution.Outcome.values:
        raise ValidationError("Choose a supported repair outcome.")
    case = _case_for(repair_execution)
    facts = _action_facts(repair_execution)
    with _technical(case, actor, [row[1] for row in facts]) as (case, assignment, actor, category):
        execution = _open_execution(repair_execution, case, assignment, expected_updated_at)
        if facts != _action_facts(execution, lock=True):
            raise ValidationError("Repair plan changed; reload before completion.")
        if not facts or any(row[2] is None for row in facts):
            raise ValidationError("Completion requires at least one active action and every active action performed.")
        for _, taxonomy_id, _, _ in facts:
            _validate_taxonomy(taxonomy_id, category)
        if outcome == "REPAIRED":
            from apps.commercial.gating import require_commercial_authorization
            require_commercial_authorization(service_case=case, actor=actor, operation="COMPLETE", repair_execution=execution)
        if note is not _UNSET:
            execution.note = note
        execution.status = "COMPLETED"
        execution.outcome = outcome
        execution.completed_at, execution.completed_by = timezone.now(), actor
        execution._persist()
        case.status = "REPAIRED" if outcome == "REPAIRED" else "DIAGNOSED"
        case._persist()
        return execution


def _abandon(execution, actor, reason, timestamp):
    if not isinstance(reason, str) or not reason.strip():
        raise ValidationError("Repair abandonment requires a reason.")
    execution.status = "ABANDONED"
    execution.abandoned_at, execution.abandoned_by, execution.abandon_reason = timestamp, actor, reason
    execution._persist()


def abandon_service_case_repair(*, repair_execution, actor, reason, expected_updated_at=_UNSET):
    with _write():
        actor = _actors(actor)
        case = _locked_case(_case_for(repair_execution))
        assignment = ServiceEngineerAssignment.objects.select_for_update().filter(service_case=case, ended_at=None).first()
        if assignment is None:
            raise ValidationError("Current engineer assignment is missing.")
        execution = _open_execution(repair_execution, case, assignment, expected_updated_at)
        _abandon(execution, actor, reason, timezone.now())
        case.status = "DIAGNOSED"
        case._persist()
        return execution
