from __future__ import annotations

from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from datetime import datetime
from typing import Literal, Protocol, runtime_checkable


IDM_MODULE_ID = "idm"
CAPABILITY_IDM_DIRECTORY = "idm.directory"
CAPABILITY_IDM_FUNCTION_ASSIGNMENTS = "idm.function_assignments"
CAPABILITY_IDM_ASSIGNMENT_LIFECYCLE = "idm.assignment_lifecycle"

IdmStatus = Literal["active", "inactive", "suspended"]
OrganizationFunctionAssignmentSource = Literal["direct", "delegated", "acting_for", "directory", "governance", "system"]


@dataclass(frozen=True, slots=True)
class OrganizationFunctionAssignmentRef:
    id: str
    tenant_id: str
    identity_id: str
    function_id: str
    organization_unit_id: str
    account_id: str | None = None
    applies_to_subunits: bool = False
    source: OrganizationFunctionAssignmentSource = "direct"
    delegated_from_assignment_id: str | None = None
    acting_for_account_id: str | None = None
    valid_from: datetime | None = None
    valid_until: datetime | None = None
    status: IdmStatus = "active"


@dataclass(frozen=True, slots=True)
class OrganizationFunctionIncumbencyRef:
    tenant_id: str
    function_id: str
    assignments: tuple[OrganizationFunctionAssignmentRef, ...] = ()
    function_active: bool = True

    @property
    def vacant(self) -> bool:
        return not self.assignments


@runtime_checkable
class IdmDirectory(Protocol):
    def get_organization_function_assignment(self, assignment_id: str) -> OrganizationFunctionAssignmentRef | None:
        ...

    def organization_function_assignments_for_identity(
        self,
        identity_id: str,
        *,
        tenant_id: str | None = None,
        effective_at: datetime | None = None,
    ) -> Sequence[OrganizationFunctionAssignmentRef]:
        ...

    def organization_function_assignments_for_account(
        self,
        account_id: str,
        *,
        tenant_id: str | None = None,
        effective_at: datetime | None = None,
    ) -> Sequence[OrganizationFunctionAssignmentRef]:
        ...

    def organization_function_assignments_for_identities(
        self,
        identity_ids: Sequence[str],
        *,
        tenant_id: str | None = None,
        effective_at: datetime | None = None,
    ) -> Mapping[str, Sequence[OrganizationFunctionAssignmentRef]]:
        ...

    def organization_function_assignments_for_accounts(
        self,
        account_ids: Sequence[str],
        *,
        tenant_id: str | None = None,
        effective_at: datetime | None = None,
    ) -> Mapping[str, Sequence[OrganizationFunctionAssignmentRef]]:
        ...


@runtime_checkable
class IdmFunctionAssignmentDirectory(Protocol):
    """Reverse lookup for effective incumbency and vacancy decisions."""

    def organization_function_assignments_for_function(
        self,
        function_id: str,
        *,
        tenant_id: str | None = None,
        effective_at: datetime | None = None,
    ) -> Sequence[OrganizationFunctionAssignmentRef]:
        ...

    def organization_function_incumbencies(
        self,
        function_ids: Sequence[str],
        *,
        tenant_id: str,
        effective_at: datetime | None = None,
    ) -> Mapping[str, OrganizationFunctionIncumbencyRef]:
        ...


@runtime_checkable
class IdmAssignmentLifecycle(Protocol):
    """Worker boundary for time-driven function-assignment transitions."""

    def process_expired(
        self,
        session: object,
        *,
        tenant_id: str | None = None,
        effective_at: datetime | None = None,
        limit: int = 100,
    ) -> Mapping[str, object]:
        ...


def idm_assignment_lifecycle(
    registry: object | None,
) -> IdmAssignmentLifecycle | None:
    if (
        registry is None
        or not hasattr(registry, "has_capability")
        or not hasattr(registry, "capability")
        or not registry.has_capability(CAPABILITY_IDM_ASSIGNMENT_LIFECYCLE)
    ):
        return None
    capability = registry.capability(CAPABILITY_IDM_ASSIGNMENT_LIFECYCLE)
    return (
        capability
        if isinstance(capability, IdmAssignmentLifecycle)
        else None
    )
