from __future__ import annotations

from collections.abc import Callable, Iterable, Mapping, Sequence
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal, Protocol, cast, runtime_checkable

from govoplan_core.core.modules import AccessDecision
from govoplan_core.core.institutional import GovernedContextEnvelope


ACCESS_MODULE_ID = "access"
TENANCY_MODULE_ID = "tenancy"
POLICY_MODULE_ID = "policy"
AUDIT_MODULE_ID = "audit"

CAPABILITY_ACCESS_PRINCIPAL_RESOLVER = "access.principalResolver"
CAPABILITY_ACCESS_DIRECTORY = "access.directory"
CAPABILITY_ACCESS_PERMISSION_EVALUATOR = "access.permissionEvaluator"
CAPABILITY_ACCESS_RESOURCE_ACCESS = "access.resourceAccess"
CAPABILITY_ACCESS_SEMANTIC_DIRECTORY = "access.semanticDirectory"
CAPABILITY_ACCESS_EXPLANATION = "access.explanation"
CAPABILITY_ACCESS_TENANT_PROVISIONER = "access.tenantProvisioner"
CAPABILITY_ACCESS_ADMINISTRATION = "access.administration"
CAPABILITY_ACCESS_GOVERNANCE_MATERIALIZER = "access.governanceMaterializer"
CAPABILITY_TENANCY_TENANT_RESOLVER = "tenancy.tenantResolver"
CAPABILITY_AUDIT_SINK = "audit.sink"
CAPABILITY_AUDIT_RECORDER = "audit.recorder"
CAPABILITY_AUDIT_RETENTION = "audit.retention"

CAPABILITY_AUTH_API_PRINCIPAL_PROVIDER = "auth.apiPrincipalProvider"
CAPABILITY_AUTH_AUTOMATION_PRINCIPAL_PROVIDER = (
    "auth.automationPrincipalProvider"
)
CAPABILITY_AUTH_PRINCIPAL_RESOLVER = "auth.principalResolver"
CAPABILITY_AUTH_PERMISSION_EVALUATOR = "auth.permissionEvaluator"
CAPABILITY_AUTH_TENANT_CONTEXT_SWITCHER = "auth.tenantContextSwitcher"

ACCESS_CAPABILITY_NAMES = frozenset(
    {
        CAPABILITY_ACCESS_PRINCIPAL_RESOLVER,
        CAPABILITY_ACCESS_DIRECTORY,
        CAPABILITY_ACCESS_PERMISSION_EVALUATOR,
        CAPABILITY_ACCESS_RESOURCE_ACCESS,
        CAPABILITY_ACCESS_SEMANTIC_DIRECTORY,
        CAPABILITY_ACCESS_EXPLANATION,
        CAPABILITY_ACCESS_TENANT_PROVISIONER,
        CAPABILITY_ACCESS_ADMINISTRATION,
        CAPABILITY_ACCESS_GOVERNANCE_MATERIALIZER,
        CAPABILITY_TENANCY_TENANT_RESOLVER,
        CAPABILITY_AUDIT_SINK,
        CAPABILITY_AUDIT_RECORDER,
        CAPABILITY_AUDIT_RETENTION,
        CAPABILITY_AUTH_API_PRINCIPAL_PROVIDER,
        CAPABILITY_AUTH_AUTOMATION_PRINCIPAL_PROVIDER,
        CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
        CAPABILITY_AUTH_PERMISSION_EVALUATOR,
        CAPABILITY_AUTH_TENANT_CONTEXT_SWITCHER,
    }
)

AUTH_CAPABILITY_NAMES = frozenset(
    {
        CAPABILITY_AUTH_API_PRINCIPAL_PROVIDER,
        CAPABILITY_AUTH_AUTOMATION_PRINCIPAL_PROVIDER,
        CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
        CAPABILITY_AUTH_PERMISSION_EVALUATOR,
        CAPABILITY_AUTH_TENANT_CONTEXT_SWITCHER,
    }
)

