from django import forms
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Count, Q, OuterRef, Subquery
from django.http import Http404
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django.utils import timezone
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_GET
from apps.access.authorization import is_authorized
from apps.frontdesk.models import QueueEntry
from apps.communications.queries import history
from apps.sla.queries import records
from apps.service.engineer_queries import assigned_service_cases_for_engineer
from apps.service.quality_control_queries import qc_cases_for_inspector, quality_control_visible_cases
from apps.commercial import payment_queries, queries as quotation_queries
from apps.inventory import request_queries, queries as inventory_queries
from . import queries as q
from .context import shell
from .presentation import card, admin_link


class SearchForm(forms.Form):
    q = forms.CharField(label="Reference, customer name, exact mobile, IMEI or serial", max_length=128, required=False,
                        widget=forms.TextInput(attrs={"type": "search", "autocomplete": "off"}))


def permitted(request, permission, company_only=False):
    workspace = shell(request)["workspace"]
    if not workspace:
        return False
    key = (permission, company_only)
    cache = workspace["capabilities"]
    if key not in cache:
        cache[key] = q.capable(request.user, permission, company_only=company_only)
    return cache[key]


@never_cache
@login_required(login_url="operations:login")
@require_GET
def dashboard(request):
    actor = request.user
    today = timezone.localdate(timezone=timezone.get_default_timezone())
    cards = []
    queues = []
    def add(label, value, url, period="Current state"):
        cards.append(dict(label=label, value=value, url=url, period=period))
    case_url = reverse("operations:list", args=["cases"])
    def queue(label, rows, url):
        from apps.service.models import ServiceEngineerAssignment
        engineer = ServiceEngineerAssignment.objects.filter(service_case_id=OuterRef("pk"), ended_at=None)
        rows = rows.annotate(queue_engineer=Subquery(engineer.values("engineer__username")[:1]))
        items = []
        for row in rows.order_by("received_at", "pk")[:5]:
            item = card("cases", row)
            item["facts"].append(("Assigned engineer", row.queue_engineer or "Unassigned"))
            items.append(item)
        queues.append(dict(label=label, cards=items, url=url))
    if permitted(request, "service.view_servicecase"):
        grouped = list(q.cases(actor).order_by().values("status").annotate(
            total=Count("pk"), today=Count("pk", filter=Q(received_at__date=today))))
        counts = {r["status"]:r["total"] for r in grouped}
        add("Today's intake", sum(r["today"] for r in grouped), case_url+"?bucket=today", str(today))
        add("Assigned, awaiting diagnosis", counts.get("ASSIGNED", 0), case_url+"?state=ASSIGNED")
        add("Open service cases", sum(v for k,v in counts.items() if k not in ("CLOSED", "CANCELLED")), case_url+"?bucket=open")
        for status, label in [("DIAGNOSING", "Diagnosis in progress"), ("REPAIRING", "Repair in progress"),
                              ("QC_PENDING", "QC pending"), ("READY_FOR_DELIVERY", "Ready for delivery")]:
            add(label, counts.get(status,0), case_url+"?state="+status)
        if permitted(request, "service.handle_servicecase"):
            add("My eligible assigned jobs", assigned_service_cases_for_engineer(actor).filter(pk__in=q.cases(actor)).count(), case_url+"?bucket=mine")
            queue("Engineer work", q.cases(actor).filter(pk__in=assigned_service_cases_for_engineer(actor)), case_url+"?bucket=mine")
        if permitted(request, "service.perform_quality_control"):
            add("My eligible QC queue", qc_cases_for_inspector(actor).filter(pk__in=q.cases(actor)).count(), case_url+"?bucket=qc")
            queue("Quality control", q.cases(actor).filter(pk__in=qc_cases_for_inspector(actor)), case_url+"?bucket=qc")
        queue("Today's intake", q.cases(actor).filter(received_at__date=today), case_url+"?bucket=today")
        queue("Ready for delivery", q.cases(actor).filter(status="READY_FOR_DELIVERY"), case_url+"?state=READY_FOR_DELIVERY")
    if permitted(request, "frontdesk.view_appointment"):
        add("Appointments today (all statuses)", q.rows(actor,"appointments").filter(slot__date=today).count(), reverse("operations:list",args=["appointments"])+"?bucket=today", str(today))
        queues.append(dict(label="Today's appointments", url=reverse("operations:list", args=["appointments"])+"?bucket=today",
            cards=[card("appointments", row) for row in q.rows(actor, "appointments").filter(slot__date=today).order_by("slot__start_time", "pk")[:5]]))
    if permitted(request, "frontdesk.view_queue"):
        add("Waiting queue today", QueueEntry.objects.filter(service_center__in=q.centers(actor,"frontdesk.view_queue"),
            business_date=today, status="WAITING").count(), reverse("frontdesk:index"), str(today))
    if permitted(request, "sla.view_sla"):
        counts = {r["sla_state"]:r["total"] for r in records(actor).order_by().values("sla_state").annotate(total=Count("pk"))}
        for state,label in [("DUE_SOON","SLA due soon"),("OVERDUE","SLA overdue")]:
            add(label,counts.get(state,0),reverse("sla:dashboard")+"?state="+state)
        queues.append(dict(label="SLA attention", url=reverse("sla:dashboard"), cards=[dict(
            title=row.service_case.job_number, subtitle=str(row.service_case.service_center), state=row.sla_state,
            url=reverse("sla:detail", args=[row.service_case_id]), facts=[("Customer", row.service_case.customer.display_name),
                ("Due", row.due_at), ("Assigned engineer", row.engineer_name or "Unassigned")])
            for row in records(actor).filter(sla_state__in=("DUE_SOON", "OVERDUE")).order_by("due_at", "pk")[:5]]))
    if permitted(request,"commercial.view_servicequotation"):
        add("Cases awaiting customer approval",quotation_queries.service_cases_awaiting_customer_approval(actor=actor).count(),
            reverse("operations:list",args=["quotations"])+"?bucket=approval")
    if permitted(request,"commercial.view_servicepayment"):
        add("Finalized invoices with customer balance",payment_queries.unsettled_invoices(actor=actor).count(),reverse("operations:balances"))
    if permitted(request,"inventory.view_stock"):
        add("Active stock reservations",request_queries.stock_reservations(actor=actor,active_only=True).count(),reverse("operations:reservations"))
    if permitted(request,"communications.view_notification"):
        counts = history(actor).aggregate(pending=Count("pk",filter=Q(status="PENDING")),failed=Count("pk",filter=Q(status="FAILED")))
        for key in ("pending","failed"):
            add("Communications "+key,counts[key],reverse("communications:history"))
    return render(request,"operations/dashboard.html",{"title":"Operational dashboard","cards":cards,"today":today,"queues":queues})


