"""Explicit adapters for the organization models supported by business authorization."""
from dataclasses import dataclass

from django.db.models import Exists, F, OuterRef, Q

from apps.organization.models import Company, Department, Region, ServiceCenter, UserOrganizationAssignment


def assignment_is_valid():
    """Defense in depth against stale or unsupported raw hierarchy changes."""
    return (
        Q(is_active=True, user__is_active=True, company__is_active=True)
        & (Q(region__isnull=True) | Q(region__is_active=True, region__company_id=F("company_id")))
        & (Q(department__isnull=True) | Q(department__is_active=True, department__company_id=F("company_id")))
        & (Q(service_center__isnull=True) | Q(
            service_center__is_active=True, service_center__company_id=F("company_id"),
            region__isnull=False, region_id=F("service_center__region_id"),
        ))
    )


def company_scope():
    return Q(region__isnull=True, service_center__isnull=True, department__isnull=True)


@dataclass(frozen=True)
class ScopeAdapter:
    # containment is applied to a source assignment correlated to the outer target.
    valid_target: Q
    containment: Q


ADAPTERS = {
    Company: ScopeAdapter(
        Q(is_active=True),
        Q(company_id=OuterRef("pk")) & company_scope(),
    ),
    Region: ScopeAdapter(
        Q(is_active=True, company__is_active=True),
        Q(company_id=OuterRef("company_id")) & (
            company_scope() | Q(region_id=OuterRef("pk"), service_center__isnull=True)
        ),
    ),
    ServiceCenter: ScopeAdapter(
        Q(is_active=True, company__is_active=True, region__is_active=True,
          region__company_id=F("company_id")),
        Q(company_id=OuterRef("company_id")) & (
            company_scope()
            | Q(region_id=OuterRef("region_id"), service_center__isnull=True, department__isnull=True)
            | Q(service_center_id=OuterRef("pk"))
        ),
    ),
    Department: ScopeAdapter(
        Q(is_active=True, company__is_active=True),
        Q(company_id=OuterRef("company_id")) & (
            company_scope() | Q(department_id=OuterRef("pk"))
        ),
    ),
    UserOrganizationAssignment: ScopeAdapter(
        assignment_is_valid(),
        Q(company_id=OuterRef("company_id"))
        & (Q(region__isnull=True) | Q(region_id=OuterRef("region_id")))
        & (Q(service_center__isnull=True) | Q(service_center_id=OuterRef("service_center_id")))
        & (Q(department__isnull=True) | Q(department_id=OuterRef("department_id"))),
    ),
}


def _assignment_reaches(model, **target):
    """Exists of one valid `model` row that the outer assignment contains.

    `ADAPTERS[model].containment` is written to filter an *assignment* while the
    candidate target supplies the `OuterRef` columns. Bulk capability resolution
    needs the quantifier order the other way round: the permission must be named
    as a row rather than as a query parameter, so the assignment becomes the outer
    row and each target becomes a correlated `Exists`. The clause below is the
    clause-for-clause inverse of `containment`; `tests/test_authorization_bulk.py`
    asserts the two agree for every actor, scope and permission.
    """
    return Exists(model._default_manager.filter(ADAPTERS[model].valid_target, **target))


def assignment_reaches(model):
    """Q on an assignment: it contains at least one valid `model` row."""
    if model is Company:
        return _assignment_reaches(Company, pk=OuterRef("company_id")) & company_scope()
    if model is Region:
        return (
            (_assignment_reaches(Region, company_id=OuterRef("company_id")) & company_scope())
            | (Q(service_center__isnull=True, department__isnull=True)
               & _assignment_reaches(Region, company_id=OuterRef("company_id"), pk=OuterRef("region_id")))
        )
    if model is ServiceCenter:
        return (
            (_assignment_reaches(ServiceCenter, company_id=OuterRef("company_id")) & company_scope())
            | (Q(service_center__isnull=True, department__isnull=True)
               & _assignment_reaches(ServiceCenter, company_id=OuterRef("company_id"), region_id=OuterRef("region_id")))
            | _assignment_reaches(ServiceCenter, company_id=OuterRef("company_id"),
                pk=OuterRef("service_center_id"))
        )
    if model is Department:
        return (
            (_assignment_reaches(Department, company_id=OuterRef("company_id")) & company_scope())
            | _assignment_reaches(Department, company_id=OuterRef("company_id"), pk=OuterRef("department_id"))
        )
    raise KeyError(model)
