from __future__ import annotations

from collections.abc import Callable, Iterable, Mapping, Sequence
from dataclasses import dataclass, field
from typing import Any, Literal, Protocol, TYPE_CHECKING

from govoplan_core.core.ownership import OwnershipProviderRegistration
from govoplan_core.core.provider_governance import (
    ExternalProviderDeclaration,
    ExternalProviderStateProviderRegistration,
    ModuleArchitectureDeclaration,
)
from govoplan_core.core.views import ViewSurface

if TYPE_CHECKING:
    from fastapi import APIRouter
    from govoplan_core.core.operations import OperationalCheckProviderRegistration
    from govoplan_core.core.search import (
        SearchProviderRegistration,
        SearchSourceProviderRegistration,
    )
    from govoplan_core.core.workflows import WorkflowDefinitionContribution


SUPPORTED_MANIFEST_CONTRACT_VERSION = "1"
SUPPORTED_FRONTEND_ASSET_MANIFEST_CONTRACT_VERSION = "1"

PermissionLevel = Literal["system", "tenant"]
SubjectType = Literal["account", "membership", "group", "service_account", "tenant"]
MigrationTaskPhase = Literal[
    "pre_migration_check",
    "pre_migration_prepare",
    "post_migration_backfill",
    "post_migration_verify",
]
MigrationTaskSafety = Literal["automatic", "requires_review", "forward_only", "destructive"]
MigrationTaskStatus = Literal["ok", "warning", "blocked", "skipped"]


@dataclass(frozen=True, slots=True)
class PermissionDefinition:
    scope: str
    label: str
    description: str
    category: str
    level: PermissionLevel
    module_id: str
    resource: str
    action: str
    deprecated: bool = False


@dataclass(frozen=True, slots=True)
class RoleTemplate:
    slug: str
    name: str
    description: str
    permissions: tuple[str, ...]
    level: PermissionLevel = "tenant"
    managed: bool = True
    protected: bool = False
    default_authenticated: bool = False


@dataclass(frozen=True, slots=True)
class NavItem:
    path: str
    label: str
    icon: str | None = None
    section: str | None = None
    required_all: tuple[str, ...] = ()
    required_any: tuple[str, ...] = ()
    order: int = 100
    surface_id: str | None = None




@dataclass(frozen=True, slots=True)
class FrontendRoute:
    path: str
    component: str
    required_all: tuple[str, ...] = ()
    required_any: tuple[str, ...] = ()
    order: int = 100
    surface_id: str | None = None


@dataclass(frozen=True, slots=True)
class PublicFrontendRoute:
    """Explicitly allowlisted route that can render without authentication."""

    path: str
    component: str
    order: int = 100


@dataclass(frozen=True, slots=True)
class FrontendModule:
    module_id: str
    package_name: str | None = None
    asset_manifest: str | None = None
    asset_manifest_signature: str | None = None
    asset_manifest_public_key_id: str | None = None
    asset_manifest_integrity: str | None = None
    asset_manifest_contract_version: str = "1"
    routes: tuple[FrontendRoute, ...] = ()
    public_routes: tuple[PublicFrontendRoute, ...] = ()
    nav_items: tuple[NavItem, ...] = ()
    settings_routes: tuple[FrontendRoute, ...] = ()
    view_surfaces: tuple[ViewSurface, ...] = ()


@dataclass(frozen=True, slots=True)
class MigrationRetirementPlan:
    supported: bool
    summary: str
    warnings: tuple[str, ...] = ()
    blocking_reason: str | None = None
    destroy_data_supported: bool = False
    destroy_data_summary: str | None = None
    destroy_data_warnings: tuple[str, ...] = ()
    destroy_data_executor: MigrationRetirementExecutor | None = None


MigrationRetirementExecutor = Callable[[object, str], None]
MigrationRetirementProvider = Callable[[object | None, str], MigrationRetirementPlan]


