"""Operational forms for existing technical services; no alternate lifecycle."""
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied, ValidationError
from django.http import Http404
from django.shortcuts import get_object_or_404, render, redirect
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_http_methods
from apps.access.authorization import require_permission, is_authorized
from apps.service import engineer_queries as eq, engineer_services as es
from apps.service import diagnostic_queries as dq, diagnostic_services as ds
from apps.service import repair_queries as rq, repair_services as rs
from apps.service import quality_control_queries as qq, quality_control_services as qs
from apps.service import handover_services as hs
from apps.service.engineer_admin import AssignmentForm
from apps.service.diagnostic_admin import DiagnosisForm
from apps.service.repair_admin import RepairForm
from apps.service.quality_control_admin import QualityControlForm
from apps.service.handover_admin import HandoverForm
from .queries import cases


STAGES = {
    "assignment": ("Engineer assignment", "service.change_servicecase", ("RECEIVED","ASSIGNED")),
    "diagnosis": ("Diagnosis", "service.handle_servicecase", ("ASSIGNED","DIAGNOSING")),
    "repair": ("Repair", "service.handle_servicecase", ("DIAGNOSED","REPAIRING")),
    "qc": ("Quality control", "service.perform_quality_control", ("REPAIRED","QC_PENDING","QC_IN_PROGRESS")),
    "delivery": ("Delivery and closure", "service.handover_servicecase", ("QC_PASSED","READY_FOR_DELIVERY","DELIVERED")),
}


def permitted(actor, case, stage):
    _, permission, states = STAGES[stage]
    if case.status not in states:
        return False
    if stage == "delivery" and case.status == "DELIVERED":
        permission = "service.close_servicecase"
    if not is_authorized(user=actor,permission=permission,target=case.service_center):
        return False
    if stage in ("diagnosis","repair"):
        assignment = eq.current_engineer_assignment(case)
        return bool(assignment and assignment.engineer_id == actor.pk)
    if stage == "qc":
        current=qq.current_quality_control(case)
        return (not current or current.inspector_id==actor.pk) and qq.eligible_quality_control_inspectors(case).filter(pk=actor.pk).exists()
    return True


def form_for(stage, case, data):
    if stage == "assignment":
        return AssignmentForm(data,case=case,current=eq.current_engineer_assignment(case))
    if stage == "diagnosis":
        return DiagnosisForm(data,case=case,assessment=dq.current_diagnostic_assessment(case),assignment=eq.current_engineer_assignment(case))
    if stage == "repair":
        return RepairForm(data,case=case,execution=rq.current_repair_execution(case),assignment=eq.current_engineer_assignment(case))
    if stage == "qc":
        return QualityControlForm(data,case=case,quality_control=qq.current_quality_control(case))
    return HandoverForm(data,case=case)