def valid_kind(kind):
    if kind not in q.KINDS:
        raise Http404


@never_cache
@login_required(login_url="operations:login")
@require_GET
def listing(request,kind):
    valid_kind(kind)
    rows = q.rows(request.user,kind)
    form = SearchForm(request.GET)
    if not form.is_valid():
        rows = rows.none()
    elif form.cleaned_data["q"]:
        rows = q.match(rows,kind,form.cleaned_data["q"])
    bucket = request.GET.get("bucket", "")
    phase = request.GET.get("phase", "")
    if kind == "cases":
        state = request.GET.get("state", "")
        if state:
            if state not in q.ServiceCase.Status.values:
                raise Http404
            rows = rows.filter(status=state)
        if bucket == "open":
            rows = rows.exclude(status__in=("CLOSED","CANCELLED"))
        elif bucket == "today":
            rows = rows.filter(received_at__date=timezone.localdate(timezone=timezone.get_default_timezone()))
        elif bucket == "mine":
            rows = rows.filter(pk__in=assigned_service_cases_for_engineer(request.user))
        elif bucket == "qc":
            rows = rows.filter(pk__in=quality_control_visible_cases(request.user) if phase == "rework" else qc_cases_for_inspector(request.user))
        elif bucket:
            raise Http404
    elif kind == "appointments" and bucket == "today":
        rows = rows.filter(slot__date=timezone.localdate(timezone=timezone.get_default_timezone()))
    elif kind == "quotations" and bucket == "approval":
        rows = rows.filter(is_current=True,status="SUBMITTED",family__service_case__in=quotation_queries.service_cases_awaiting_customer_approval(actor=request.user))
    elif bucket:
        raise Http404
    queue_filters = []
    if kind == "cases" and bucket in ("mine", "qc"):
        from . import work_queues
        rows = work_queues.enriched(work_queues.filtered(rows, request.user, bucket, phase), request.user)
        choices = [("", "All eligible jobs")] + ([("assigned", "Awaiting diagnosis"), ("diagnosis", "Diagnosis in progress"), ("repair", "Repair work")]
            if bucket == "mine" else [("pending", "Awaiting QC"), ("in-progress", "QC in progress"), ("passed", "QC passed")])
        if permitted(request, "service.perform_quality_control"):
            choices.append(("rework", "Recorded QC failures (inspector scope)"))
        for value, label in choices:
            parameters = request.GET.copy()
            parameters["phase"] = value
            parameters.pop("page", None)
            queue_filters.append(dict(label=label, url="?"+parameters.urlencode(), current=value == phase))
    elif phase:
        raise Http404
    page = Paginator(rows,25).get_page(request.GET.get("page"))
    retained = request.GET.copy()
    retained.pop("page",None)
    titles = {"mine": "Engineer work", "qc": "Quality control", "today": "Today's intake", "open": "Open service jobs"}
    cards = []
    for row in page:
        item = card(kind, row)
        if queue_filters:
            item["facts"].extend(work_queues.facts(row))
        cards.append(item)
    return render(request,"operations/list.html",dict(title=titles.get(bucket, q.KINDS[kind][0]) if kind == "cases" else q.KINDS[kind][0],form=form,page=page,
        cards=cards,queue_filters=queue_filters,retained=retained.urlencode()),status=200 if form.is_valid() else 400)