@dataclass(frozen=True, slots=True)
class ModuleMigrationTaskContext:
    module_id: str
    task_id: str
    phase: MigrationTaskPhase
    database_url: str
    target_version: str | None = None
    session: object | None = None
    dry_run: bool = False
    metadata: Mapping[str, Any] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class ModuleMigrationTaskResult:
    status: MigrationTaskStatus = "ok"
    message: str | None = None
    details: Mapping[str, Any] = field(default_factory=dict)


ModuleMigrationTaskExecutor = Callable[[ModuleMigrationTaskContext], ModuleMigrationTaskResult | None]


@dataclass(frozen=True, slots=True)
class ModuleMigrationTask:
    task_id: str
    phase: MigrationTaskPhase
    summary: str
    task_version: str = "1"
    safety: MigrationTaskSafety = "automatic"
    idempotent: bool = True
    timeout_seconds: int | None = None
    executor: ModuleMigrationTaskExecutor | None = None
    metadata: Mapping[str, Any] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class MigrationSpec:
    module_id: str
    metadata: object | None = None
    script_location: str | None = None
    migration_after: tuple[str, ...] = ()
    migration_before: tuple[str, ...] = ()
    migration_tasks: tuple[ModuleMigrationTask, ...] = ()
    retirement_supported: bool = False
    retirement_provider: MigrationRetirementProvider | None = None
    retirement_notes: str | None = None


@dataclass(frozen=True, slots=True)
class ModuleCompatibility:
    core_version_min: str | None = None
    core_version_max: str | None = None
    manifest_contract_version: str = "1"


@dataclass(frozen=True, slots=True)
class ModuleInterfaceProvider:
    name: str
    version: str


@dataclass(frozen=True, slots=True)
class ModuleInterfaceRequirement:
    name: str
    version_min: str | None = None
    version_max_exclusive: str | None = None
    optional: bool = False


@dataclass(frozen=True, slots=True)
class ModuleUninstallGuardResult:
    severity: Literal["blocker", "warning", "info"]
    code: str
    message: str


UninstallGuardProvider = Callable[[object | None, str], Iterable[ModuleUninstallGuardResult]]


@dataclass(frozen=True, slots=True)
class AccessDecision:
    allowed: bool
    reason: str | None = None
    requirements: tuple[str, ...] = ()


@dataclass(frozen=True, slots=True)
class ModuleContext:
    registry: object
    settings: object
    data: Mapping[str, Any] = field(default_factory=dict)


DocumentationLayer = Literal["always", "configured", "available", "evidence"]
DocumentationLinkKind = Literal["runtime", "api", "repository", "wiki", "public"]
DocumentationType = Literal["admin", "user"]
DocumentationConfigurationState = Literal["enabled", "disabled", "inherited", "unavailable"]
DocumentationSourceKind = Literal[
    "manifest",
    "route",
    "capability",
    "policy",
    "release_catalog",
    "configuration_package",
    "wiki",
    "repository",
]
DocumentationSourceState = Literal["configured", "disabled", "unavailable"]
CapabilityStability = Literal["experimental", "stable", "deprecated"]


@dataclass(frozen=True, slots=True)
class DocumentationLink:
    label: str
    href: str
    kind: DocumentationLinkKind = "runtime"


@dataclass(frozen=True, slots=True)
class DocumentationCondition:
    required_modules: tuple[str, ...] = ()
    any_modules: tuple[str, ...] = ()
    missing_modules: tuple[str, ...] = ()
    required_capabilities: tuple[str, ...] = ()
    required_scopes: tuple[str, ...] = ()
    any_scopes: tuple[str, ...] = ()
    configuration_keys: tuple[str, ...] = ()


