from urllib.parse import urlencode

from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.core.paginator import Paginator
from django.db.models import Count, Window
from django.http import Http404, HttpResponseBadRequest
from django.shortcuts import render
from django.urls import reverse
from django.views.decorators.http import require_GET

from . import permissions as p
from .scope import centers, companies
from .filters import ReportFilters
from .service_dashboard import dashboard, dashboard_tables, turnaround
from .service_analytics import service_tables, service_rates, performed_actions, rate_totals, rates_from
from .inventory_analytics import inventory_tables
from .commercial_analytics import commercial_tables, quotation_approval_turnaround
from .management_reports import management_tables
from .exports import csv_response
from .query import combined_exists, combined_totals, totals

REPORTS = {
    "operational": ("Operational dashboard", p.OPERATIONAL, dashboard_tables),
    "service": ("Service analytics", p.SERVICE, service_tables),
    "inventory": ("Inventory analytics", p.INVENTORY, inventory_tables),
    "commercial": ("Commercial analytics", p.COMMERCIAL, commercial_tables),
    "management": ("Management summary", p.MANAGEMENT, management_tables),
}
LOCATION_ONLY = {"positions", "lowest_available", "zero_stock", "location_types", "movements", "transfers", "count_variances"}
LOCATION_FILTERS = {"date_from", "date_to", "company", "region", "service_center"}
ANALYTICAL_FILTERS = {"complaint", "fault_diagnosis", "root_cause", "unknown_root_cause", "repair_action", "event_outcome"}
PER_PAGE = 50
MAX_OFFSET = 2 ** 63 - 1  # PostgreSQL OFFSET is a bigint
DEFAULT_TABLES = {"operational": "cases", "service": "complaints_complaint", "inventory": "positions", "commercial": "quotations_created", "management": "workflow"}


def allowed_filters(key):
    if key in LOCATION_ONLY:
        return LOCATION_FILTERS
    allowed = set(ReportFilters.base_fields) - ANALYTICAL_FILTERS
    if key == "reservations":
        allowed.discard("engineer")
    if key.startswith("complaint"):
        allowed.add("complaint")
    if key.startswith(("diagnosis", "diagnoses", "root_causes", "cooccurrence")):
        allowed |= {"complaint", "fault_diagnosis", "root_cause", "unknown_root_cause"}
    if key.startswith(("cooccurrence", "actions")):
        allowed.add("repair_action")
    if key in {"repair_attempts", "repair_completed", "completed_repair_records", "qc_attempts", "qc_completed", "completed_qc_records", "qc_drilldown"} or key.startswith("qc_failure"):
        allowed.add("event_outcome")
    return allowed


def access(user, permission):
    """(view, export) access from the authoritative scoped querysets, probed in one round trip."""
    found = combined_exists(
        view_centers=centers(user, permission), view_companies=companies(user, permission),
        export_centers=centers(user, permission, export=True), export_companies=companies(user, permission, export=True))
    return found["view_centers"] or found["view_companies"], found["export_centers"] or found["export_companies"]


def page_of(rows, page_number):
    """The Paginator page, with its rows and the total read in ONE query at any size.

    ``COUNT(*) OVER ()`` is evaluated after WHERE/GROUP BY and before LIMIT, so it
    equals ``rows.count()``. Report rows are ``values()`` dicts without DISTINCT.
    """
    paginator = Paginator(rows, PER_PAGE)
    offset = (page_number - 1) * PER_PAGE
    if offset > MAX_OFFSET:
        # Past any possible last page; never send an out-of-range OFFSET to PostgreSQL.
        raise Http404
    page_rows = list(rows.annotate(_report_total=Window(Count("*")))[offset:offset + PER_PAGE])
    if page_rows:
        paginator.count = page_rows[0]["_report_total"]
        for row in page_rows:
            del row["_report_total"]
    elif page_number == 1:
        paginator.count = 0
    else:
        # No row at this offset means total <= offset: always past the last page.
        raise Http404
    page = paginator.get_page(page_number)
    if page.number != page_number:
        raise Http404
    page.object_list = page_rows
    return page


def navigation_link(label, path, target, retained):
    compatible = {k: v for k, v in retained.items() if k in allowed_filters(target)}
    removed = sorted(set(retained) - set(compatible))
    if removed:
        label += " (clears " + ", ".join(key.replace("_", " ") for key in removed) + ")"
    return label, path + "?" + urlencode(compatible | {"table": target})