@never_cache
@login_required(login_url="operations:login")
@require_GET
def search(request):
    form = SearchForm(request.GET)
    groups = []
    if form.is_valid() and form.cleaned_data["q"]:
        for kind,(label,permission) in q.KINDS.items():
            if not permitted(request,permission,kind=="customers"):
                continue
            rows = list(q.match(q.rows(request.user,kind),kind,form.cleaned_data["q"])[:11])
            if rows:
                groups.append(dict(label=label,cards=[card(kind,row) for row in rows[:10]],more=len(rows)>10))
    return render(request,"operations/search.html",dict(title="Operational search",form=form,groups=groups),status=200 if form.is_valid() else 400)


@never_cache
@login_required(login_url="operations:login")
@require_GET
def detail(request,kind,pk):
    valid_kind(kind)
    actor = request.user
    row = get_object_or_404(q.rows(actor,kind),pk=pk)
    links,related = [],[]
    primary_action_url = None
    next_actions = []
    def link_record(other_kind,pk,label):
        if q.rows(actor,other_kind).filter(pk=pk).exists():
            links.append((label,reverse("operations:detail",args=[other_kind,pk])))
    case = row if kind=="cases" else row.family.service_case if kind=="quotations" else row.service_case if kind=="invoices" else row.invoice.service_case if kind=="payments" else None
    if case:
        if kind!="cases":
            link_record("cases",case.pk,"Service case")
        else:
            from .workflows import STAGES, permitted as workflow_permitted
            for stage, (label, _, _) in STAGES.items():
                if workflow_permitted(actor,case,stage):
                    links.append((label,reverse("operations:workflow",args=[case.pk,stage])))
            from . import commercial_workflows as cw
            quotation, invoice = cw.context(actor,case)
            for operation in cw.available(actor,case,quotation,invoice):
                links.append((cw.OPERATIONS[operation][0],reverse("operations:commercial_workflow",args=[case.pk,operation])))
            from . import parts_workflows as pw
            for operation in pw.available(actor,case):
                links.append((pw.OPERATIONS[operation][0],reverse("operations:parts_workflow",args=[case.pk,operation])))
            primary_action_url = links[0][1] if links else None
            next_actions, links = links, []
            link_record("customers",case.customer_id,"Customer")
            link_record("devices",case.device_id,"Device")
            for other_kind,filter_by in [("quotations",{"family__service_case":case}),("invoices",{"service_case":case}),("payments",{"invoice__service_case":case})]:
                related.extend(card(other_kind,r) for r in q.rows(actor,other_kind).filter(**filter_by)[:10])
            if records(actor).filter(service_case=case).exists():
                links.append(("SLA evidence",reverse("sla:detail",args=[case.pk])))
            if history(actor).filter(related_id=case.pk).exists():
                links.append(("Case communications",reverse("operations:case_communications",args=[case.pk])))
            if request.user.is_staff:
                for permission,label,name in [("service.handle_servicecase","Diagnosis","diagnosis"),("service.handle_servicecase","Repair","repair"),("service.perform_quality_control","Quality control","quality_control")]:
                    if is_authorized(user=actor,permission=permission,target=case.service_center) and actor.has_perm("service.view_servicecase"):
                        if name == "quality_control" and not quality_control_visible_cases(actor).filter(pk=case.pk).exists():
                            continue
                        links.append((label+" workflow (Admin)",reverse("admin:service_servicecase_"+name,args=[case.pk])))
            if request_queries.parts_requests(actor=actor).filter(service_case=case).exists():
                links.append(("Case parts requests",reverse("operations:case_parts",args=[case.pk])))
    if kind in ("customers","devices"):
        related.extend(card("cases",r) for r in q.cases(actor).filter(**{ "customer" if kind=="customers" else "device":row}).order_by("-received_at")[:10])
    if kind=="customers":
        from .device_workflows import permitted as may_register
        if may_register(actor,row):
            links.append(("Register customer's device",reverse("operations:register_device",args=[row.pk])))
        from apps.devices.queries import currently_owned_devices
        related.extend(card("devices",r) for r in q.devices(actor).filter(pk__in=currently_owned_devices(row).values("pk"))[:10])
    if kind=="appointments" and is_authorized(user=actor,permission="frontdesk.view_appointment",target=row.service_center):
        links.append(("Front desk",reverse("frontdesk:desk",args=[row.service_center_id])+"?date="+row.slot.date.isoformat()))
    if kind == "payments":
        link_record("invoices",row.invoice_id,"Invoice")
    stock = []
    if kind=="inventory":
        stock = list(inventory_queries.stock_positions_for_location(actor=actor,location=row)[:50])
    admin_url = admin_link(request,row)
    job = None
    if kind == "cases":
        from .job_presentation import job_overview
        job = job_overview(actor, row, quotation)
    return render(request,"operations/detail.html",dict(title=card(kind,row)["title"],record=card(kind,row),links=links,
        related=related,admin_url=admin_url,stock=stock,kind=kind,job=job,primary_action_url=primary_action_url,next_actions=next_actions))