DEFAULT_CAPABILITY_PROVIDERS: Mapping[str, str] = {
    CAPABILITY_AUTH_PRINCIPAL_RESOLVER: ACCESS_MODULE_ID,
    CAPABILITY_AUTH_PERMISSION_EVALUATOR: ACCESS_MODULE_ID,
    CAPABILITY_AUTH_TENANT_CONTEXT_SWITCHER: ACCESS_MODULE_ID,
}

AuthMethod = Literal["session", "api_key", "service_account"]
AccessSubjectKind = Literal[
    "identity",
    "account",
    "user",
    "group",
    "role",
    "tenant",
    "organization_unit",
    "function",
    "api_key",
    "service_account",
]
AccessStatus = Literal["active", "inactive", "suspended"]
PermissionLevel = Literal["system", "tenant"]
AuditOutcome = Literal["success", "failure", "denied", "unknown"]
FunctionAssignmentSource = Literal["direct", "delegated", "acting_for", "directory", "governance", "system"]
FunctionDelegationMode = Literal["delegate", "act_in_place"]
AccessProvenanceKind = Literal[
    "identity",
    "account",
    "tenant_membership",
    "resource",
    "owner",
    "share",
    "organization_unit",
    "function",
    "group",
    "role",
    "right",
    "delegation",
    "policy",
    "system_actor",
]


@dataclass(frozen=True, slots=True)
class PrincipalRef:
    account_id: str
    membership_id: str | None
    tenant_id: str | None
    identity_id: str | None = None
    scopes: frozenset[str] = field(default_factory=frozenset)
    group_ids: frozenset[str] = field(default_factory=frozenset)
    role_ids: frozenset[str] = field(default_factory=frozenset)
    function_assignment_ids: frozenset[str] = field(default_factory=frozenset)
    delegation_ids: frozenset[str] = field(default_factory=frozenset)
    auth_method: AuthMethod = "session"
    api_key_id: str | None = None
    session_id: str | None = None
    service_account_id: str | None = None
    acting_assignment_id: str | None = None
    acting_for_account_id: str | None = None
    email: str | None = None
    display_name: str | None = None

    def to_dict(self) -> dict[str, object]:
        return {
            "account_id": self.account_id,
            "membership_id": self.membership_id,
            "tenant_id": self.tenant_id,
            "identity_id": self.identity_id,
            "scopes": sorted(self.scopes),
            "group_ids": sorted(self.group_ids),
            "role_ids": sorted(self.role_ids),
            "function_assignment_ids": sorted(self.function_assignment_ids),
            "delegation_ids": sorted(self.delegation_ids),
            "auth_method": self.auth_method,
            "api_key_id": self.api_key_id,
            "session_id": self.session_id,
            "service_account_id": self.service_account_id,
            "acting_assignment_id": self.acting_assignment_id,
            "acting_for_account_id": self.acting_for_account_id,
            "email": self.email,
            "display_name": self.display_name,
        }

    @classmethod
    def from_mapping(cls, value: Mapping[str, object]) -> PrincipalRef:
        return cls(
            account_id=str(value["account_id"]),
            membership_id=_optional_str(value.get("membership_id")),
            tenant_id=_optional_str(value.get("tenant_id")),
            identity_id=_optional_str(value.get("identity_id")),
            scopes=_string_frozenset(value.get("scopes")),
            group_ids=_string_frozenset(value.get("group_ids")),
            role_ids=_string_frozenset(value.get("role_ids")),
            function_assignment_ids=_string_frozenset(value.get("function_assignment_ids")),
            delegation_ids=_string_frozenset(value.get("delegation_ids")),
            auth_method=cast(AuthMethod, str(value.get("auth_method") or "session")),
            api_key_id=_optional_str(value.get("api_key_id")),
            session_id=_optional_str(value.get("session_id")),
            service_account_id=_optional_str(value.get("service_account_id")),
            acting_assignment_id=_optional_str(value.get("acting_assignment_id")),
            acting_for_account_id=_optional_str(value.get("acting_for_account_id")),
            email=_optional_str(value.get("email")),
            display_name=_optional_str(value.get("display_name")),
        )