@dataclass(frozen=True, slots=True)
class DocumentationTopic:
    id: str
    title: str
    summary: str
    body: str = ""
    layer: DocumentationLayer = "configured"
    documentation_types: tuple[DocumentationType, ...] = ("admin",)
    audience: tuple[str, ...] = ()
    order: int = 100
    conditions: tuple[DocumentationCondition, ...] = ()
    links: tuple[DocumentationLink, ...] = ()
    related_modules: tuple[str, ...] = ()
    unlocks: tuple[str, ...] = ()
    configuration_keys: tuple[str, ...] = ()
    i18n_key: str | None = None
    translations: Mapping[str, Mapping[str, str]] = field(default_factory=dict)
    source_module_id: str | None = None
    version_min: str | None = None
    version_max_exclusive: str | None = None
    metadata: Mapping[str, Any] = field(default_factory=dict)


def user_workflow_scope_condition_issues(topic: DocumentationTopic) -> tuple[str, ...]:
    """Return fail-closed authoring issues for a user-facing workflow topic.

    Topic conditions are alternatives.  Every alternative therefore needs an
    explicit scope constraint; otherwise one unscoped alternative would make
    the workflow visible regardless of the actor's task authority.
    """

    raw_kind = topic.metadata.get("kind")
    kind = raw_kind.strip().lower().replace("_", "-") if isinstance(raw_kind, str) else ""
    if kind != "workflow" or "user" not in topic.documentation_types:
        return ()
    if not topic.conditions:
        return ("user workflow topics must declare at least one scope-conditioned alternative",)

    unscoped_alternatives = tuple(
        index
        for index, condition in enumerate(topic.conditions, start=1)
        if not any(scope.strip() for scope in (*condition.required_scopes, *condition.any_scopes))
    )
    if not unscoped_alternatives:
        return ()
    alternatives = ", ".join(str(index) for index in unscoped_alternatives)
    return (
        "every user workflow condition alternative must declare required_scopes or any_scopes; "
        f"unscoped alternative(s): {alternatives}",
    )


@dataclass(frozen=True, slots=True)
class DocumentationContext:
    registry: object
    principal: object | None = None
    settings: object | None = None
    session: object | None = None
    documentation_type: DocumentationType = "admin"
    locale: str = "en"
    data: Mapping[str, Any] = field(default_factory=dict)


@dataclass(frozen=True, slots=True)
class DocumentationConfigurationDecision:
    key: str
    state: DocumentationConfigurationState
    source: str | None = None
    reason: str | None = None


DocumentationConfigurationResolver = Callable[
    [DocumentationContext, tuple[str, ...]],
    Mapping[str, DocumentationConfigurationDecision],
]


@dataclass(frozen=True, slots=True)
class DocumentationConfigurationProviderRegistration:
    keys: tuple[str, ...]
    resolve: DocumentationConfigurationResolver


@dataclass(frozen=True, slots=True)
class DocumentationSourceDefinition:
    id: str
    kind: DocumentationSourceKind
    label: str
    documentation_types: tuple[DocumentationType, ...] = ("admin",)
    condition: DocumentationCondition = field(default_factory=DocumentationCondition)
    state: DocumentationSourceState = "configured"
    state_reason: str | None = None
    provenance: Mapping[str, Any] = field(default_factory=dict)
    inspection: Mapping[str, Any] = field(default_factory=dict)
    link: DocumentationLink | None = None
    configuration_key: str | None = None


@dataclass(frozen=True, slots=True)
class CapabilityDocumentation:
    """Provider-owned capability metadata safe for generic platform consumers."""

    label: str
    summary: str
    contract_version: str | None = None
    stability: CapabilityStability = "stable"
    documentation_types: tuple[DocumentationType, ...] = ("admin",)
    audience: tuple[str, ...] = ()


class ResourceAclProvider(Protocol):
    resource_type: str

    def can_read(self, principal: object, resource_id: str) -> bool:
        ...

    def can_write(self, principal: object, resource_id: str) -> bool:
        ...

    def explain(self, principal: object, resource_id: str) -> AccessDecision:
        ...