@require_GET
@login_required(login_url="reporting:login")
def report(request, section="operational"):
    if section not in REPORTS:
        raise Http404
    title, permission, builder = REPORTS[section]
    exporting = request.GET.get("export") == "csv"
    viewable, exportable = access(request.user, permission)
    if not (exportable if exporting else viewable):
        raise PermissionDenied
    supported = set(ReportFilters.base_fields) | {"table", "page", "export"}
    if set(request.GET) - supported or any(len(request.GET.getlist(key)) != 1 for key in request.GET):
        return HttpResponseBadRequest("Unsupported or repeated report parameter.")
    if request.GET.get("export", "") not in ("", "csv"):
        return HttpResponseBadRequest("Unsupported export format.")
    form = ReportFilters(request.GET)
    if not form.is_valid():
        return render(request, "reporting/error.html", {"title": "Invalid reporting filters", "form": form}, status=400)
    filters = form.cleaned_data
    reports = builder(request.user, filters, permission=permission, export=exporting)
    by_key = {row.key: row for row in reports}
    key = request.GET.get("table") or reports[0].key
    if key not in by_key:
        raise Http404
    allowed = allowed_filters(key)
    if any(value not in (None, "") and name not in allowed for name, value in filters.items()):
        return HttpResponseBadRequest("This report has no authoritative relationship for one of the supplied filter dimensions.")
    selected = by_key[key]
    if exporting:
        return csv_response(selected)
    try:
        page_number = int(request.GET.get("page", "1"))
        if page_number < 1:
            raise ValueError
    except ValueError:
        return HttpResponseBadRequest("Invalid page number.")
    page = page_of(selected.rows, page_number)
    page_rows = page.object_list
    retained = {k: v for k, v in request.GET.items() if k in ReportFilters.base_fields and v}
    navigation = []
    for item in reports:
        navigation.append(navigation_link(item.title, "", item.key, retained))
    query = retained | {"table": key}
    shown_form = ReportFilters(request.GET)
    for field in list(shown_form.fields):
        if field not in allowed:
            del shown_form.fields[field]
    cards, tats = {}, []
    if section in ("operational", "management"):
        cards = dashboard(request.user, filters, permission=permission)
        tats = turnaround(request.user, filters, permission=permission)
    if section == "commercial":
        tats = [dict(metric="Quotation submission → approval", **quotation_approval_turnaround(request.user, filters, permission=permission))]
    rate_filters = {k: v for k, v in filters.items() if k not in ANALYTICAL_FILTERS}
    rates = []
    if section == "service":
        # Card and ratios are independent single-row totals: read them together.
        counts = combined_totals(performed=totals(performed_actions(request.user, filters, permission=permission), count=Count("*")),
                                 **rate_totals(request.user, rate_filters, permission=permission))
        cards = {"distinct_performed_actions": counts.pop("performed")["count"]}
        rates = rates_from(counts)
    if section == "management":
        rates = service_rates(request.user, rate_filters, permission=permission)
    drilldowns = []
    for row in page_rows:
        target, extra = drilldown(key, row)
        drilldowns.append("?" + urlencode(retained | extra | {"table": target}) if target else None)
    rendered_rows = list(selected.values(page_rows))
    context = dict(title=title, site_title="c-care reporting", site_header="c-care reporting", has_permission=True,
        form=shown_form, section=section, selected=selected, headers=selected.headers, rows=rendered_rows,
        linked_rows=list(zip(rendered_rows, drilldowns)),
        page=page, can_export=exportable, navigation=navigation, cards={label.replace("_", " ").capitalize(): value for label, value in cards.items()}, turnaround=tats, rates=rates,
        sections=[navigation_link(label, reverse("reporting:section", args=[name]), DEFAULT_TABLES[name], retained) for name, (label, _, _) in REPORTS.items()],
        export_url="?" + urlencode(query | {"export": "csv"}),
        previous_url="?" + urlencode(query | {"page": page_number - 1}), next_url="?" + urlencode(query | {"page": page_number + 1}))
    response = render(request, "reporting/report.html", context)
    response["Cache-Control"] = "private, no-store"
    return response


def drilldown(key, row):
    if key == "workflow":
        return "open_cases" if row["status"] not in ("CLOSED", "CANCELLED") else None, {"status": row["status"]}
    if key.startswith("complaints_"):
        extra = {"complaint": row["complaint_symptom_id"]}
        dimension = key.removeprefix("complaints_")
        if dimension != "complaint" and row.get("dimension_id") is not None:
            extra[dimension] = row["dimension_id"]
        # No variant filter for NULL; avoid a broader drill-down than its bucket.
        if dimension == "variant" and row.get("dimension_id") is None:
            return None, {}
        return "complaint_records", extra
    if key in ("diagnoses", "root_causes"):
        extra = ({"fault_diagnosis": row["fault_diagnosis_id"]} if key == "diagnoses" else
                 {"root_cause": row["root_cause_id"]} if row["root_cause_id"] else {"unknown_root_cause": "yes"})
        return "diagnosis_records", extra
    if key == "repair_completed":
        return "completed_repair_records", {"event_outcome": row["outcome"]}
    return None, {}