def _optional_str(value: object | None) -> str | None:
    return str(value) if value is not None else None


def _string_frozenset(value: object | None) -> frozenset[str]:
    if value is None:
        return frozenset()
    if isinstance(value, str):
        return frozenset({value})
    if isinstance(value, Iterable):
        return frozenset(str(item) for item in value)
    raise TypeError("PrincipalRef collection fields must be strings or iterables")


@dataclass(frozen=True, slots=True)
class AccountRef:
    id: str
    email: str
    display_name: str | None = None
    status: AccessStatus = "active"


@dataclass(frozen=True, slots=True)
class TenantRef:
    id: str
    name: str
    slug: str | None = None
    status: AccessStatus = "active"


@dataclass(frozen=True, slots=True)
class UserRef:
    id: str
    account_id: str
    tenant_id: str
    email: str | None = None
    display_name: str | None = None
    status: AccessStatus = "active"


@dataclass(frozen=True, slots=True)
class IdentityRef:
    id: str
    display_name: str | None = None
    primary_account_id: str | None = None
    account_ids: tuple[str, ...] = ()
    status: AccessStatus = "active"


@dataclass(frozen=True, slots=True)
class GroupRef:
    id: str
    tenant_id: str
    name: str
    status: AccessStatus = "active"


@dataclass(frozen=True, slots=True)
class RoleRef:
    id: str
    name: str
    level: PermissionLevel
    tenant_id: str | None = None
    managed: bool = False
    protected: bool = False


@dataclass(frozen=True, slots=True)
class OrganizationUnitRef:
    id: str
    tenant_id: str
    name: str
    parent_id: str | None = None
    status: AccessStatus = "active"


@dataclass(frozen=True, slots=True)
class FunctionRef:
    id: str
    tenant_id: str
    organization_unit_id: str
    slug: str
    name: str
    role_ids: tuple[str, ...] = ()
    delegable: bool = False
    act_in_place_allowed: bool = False
    status: AccessStatus = "active"


@dataclass(frozen=True, slots=True)
class FunctionAssignmentRef:
    id: str
    tenant_id: str
    account_id: str
    function_id: str
    organization_unit_id: str
    identity_id: str | None = None
    applies_to_subunits: bool = False
    source: FunctionAssignmentSource = "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: AccessStatus = "active"


@dataclass(frozen=True, slots=True)
class FunctionDelegationRef:
    id: str
    tenant_id: str
    function_assignment_id: str
    delegator_account_id: str
    delegate_account_id: str
    mode: FunctionDelegationMode = "delegate"
    reason: str | None = None
    valid_from: datetime | None = None
    valid_until: datetime | None = None
    status: AccessStatus = "active"


@dataclass(frozen=True, slots=True)
class AccessDecisionProvenance:
    kind: AccessProvenanceKind
    id: str | None = None
    label: str | None = None
    tenant_id: str | None = None
    source: str | None = None
    details: Mapping[str, object] = field(default_factory=dict)

    def to_dict(self) -> dict[str, object]:
        return {
            "kind": self.kind,
            "id": self.id,
            "label": self.label,
            "tenant_id": self.tenant_id,
            "source": self.source,
            "details": dict(self.details),
        }


@dataclass(frozen=True, slots=True)
class AccessSubjectRef:
    kind: AccessSubjectKind
    id: str
    tenant_id: str | None = None
    label: str | None = None


@dataclass(frozen=True, slots=True)
class TenantOwnerCandidateRef:
    account_id: str
    email: str
    display_name: str | None = None


@dataclass(frozen=True, slots=True)
class CreatedApiKeyRef:
    id: str
    secret: str


@dataclass(frozen=True, slots=True)
class DevelopmentBootstrapRef:
    user: UserRef
    created_api_key: CreatedApiKeyRef | None = None


@dataclass(frozen=True, slots=True)
class TenantContextSwitchRef:
    account_id: str
    membership_id: str
    tenant_id: str
    session_id: str | None = None