TenantSummaryProvider = Callable[[object, str], Mapping[str, int]]
TenantSummaryBatchProvider = Callable[
    [object, Sequence[str]],
    Mapping[str, Mapping[str, int]],
]


@dataclass(frozen=True, slots=True)
class DeleteVetoIssue:
    severity: Literal["blocker", "warning", "info"]
    code: str
    message: str
    module_id: str | None = None
    details: Mapping[str, Any] = field(default_factory=dict)


DeleteVetoProviderResult = DeleteVetoIssue | Iterable[DeleteVetoIssue] | None
DeleteVetoProvider = Callable[[object, str, str], DeleteVetoProviderResult]


@dataclass(frozen=True, slots=True)
class DeleteVetoProviderRegistration:
    module_id: str
    resource_type: str
    provider: DeleteVetoProvider


RouteFactory = Callable[[ModuleContext], "APIRouter"]
CapabilityFactory = Callable[[ModuleContext], object]
DocumentationProvider = Callable[[DocumentationContext], Iterable[DocumentationTopic]]
LifecycleHook = Callable[[ModuleContext], None]


@dataclass(frozen=True, slots=True)
class ModuleManifest:
    id: str
    name: str
    version: str
    dependencies: tuple[str, ...] = ()
    optional_dependencies: tuple[str, ...] = ()
    required_capabilities: tuple[str, ...] = ()
    optional_capabilities: tuple[str, ...] = ()
    provides_interfaces: tuple[ModuleInterfaceProvider, ...] = ()
    requires_interfaces: tuple[ModuleInterfaceRequirement, ...] = ()
    permissions: tuple[PermissionDefinition, ...] = ()
    role_templates: tuple[RoleTemplate, ...] = ()
    route_factory: RouteFactory | None = None
    migration_spec: MigrationSpec | None = None
    nav_items: tuple[NavItem, ...] = ()
    frontend: FrontendModule | None = None
    resource_acl_providers: tuple[ResourceAclProvider, ...] = ()
    ownership_providers: tuple[OwnershipProviderRegistration, ...] = ()
    tenant_summary_providers: tuple[TenantSummaryProvider, ...] = ()
    tenant_summary_batch_providers: tuple[TenantSummaryBatchProvider, ...] = ()
    delete_veto_providers: Mapping[str, Sequence[DeleteVetoProvider]] = field(default_factory=dict)
    uninstall_guard_providers: tuple[UninstallGuardProvider, ...] = ()
    capability_factories: Mapping[str, CapabilityFactory] = field(default_factory=dict)
    capability_documentation: Mapping[str, CapabilityDocumentation] = field(default_factory=dict)
    search_providers: tuple["SearchProviderRegistration", ...] = ()
    search_sources: tuple["SearchSourceProviderRegistration", ...] = ()
    operational_check_providers: tuple[
        "OperationalCheckProviderRegistration",
        ...,
    ] = ()
    architecture: ModuleArchitectureDeclaration | None = None
    external_providers: tuple[ExternalProviderDeclaration, ...] = ()
    external_provider_state_providers: tuple[
        ExternalProviderStateProviderRegistration,
        ...,
    ] = ()
    compatibility: ModuleCompatibility = field(default_factory=ModuleCompatibility)
    on_activate: LifecycleHook | None = None
    on_deactivate: LifecycleHook | None = None
    documentation: tuple[DocumentationTopic, ...] = ()
    documentation_providers: tuple[DocumentationProvider, ...] = ()
    documentation_configuration_providers: tuple[
        DocumentationConfigurationProviderRegistration,
        ...,
    ] = ()
    documentation_sources: tuple[DocumentationSourceDefinition, ...] = ()
    # A renamed or extracted module may continue to own an established
    # permission namespace. This keeps persisted grants stable while the
    # runtime module ID changes.
    permission_namespace: str | None = None
    workflow_definitions: tuple["WorkflowDefinitionContribution", ...] = ()
