from django import forms
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ValidationError
from django.core.paginator import Paginator
from django.db.models import Q, Count
from django.shortcuts import get_object_or_404, redirect, render
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_http_methods

from apps.access.authorization import is_authorized, require_permission
from apps.customers import services as customer_services
from apps.organization.models import ServiceCenter
from . import forms as f, queries, services
from .models import Appointment, AppointmentSlot, QueueEntry


def _allowed(actor, permission, center):
    return is_authorized(user=actor, permission=permission, target=center)


@never_cache
@login_required(login_url="reporting:login")
@require_http_methods(["GET"])
def index(request):
    available = ServiceCenter.objects.filter(Q(pk__in=queries.centers(request.user, "frontdesk.view_appointment"))
        | Q(pk__in=queries.centers(request.user, "frontdesk.view_queue"))).select_related("company").order_by("name")
    return render(request, "frontdesk/index.html", {"centers": available})


@never_cache
@login_required(login_url="reporting:login")
@require_http_methods(["GET"])
def desk(request, center_id):
    center = get_object_or_404(ServiceCenter.objects.filter(
        Q(pk__in=queries.centers(request.user, "frontdesk.view_appointment"))
        | Q(pk__in=queries.centers(request.user, "frontdesk.view_queue"))).select_related("company"), pk=center_id)
    permissions = {}
    def can(permission, target=None):
        target = center if target is None else target
        key = (permission, target._meta.label, target.pk)
        if key not in permissions:
            permissions[key] = _allowed(request.user, permission, target)
        return permissions[key]
    today = timezone.localdate(timezone=timezone.get_default_timezone())
    try:
        selected_date = forms.DateField().clean(request.GET.get("date", today.isoformat()))
    except ValidationError:
        from django.http import HttpResponseBadRequest
        return HttpResponseBadRequest("Invalid front-desk date.")
    appointments = Appointment.objects.none()
    entries = QueueEntry.objects.none()
    show_appointments = can("frontdesk.view_appointment")
    show_queue = can("frontdesk.view_queue")
    if show_appointments:
        appointments = Appointment.objects.filter(service_center=center, company_id=center.company_id, slot__date=selected_date).select_related("slot", "customer")
    if show_queue:
        entries = QueueEntry.objects.filter(service_center=center, company_id=center.company_id, business_date=selected_date).select_related("customer", "service_case")
    links = []
    for action in ("appointment", "walk-in", "slot", "call-next", "customer"):
        if action in ("call-next", "walk-in") and selected_date != today:
            continue
        permission = ACTIONS[action][0]
        target = center.company if action == "customer" else center
        if can(permission, target):
            links.append((ACTIONS[action][1], reverse("frontdesk:operation", args=[center.pk, action])))
    cards = []
    if show_appointments:
        counts = {r["status"]: r["count"] for r in appointments.order_by().values("status").annotate(count=Count("pk"))}
        cards.extend((label, counts.get(state, 0)) for state, label in (("SCHEDULED", "Scheduled"), ("CHECKED_IN", "Checked in")))
    if show_queue:
        counts = entries.aggregate(waiting=Count("pk", filter=Q(status="WAITING")), called=Count("pk", filter=Q(status="CALLED")),
            serving=Count("pk", filter=Q(status="SERVING")), intake=Count("pk", filter=Q(service_case__isnull=False)))
        cards.extend((label, counts[key]) for key, label in (("waiting", "Waiting"), ("called", "Called"), ("serving", "Serving"), ("intake", "Intake created")))
    state = request.GET.get("queue_state", "")
    if state:
        if state not in QueueEntry.Status.values:
            from django.http import Http404
            raise Http404
        entries = entries.filter(status=state)
    appointment_page = Paginator(appointments, 25).get_page(request.GET.get("appointment_page"))
    queue_page = Paginator(entries, 25).get_page(request.GET.get("queue_page"))
    appointment_rows, queue_rows = [], []
    for row in appointment_page:
        actions = []
        if row.status == "SCHEDULED":
            for action in ("check-in", "cancel-appointment", "no-show"):
                if action == "check-in" and selected_date != today:
                    continue
                if can(ACTIONS[action][0]):
                    actions.append((ACTIONS[action][1], reverse("frontdesk:record-operation", args=[center.pk, action, row.pk])))
        appointment_rows.append((row, actions))
    for row in queue_page:
        actions = []
        if row.service_case_id and can("service.view_servicecase"):
            actions.append(("Open job card", reverse("operations:detail", args=["cases", row.service_case_id])))
        candidates = {"WAITING": ["call", "cancel-queue"], "CALLED": ["serve", "cancel-queue"],
                      "SERVING": ["intake", "complete"]}.get(row.status, [])
        for action in candidates:
            if action == "intake" and row.service_case_id:
                continue
            if can(ACTIONS[action][0]) and (action != "intake" or can("service.add_servicecase")):
                actions.append((ACTIONS[action][1], reverse("frontdesk:record-operation", args=[center.pk, action, row.pk])))
        queue_rows.append((row, actions))
    search_results = queries.search(request.user, center, request.GET.get("q", ""))
    slots = AppointmentSlot.objects.filter(service_center=center, date__gte=today)[:25] if can("frontdesk.manage_slots") else AppointmentSlot.objects.none()
    recent_jobs = []
    if can("service.view_servicecase"):
        from apps.operations.queries import cases
        from apps.operations.presentation import card
        recent_jobs = [card("cases", row) for row in cases(request.user).filter(service_center=center, received_at__date=selected_date).order_by("-received_at", "pk")[:5]]
    def page_links(page, parameter):
        links = []
        for label, number in (("Previous", page.number - 1), ("Next", page.number + 1)):
            if 1 <= number <= page.paginator.num_pages:
                values = request.GET.copy()
                values[parameter] = number
                links.append((label, "?" + values.urlencode()))
        return links
    return render(request, "frontdesk/desk.html", dict(center=center, today=today, selected_date=selected_date, appointments=appointment_rows,
        queue=queue_rows, links=links, slots=slots, search_customers=search_results[0], search_devices=search_results[1],
        search_cases=search_results[2], q=request.GET.get("q", ""), cards=cards, show_appointments=show_appointments, show_queue=show_queue,
        recent_jobs=recent_jobs, show_jobs=can("service.view_servicecase"), queue_states=QueueEntry.Status.choices, queue_state=state,
        appointment_page=appointment_page, queue_page=queue_page, appointment_pages=page_links(appointment_page, "appointment_page"),
        queue_pages=page_links(queue_page, "queue_page")))