@dataclass(frozen=True, slots=True)
class GovernanceTemplateMaterialization:
    template_id: str
    kind: Literal["group", "role"]
    tenant_id: str
    slug: str
    name: str
    description: str | None = None
    permissions: tuple[str, ...] = ()
    is_active: bool = True
    required: bool = False


@dataclass(frozen=True, slots=True)
class AuditEvent:
    event_type: str
    action: str
    outcome: AuditOutcome = "unknown"
    actor: PrincipalRef | None = None
    scope: Literal["tenant", "system"] = "tenant"
    tenant_id: str | None = None
    user_id: str | None = None
    api_key_id: str | None = None
    resource_type: str | None = None
    resource_id: str | None = None
    occurred_at: datetime | None = None
    correlation_id: str | None = None
    causation_id: str | None = None
    institutional_context: GovernedContextEnvelope | None = None
    details: Mapping[str, object] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class AuditRecordRef:
    id: str
    scope: str
    tenant_id: str | None
    user_id: str | None
    api_key_id: str | None
    action: str
    object_type: str | None = None
    object_id: str | None = None
    details: Mapping[str, object] = field(default_factory=dict)
    created_at: datetime | None = None


@runtime_checkable
class PrincipalResolver(Protocol):
    def resolve_request(self, request: object, *, session: object | None = None) -> PrincipalRef:
        ...


@runtime_checkable
class ApiPrincipalProvider(Protocol):
    def resolve_api_principal(
        self,
        request: object,
        session: object,
        *,
        authorization: str | None = None,
        x_api_key: str | None = None,
    ) -> object:
        ...


@runtime_checkable
class TenantContextSwitcher(Protocol):
    def switch_tenant_context(self, session: object, *, principal: object, tenant_id: str) -> TenantContextSwitchRef:
        ...


@runtime_checkable
class TenantResolver(Protocol):
    def get_tenant(self, tenant_id: str) -> TenantRef | None:
        ...

    def current_tenant(self, principal: PrincipalRef) -> TenantRef | None:
        ...


@runtime_checkable
class AccessDirectory(Protocol):
    def get_account(self, account_id: str) -> AccountRef | None:
        ...

    def get_user(self, user_id: str) -> UserRef | None:
        ...

    def get_users(self, user_ids: Iterable[str]) -> Mapping[str, UserRef]:
        ...

    def users_for_tenant(self, tenant_id: str) -> Sequence[UserRef]:
        ...

    def get_group(self, group_id: str) -> GroupRef | None:
        ...

    def get_groups(self, group_ids: Iterable[str]) -> Mapping[str, GroupRef]:
        ...

    def groups_for_tenant(self, tenant_id: str) -> Sequence[GroupRef]:
        ...

    def groups_for_user(self, user_id: str, *, tenant_id: str) -> Sequence[GroupRef]:
        ...

    def display_label(self, subject: AccessSubjectRef) -> str | None:
        ...


@runtime_checkable
class AccessSemanticDirectory(AccessDirectory, Protocol):
    def get_identity(self, identity_id: str) -> IdentityRef | None:
        ...

    def accounts_for_identity(self, identity_id: str) -> Sequence[AccountRef]:
        ...

    def get_organization_unit(self, organization_unit_id: str) -> OrganizationUnitRef | None:
        ...

    def organization_units_for_tenant(self, tenant_id: str) -> Sequence[OrganizationUnitRef]:
        ...

    def get_function(self, function_id: str) -> FunctionRef | None:
        ...

    def functions_for_organization_unit(
        self,
        organization_unit_id: str,
        *,
        include_subunits: bool = False,
    ) -> Sequence[FunctionRef]:
        ...

    def get_function_assignment(self, assignment_id: str) -> FunctionAssignmentRef | None:
        ...

    def function_assignments_for_account(
        self,
        account_id: str,
        *,
        tenant_id: str | None = None,
    ) -> Sequence[FunctionAssignmentRef]:
        ...