def perform(stage, form, actor):
    case, data = form.case, form.cleaned_data
    op = data["operation"]
    if stage == "assignment":
        if op == "assign":
            es.assign_service_case_engineer(service_case=case,engineer=data["engineer"],assigned_by=actor,note=data["note"],expected_updated_at=case.updated_at)
        elif op == "reassign":
            es.reassign_service_case_engineer(service_case=case,new_engineer=data["engineer"],reassigned_by=actor,reason=data["reason"],note=data["note"],expected_current_assignment_id=form.current.pk if form.current else None)
        else:
            es.unassign_service_case_engineer(service_case=case,unassigned_by=actor,reason=data["reason"],expected_current_assignment_id=form.current.pk if form.current else None)
    elif stage == "diagnosis":
        row = form.assessment
        if op == "begin":
            ds.begin_service_case_diagnosis(service_case=case,actor=actor,expected_engineer_assignment_id=form.assignment.pk,expected_updated_at=case.updated_at)
        elif op == "add":
            ds.add_diagnostic_finding(assessment=row,actor=actor,fault_diagnosis=data["fault_diagnosis"],root_cause=data["root_cause"],note=data["note"],expected_updated_at=row.updated_at)
        elif op in ("update","remove"):
            args=dict(finding=data["finding"],actor=actor,expected_updated_at=data["finding"].updated_at,expected_assessment_updated_at=row.updated_at)
            if op=="remove": ds.remove_diagnostic_finding(**args)
            else: ds.update_diagnostic_finding(**args,fault_diagnosis=data["fault_diagnosis"],root_cause=data["root_cause"],note=data["note"])
        elif op == "complete":
            ds.complete_service_case_diagnosis(assessment=row,actor=actor,technical_note=data["technical_note"],expected_updated_at=row.updated_at)
        elif op == "note":
            ds.update_diagnostic_assessment(assessment=row,actor=actor,technical_note=data["technical_note"],expected_updated_at=row.updated_at)
        else:
            ds.abandon_service_case_diagnosis(assessment=row,actor=actor,reason=data["reason"],expected_updated_at=row.updated_at)
    elif stage == "repair":
        row = form.execution
        if op == "begin":
            rs.begin_service_case_repair(service_case=case,actor=actor,note=data["execution_note"],expected_engineer_assignment_id=form.assignment.pk,expected_updated_at=case.updated_at)
        elif op == "add":
            rs.add_repair_action(repair_execution=row,actor=actor,repair_action=data["repair_action"],note=data["note"],expected_updated_at=row.updated_at)
        elif op in ("update","remove","perform"):
            args=dict(action=data["action"],actor=actor,expected_updated_at=data["action"].updated_at,expected_execution_updated_at=row.updated_at)
            if op=="update": rs.update_repair_action(**args,repair_action=data["repair_action"],note=data["note"])
            elif op=="remove": rs.remove_repair_action(**args)
            else: rs.mark_repair_action_performed(**args)
        elif op=="complete":
            rs.complete_service_case_repair(repair_execution=row,actor=actor,outcome=data["outcome"],note=data["execution_note"],expected_updated_at=row.updated_at)
        elif op=="note":
            rs.update_repair_execution(repair_execution=row,actor=actor,note=data["execution_note"],expected_updated_at=row.updated_at)
        else:
            rs.abandon_service_case_repair(repair_execution=row,actor=actor,reason=data["reason"],expected_updated_at=row.updated_at)
    elif stage == "qc":
        row = form.quality_control
        if op in ("submit","begin"):
            method=qs.submit_for_quality_control if op=="submit" else qs.begin_quality_control
            method(service_case=case,inspector=actor,expected_updated_at=case.updated_at)
        else:
            args=dict(quality_control=row,inspector=actor,expected_updated_at=row.updated_at)
            if op=="check": qs.set_quality_control_check(**args,check_code=data["check"].check_code,result=data["result"],note=data["note"])
            elif op=="complaint": qs.set_quality_control_complaint_check(**args,complaint=data["complaint_check"].complaint,result=data["complaint_result"],note=data["note"])
            elif op=="summary": qs.update_quality_control(**args,summary=data["summary"])
            elif op=="pass": qs.complete_quality_control_pass(**args,summary=data["summary"])
            elif op=="fail": qs.complete_quality_control_fail(**args,failure_note=data["reason"])
            else: qs.abandon_quality_control(quality_control=row,actor=actor,reason=data["reason"],expected_updated_at=row.updated_at)
    else:
        args=dict(service_case=case,actor=actor,note=data["note"],expected_updated_at=case.updated_at)
        if op=="close": hs.close_service_case(**args)
        else:
            args.update(expected_device=form.device,expected_device_identity=form.device_identity)
            if op=="ready": hs.mark_service_case_ready_for_delivery(**args)
            else:
                hs.handover_service_case(**args,expected_customer_updated_at=form.customer.updated_at,
                    accessories=[dict(intake_accessory=a,returned_quantity=data[f"returned_{a.pk.hex}"],condition_note=data[f"condition_{a.pk.hex}"]) for a in form.accessories],
                    **{name:data[name] for name in ("recipient_type","recipient_name","recipient_mobile","recipient_relationship",
                    "recipient_identity_type","recipient_identity_reference","verification_method","verification_reference")})


@never_cache
@login_required(login_url="operations:login")
@require_http_methods(["GET","POST"])
def workflow(request, pk, stage):
    if stage not in STAGES: raise Http404
    case=get_object_or_404(cases(request.user),pk=pk)
    if not permitted(request.user,case,stage): raise PermissionDenied
    form=form_for(stage,case,request.POST if request.method=="POST" else None)
    if request.method=="POST" and form.is_valid():
        try:
            perform(stage,form,request.user)
        except ValidationError as error:
            form.add_error(None,ValidationError(error.messages))
        else:
            messages.success(request,"Workflow operation recorded.")
            return redirect("operations:detail",kind="cases",pk=case.pk)
    return render(request,"operations/workflow.html",dict(title=STAGES[stage][0],case=case,form=form,
        identifiers=case.device.identifiers.filter(is_active=True)),status=400 if request.method=="POST" else 200)
