from rest_framework.exceptions import PermissionDenied


def get_active_organization(request):
    """Resolve the caller's current Organization.

    Uses request.session['active_org_id'] if set and the user is still a
    member; otherwise falls back to their first membership (this covers
    every user today, since each has exactly one org until the org-switcher
    UI ships). Raises PermissionDenied if they have no membership at all.
    """
    from apps.companies.models import Organization

    user = request.user
    active_org_id = request.session.get("active_org_id")
    if active_org_id:
        org = Organization.objects.filter(pk=active_org_id, members=user).first()
        if org:
            return org

    org = Organization.objects.filter(members=user).order_by("memberships__created_at").first()
    if org is None:
        raise PermissionDenied("User is not a member of any organization.")
    return org


def assert_same_organization(organization, *objs):
    """Raise PermissionDenied if any non-null obj belongs to a different org.

    Guards writable FK fields on a serializer (e.g. `account`, `vendor`,
    `invoice`) that DRF validates for existence but not tenant ownership.
    """
    for obj in objs:
        if obj is not None and obj.organization_id != organization.id:
            raise PermissionDenied(f"{obj.__class__.__name__} is outside your organization.")


def validate_same_organization_field(serializer, value):
    """Field-level validator: raise DRF's own "does not exist" shape if
    `value` belongs to a different organization than the request's active one.

    Use as `validate_<field> = lambda self, value: validate_same_organization_field(self, value)`
    or call directly from a `validate_<field>(self, value)` method. Requires
    `request` in the serializer's context — raises `AssertionError` if it's
    missing rather than silently skipping the check, since a missing request
    context is itself the bug this guards against (see PERF/BE-002 audit:
    several nested-action call sites omitted `context={"request": request}`
    entirely, which would make a context-optional check silently no-op).

    Indistinguishable from a genuinely invalid PK: a caller cannot use this
    to probe whether an ID exists in another organization.
    """
    from rest_framework import serializers as drf_serializers

    request = serializer.context.get("request")
    assert request is not None, (
        f"{serializer.__class__.__name__} must receive request in its "
        "context for cross-organization FK validation to run."
    )
    if value is not None and value.organization_id != get_active_organization(request).id:
        raise drf_serializers.ValidationError(
            f'Invalid pk "{value.pk}" - object does not exist.',
            code="does_not_exist",
        )
    return value


class TenantScopedMixin:
    """Filters queryset and tags new rows with the caller's active Organization.

    Apply to ViewSets whose model has a direct `organization` FK. For a
    child model scoped only via a parent's organization FK (no column of
    its own), set `organization_lookup = "parent__organization"` — the
    queryset filter joins through it, but perform_create still expects
    `organization` to be settable on the model (skip mixin perform_create /
    override it if the child has no such field to write).

    For ViewSets that already build a custom get_queryset() without calling
    super(), filter by get_active_organization(self.request) inline instead
    — this mixin only composes through the super() chain.
    """

    organization_lookup = "organization"

    def get_queryset(self):
        qs = super().get_queryset()
        return qs.filter(**{self.organization_lookup: get_active_organization(self.request)})

    def perform_create(self, serializer):
        serializer.save(organization=get_active_organization(self.request))
