"""Read-only presentation adapters. Scope is applied before matching or slicing."""
from uuid import UUID
from django.core.exceptions import ValidationError
from django.db.models import Q, F
from apps.access.authorization import authorized_capabilities, authorized_capability_map, authorized_queryset
from apps.organization.models import Company, ServiceCenter
from apps.service.models import ServiceCase
from apps.customers.models import Customer, normalize_mobile
from apps.devices.models import Device, CustomerDeviceRelationship
from apps.devices.identification import identity_key
from apps.commercial import queries as quotations, invoice_queries as invoices, payment_queries as payments
from apps.inventory import queries as inventory
from apps.parts.queries import active_spare_parts
from apps.frontdesk.models import Appointment

KINDS = {
    "cases": ("Service cases", "service.view_servicecase"),
    "customers": ("Customers", "customers.view_customer"),
    "devices": ("Devices", "devices.view_device"),
    "quotations": ("Quotations", "commercial.view_servicequotation"),
    "invoices": ("Invoices", "commercial.view_serviceinvoice"),
    "payments": ("Payments / receipts", "commercial.view_servicepayment"),
    "parts": ("Parts", "parts.view_sparepart"),
    "inventory": ("Inventory locations", "inventory.view_stock"),
    "appointments": ("Appointments", "frontdesk.view_appointment"),
}


def centers(actor, permission):
    return authorized_queryset(user=actor, permission=permission, queryset=ServiceCenter.objects.all())


def companies(actor, permission):
    return authorized_queryset(user=actor, permission=permission, queryset=Company.objects.all())


def capable(actor, permission, *, company_only=False):
    return companies(actor, permission).exists() or (not company_only and centers(actor, permission).exists())


def capabilities(actor, permissions, *, company_only=False):
    """Authoritative bulk capability decisions for many permissions at once.

    Delegates to the authorization layer, which resolves the whole set in a
    bounded number of queries. Each value is identical to `capable()` for the
    same permission and `company_only`.
    """
    return authorized_capabilities(user=actor, permissions=permissions, company_only=company_only)


def capability_map(actor, permissions):
    """Both scope modes for many permissions, from one authoritative resolution."""
    return authorized_capability_map(user=actor, permissions=permissions)


def cases(actor):
    return ServiceCase.objects.filter(service_center__in=centers(actor, "service.view_servicecase"),
        company_id=F("service_center__company_id")).select_related("service_center", "customer", "device__product_model__brand")


def customers(actor):
    # Customer master data is company scoped, not implicitly visible from a case.
    return Customer.objects.filter(company__in=companies(actor, "customers.view_customer")).select_related("company")


def devices(actor):
    allowed = centers(actor, "devices.view_device")
    customer_companies = companies(actor, "customers.view_customer").filter(pk__in=allowed.values("company_id"))
    owned = CustomerDeviceRelationship.objects.filter(customer__company__in=customer_companies).values("device_id")
    serviced = cases(actor).filter(service_center__in=allowed).values("device_id")
    visible_companies = allowed.values("company_id")
    foreign_owners = CustomerDeviceRelationship.objects.filter(relationship_type="OWNER").exclude(customer__company__in=visible_companies).values("device_id")
    return Device.objects.filter(Q(pk__in=owned) | Q(pk__in=serviced)).exclude(pk__in=foreign_owners).select_related("product_model__brand", "product_variant")


def rows(actor, kind):
    if kind == "cases":
        return cases(actor).order_by("-received_at", "pk")
    if kind == "customers":
        return customers(actor).order_by("customer_number", "pk")
    if kind == "devices":
        return devices(actor).order_by("pk")
    if kind == "quotations":
        return quotations.service_quotations(actor=actor).select_related("family")
    if kind == "invoices":
        return invoices.service_invoices(actor=actor)
    if kind == "payments":
        return payments.service_payments(actor=actor)
    if kind == "inventory":
        return inventory.authorized_locations(actor=actor)
    if kind == "parts":
        return active_spare_parts() if capable(actor, KINDS[kind][1]) else active_spare_parts().none()
    if kind == "appointments":
        return Appointment.objects.filter(service_center__in=centers(actor, KINDS[kind][1]),
            company_id=F("service_center__company_id")).select_related("service_center", "slot", "customer")
    raise KeyError(kind)


def match(rows, kind, term):
    """Bounded input; exact normalized device/contact IDs, reference/name matching."""
    term = term.strip()
    if not term or len(term) > 128:
        return rows.none()
    try:
        key = identity_key(term)
    except ValidationError:
        key = ""
    try:
        mobile = normalize_mobile(term)
    except ValidationError:
        mobile = None
    if kind == "cases":
        return rows.filter(Q(job_number__iexact=term) | Q(device__identifiers__identity_key=key)).distinct()
    if kind == "customers":
        predicate = Q(customer_number__iexact=term) | Q(full_name__icontains=term) | Q(organization_name__icontains=term)
        if mobile:
            predicate |= Q(primary_mobile=mobile) | Q(customercontact_records__is_active=True,
                customercontact_records__normalized_value=mobile)
        return rows.filter(predicate).distinct()
    if kind == "devices":
        return rows.filter(identifiers__identity_key=key).distinct()
    if kind == "quotations":
        return rows.filter(family__number__iexact=term)
    if kind == "invoices":
        return rows.filter(number__iexact=term)
    if kind == "payments":
        return rows.filter(Q(receipt__number__iexact=term) | Q(reference__iexact=term))
    if kind == "parts":
        return rows.filter(Q(part_code__icontains=term) | Q(manufacturer_part_number__iexact=term) | Q(name__icontains=term))
    if kind == "inventory":
        return rows.filter(code__iexact=term)
    if kind == "appointments":
        try:
            pk = UUID(term)
        except ValueError:
            return rows.none()
        return rows.filter(pk=pk)
    return rows.none()