@runtime_checkable
class PermissionEvaluator(Protocol):
    def scopes_grant(self, scopes: Iterable[str], required_scope: str) -> bool:
        ...

    def has_scope(self, principal: PrincipalRef, required_scope: str) -> bool:
        ...

    def explain_scope(self, principal: PrincipalRef, required_scope: str) -> AccessDecision:
        ...


@runtime_checkable
class ResourceAccessService(Protocol):
    def explain(self, principal: PrincipalRef, *, resource_type: str, resource_id: str, action: str) -> AccessDecision:
        ...


@runtime_checkable
class ResourceAccessExplanationProvider(Protocol):
    def explain_resource_provenance(
        self,
        session: object,
        principal: PrincipalRef,
        *,
        resource_type: str,
        resource_id: str,
        action: str,
    ) -> Sequence[AccessDecisionProvenance]:
        ...


@runtime_checkable
class AccessExplanationService(Protocol):
    def explain_scope_provenance(
        self,
        principal: PrincipalRef,
        required_scope: str,
    ) -> Sequence[AccessDecisionProvenance]:
        ...

    def explain_resource_provenance(
        self,
        principal: PrincipalRef,
        *,
        resource_type: str,
        resource_id: str,
        action: str,
    ) -> Sequence[AccessDecisionProvenance]:
        ...


@runtime_checkable
class TenantAccessProvisioner(Protocol):
    def ensure_default_roles(self, session: object, tenant: object | None = None) -> Mapping[str, object]:
        ...

    def tenant_owner_candidates(self, session: object) -> Sequence[TenantOwnerCandidateRef]:
        ...

    def ensure_tenant_owner_membership(
        self,
        session: object,
        *,
        tenant: object,
        owner_account_id: str,
    ) -> str:
        ...

    def ensure_development_admin(
        self,
        session: object,
        *,
        tenant: object,
        user_email: str,
        user_password: str,
        api_key_secret: str | None,
        scopes: Sequence[str],
    ) -> DevelopmentBootstrapRef:
        ...


@runtime_checkable
class AccessAdministration(Protocol):
    def tenant_counts(self, session: object, tenant_id: str) -> Mapping[str, int]:
        ...

    def system_account_count(self, session: object) -> int:
        ...

    def role_count_for_tenant(self, session: object, tenant_id: str) -> int:
        ...

    def active_api_key_count_for_tenant(self, session: object, tenant_id: str) -> int:
        ...

    def actor_email_by_user_id(self, session: object, user_ids: Iterable[str]) -> Mapping[str, str | None]:
        ...

    def user_ids_for_actor_filter(self, session: object, *, operator: str, value: str) -> Sequence[str]:
        ...

    def user_settings(self, session: object, user_id: str, *, tenant_id: str) -> Mapping[str, object] | None:
        ...

    def set_user_settings(
        self,
        session: object,
        user_id: str,
        *,
        tenant_id: str,
        settings: Mapping[str, object],
    ) -> Mapping[str, object] | None:
        ...

    def group_settings(self, session: object, group_id: str, *, tenant_id: str) -> Mapping[str, object] | None:
        ...

    def set_group_settings(
        self,
        session: object,
        group_id: str,
        *,
        tenant_id: str,
        settings: Mapping[str, object],
    ) -> Mapping[str, object] | None:
        ...


@runtime_checkable
class AccessGovernanceMaterializer(Protocol):
    def sync_template(self, session: object, template: GovernanceTemplateMaterialization) -> None:
        ...

    def remove_template(self, session: object, template: GovernanceTemplateMaterialization) -> None:
        ...


@runtime_checkable
class AuditSink(Protocol):
    def record(self, event: AuditEvent) -> None:
        ...


@runtime_checkable
class AuditRecorder(Protocol):
    def record_event(self, session: object, event: AuditEvent) -> AuditRecordRef:
        ...


@runtime_checkable
class AuditRetentionProvider(Protocol):
    def apply_detail_retention(
        self,
        session: object,
        *,
        dry_run: bool,
        now: datetime,
        policy_for_record: Callable[[AuditRecordRef], object],
    ) -> Mapping[str, int]:
        ...