ACTIONS = {
    "appointment": ("frontdesk.create_appointment", "Create appointment", f.AppointmentForm, None),
    "walk-in": ("frontdesk.manage_queue", "Add walk-in", f.PartyForm, None),
    "slot": ("frontdesk.manage_slots", "Configure new availability", f.SlotForm, None),
    "configure-slot": ("frontdesk.manage_slots", "Update capacity / availability", f.ConfigureSlotForm, AppointmentSlot),
    "customer": ("customers.add_customer", "Create customer", f.CustomerForm, None),
    "check-in": ("frontdesk.check_in_appointment", "Check in", forms.Form, Appointment),
    "cancel-appointment": ("frontdesk.cancel_appointment", "Cancel appointment", f.ReasonForm, Appointment),
    "no-show": ("frontdesk.cancel_appointment", "Record no show", f.ReasonForm, Appointment),
    "call-next": ("frontdesk.manage_queue", "Call next", forms.Form, None),
    "call": ("frontdesk.manage_queue", "Call", forms.Form, QueueEntry),
    "serve": ("frontdesk.manage_queue", "Start serving", forms.Form, QueueEntry),
    "intake": ("frontdesk.manage_queue", "Create service intake", f.IntakeForm, QueueEntry),
    "complete": ("frontdesk.manage_queue", "Complete front-desk visit", forms.Form, QueueEntry),
    "cancel-queue": ("frontdesk.manage_queue", "Cancel visit", f.ReasonForm, QueueEntry),
}


@never_cache
@login_required(login_url="reporting:login")
@require_http_methods(["GET", "POST"])
def operation(request, center_id, action, record_id=None):
    from django.http import Http404
    if action not in ACTIONS:
        raise Http404
    permission, title, form_class, model = ACTIONS[action]
    center = get_object_or_404(ServiceCenter, pk=center_id)
    require_permission(user=request.user, permission=permission, target=center.company if action == "customer" else center)
    if bool(model) != bool(record_id):
        raise Http404
    record_filters = {"company_id": center.company_id} if model in (Appointment, QueueEntry) else {}
    record = get_object_or_404(model, pk=record_id, service_center=center, **record_filters) if model else None
    if action == "intake":
        require_permission(user=request.user, permission="service.add_servicecase", target=center)
    kwargs = {"actor": request.user, "center": center} if issubclass(form_class, (f.PartyForm, f.IntakeForm)) else {}
    if action == "configure-slot":
        kwargs["initial"] = {"capacity": record.capacity, "is_active": record.is_active}
    form = form_class(request.POST if request.method == "POST" else None, **kwargs)
    if request.method == "POST" and form.is_valid():
        data, actor = form.cleaned_data, request.user
        try:
            if action == "appointment":
                services.book_appointment(actor=actor, **data)
            elif action == "walk-in":
                services.add_walk_in(actor=actor, service_center=center, **data)
            elif action == "slot":
                services.create_slot(actor=actor, service_center=center, **data)
            elif action == "configure-slot":
                services.configure_slot(actor=actor, slot=record, **data)
            elif action == "customer":
                customer_services.create_customer(company=center.company, **data)
            elif action == "check-in":
                services.check_in(actor=actor, appointment=record)
            elif action in ("cancel-appointment", "no-show"):
                services.end_appointment(actor=actor, appointment=record, no_show=action == "no-show", **data)
            elif action == "call-next":
                services.call_next(actor=actor, service_center=center)
            elif action == "intake":
                services.start_intake(actor=actor, queue_entry=record, **data)
            else:
                target = {"call": "CALLED", "serve": "SERVING", "complete": "COMPLETED", "cancel-queue": "CANCELLED"}[action]
                services.transition_queue(actor=actor, queue_entry=record, target=target, **data)
        except ValidationError as error:
            form.add_error(None, " ".join(error.messages))
        else:
            messages.success(request, "Front-desk operation recorded.")
            return redirect("frontdesk:desk", center_id=center.pk)
    return render(request, "frontdesk/form.html", {"form": form, "title": title, "center": center, "record": record},
                  status=400 if request.method == "POST" else 200)