@never_cache
@login_required(login_url="operations:login")
@require_GET
def parts_requests(request):
    page = Paginator(request_queries.parts_requests(actor=request.user), 25).get_page(request.GET.get("page"))
    return render(request, "operations/evidence.html", dict(title="Parts requests", page=page,
        evidence=[[("Request", str(row.pk)), ("Job", row.service_case.job_number),
                   ("Center", str(row.service_case.service_center)), ("Status", row.status),
                   ("Requested", row.requested_at)] for row in page]))


@never_cache
@login_required(login_url="operations:login")
@require_GET
def balances(request):
    page = Paginator(payment_queries.unsettled_invoices(actor=request.user),25).get_page(request.GET.get("page"))
    return render(request,"operations/evidence.html",dict(title="Outstanding customer balances",page=page,
        evidence=[[("Invoice",r.number),("Center",str(r.service_center)),("Currency",r.currency),("Balance due",r.balance_due)] for r in page]))


@never_cache
@login_required(login_url="operations:login")
@require_GET
def reservations(request):
    page = Paginator(request_queries.stock_reservations(actor=request.user,active_only=True),25).get_page(request.GET.get("page"))
    return render(request,"operations/evidence.html",dict(title="Active stock reservations",page=page,
        evidence=[[("Part",r.spare_part.part_code),("Location",str(r.location)),("Quantity",r.quantity)] for r in page]))


@never_cache
@login_required(login_url="operations:login")
@require_GET
def case_evidence(request,pk,section):
    case = get_object_or_404(q.cases(request.user),pk=pk)
    if section=="communications":
        rows = history(request.user).filter(related_id=case.pk)
        page = Paginator(rows,25).get_page(request.GET.get("page"))
        evidence = [[("Requested",r.created_at),("Channel",r.channel),("Status",r.status)] for r in page]
        link = ("Communication workflow",reverse("communications:history")) if rows.exists() else None
    else:
        rows = request_queries.parts_requests(actor=request.user).filter(service_case=case)
        page = Paginator(rows,25).get_page(request.GET.get("page"))
        evidence = [[("Request",str(r.pk)),("Status",r.status),("Requested",r.requested_at)] for r in page]
        link = None
    return render(request,"operations/evidence.html",dict(title=case.job_number+" · "+section,page=page,evidence=evidence,link=link))
