from __future__ import annotations

from dataclasses import dataclass
from typing import Iterable

from govoplan_core.security.scope_aliases import LEGACY_SCOPE_ALIASES


@dataclass(frozen=True, slots=True)
class PermissionDefinition:
    scope: str
    label: str
    description: str
    category: str
    level: str = "tenant"


TENANT_PERMISSIONS: tuple[PermissionDefinition, ...] = (
    PermissionDefinition("campaign:read", "View campaigns", "Open campaign metadata, versions and permitted message summaries.", "Campaigns"),
    PermissionDefinition("campaign:create", "Create campaigns", "Create new campaigns in an accessible tenant scope.", "Campaigns"),
    PermissionDefinition("campaign:update", "Edit campaigns", "Edit current working campaign versions.", "Campaigns"),
    PermissionDefinition("campaign:copy", "Copy campaigns", "Create a new campaign or working version from an existing campaign.", "Campaigns"),
    PermissionDefinition("campaign:archive", "Archive campaigns", "Archive campaigns without destroying audit evidence.", "Campaigns"),
    PermissionDefinition("campaign:delete", "Delete campaigns", "Delete draft-only campaigns where retention policy allows it.", "Campaigns"),
    PermissionDefinition("campaign:share", "Share campaigns", "Grant or revoke explicit campaign access for users and groups.", "Campaigns"),
    PermissionDefinition("campaign:validate", "Validate campaigns", "Run technical validation and manage validation locks.", "Campaigns"),
    PermissionDefinition("campaign:build", "Build campaigns", "Build exact messages and attachment evidence.", "Campaigns"),
    PermissionDefinition("campaign:review", "Approve campaign review", "Approve or reject built messages and non-blocking review conditions.", "Campaigns"),
    PermissionDefinition("campaign:send_test", "Mock-send campaigns", "Use mock delivery and verification tools.", "Campaigns"),
    PermissionDefinition("campaign:queue", "Queue campaigns", "Place approved executions into the delivery queue.", "Campaigns"),
    PermissionDefinition("campaign:control", "Control delivery", "Pause, resume or cancel queued/sending jobs.", "Campaigns"),
    PermissionDefinition("campaign:send", "Send campaigns", "Start real SMTP delivery.", "Campaigns"),
    PermissionDefinition("campaign:retry", "Retry delivery", "Retry failed or unattempted delivery jobs.", "Campaigns"),
    PermissionDefinition("campaign:reconcile", "Reconcile uncertain delivery", "Resolve outcome-unknown SMTP attempts after operational inspection.", "Campaigns"),
    PermissionDefinition("recipients:read", "View recipients", "Read recipient lists and recipient-specific campaign data.", "Recipients"),
    PermissionDefinition("recipients:write", "Edit recipients", "Create and edit recipient rows and recipient field values.", "Recipients"),
    PermissionDefinition("recipients:import", "Import recipients", "Bulk-import recipient lists from files or reusable lists.", "Recipients"),
    PermissionDefinition("recipients:export", "Export recipients", "Export recipient data or recipient-complete reports.", "Recipients"),
    PermissionDefinition("files:read", "View files", "List, search and preview managed files.", "Files"),
    PermissionDefinition("files:download", "Download files", "Download managed files and generated ZIP archives.", "Files"),
    PermissionDefinition("files:upload", "Upload files", "Upload new managed file versions.", "Files"),
    PermissionDefinition("files:organize", "Organize files", "Create folders, rename, move or copy managed files.", "Files"),
    PermissionDefinition("files:share", "Share files", "Grant or revoke managed file shares.", "Files"),
    PermissionDefinition("files:delete", "Delete files", "Delete or hide managed files and folders where retention policy allows it.", "Files"),
    PermissionDefinition("files:admin", "Administer file spaces", "Access and administer all user and group file spaces in the tenant.", "Files"),
    PermissionDefinition("reports:read", "View reports", "View campaign delivery reports and aggregate outcome data.", "Reports and audit"),
    PermissionDefinition("reports:export", "Export reports", "Download detailed campaign reports such as CSV exports.", "Reports and audit"),
    PermissionDefinition("reports:send", "Send reports", "Email campaign reports to configured recipients.", "Reports and audit"),
    PermissionDefinition("audit:read", "View audit log", "Read tenant and campaign audit records.", "Reports and audit"),
    PermissionDefinition("mail_servers:read", "View mail servers", "Inspect approved reusable SMTP/IMAP server profile metadata.", "Mail servers"),
    PermissionDefinition("mail_servers:use", "Use mail servers", "Select an approved mail-server profile for a campaign without reading secrets.", "Mail servers"),
    PermissionDefinition("mail_servers:test", "Test mail servers", "Run SMTP/IMAP connection tests.", "Mail servers"),
    PermissionDefinition("mail_servers:write", "Manage mail servers", "Create and edit reusable mail-server profiles.", "Mail servers"),
    PermissionDefinition("mail_servers:manage_credentials", "Manage mail credentials", "Create or replace stored SMTP/IMAP credentials.", "Mail servers"),
    PermissionDefinition("admin:users:read", "View users", "List tenant users and their effective access.", "Tenant administration"),
    PermissionDefinition("admin:users:create", "Create users", "Create tenant memberships for accounts.", "Tenant administration"),
    PermissionDefinition("admin:users:update", "Update users", "Edit non-access membership metadata.", "Tenant administration"),
    PermissionDefinition("admin:users:suspend", "Suspend users", "Activate or suspend tenant memberships.", "Tenant administration"),
    PermissionDefinition("admin:groups:read", "View groups", "List tenant groups, members and assigned roles.", "Tenant administration"),
    PermissionDefinition("admin:groups:write", "Manage groups", "Create or edit tenant group definitions.", "Tenant administration"),
    PermissionDefinition("admin:groups:manage_members", "Manage group members", "Add and remove users from tenant groups.", "Tenant administration"),
    PermissionDefinition("admin:roles:read", "View roles", "Inspect role definitions and the permission catalogue.", "Tenant administration"),
    PermissionDefinition("admin:roles:write", "Define roles", "Create and edit assignable tenant role definitions.", "Tenant administration"),
    PermissionDefinition("admin:roles:assign", "Assign roles", "Assign tenant roles to users or groups within delegation limits.", "Tenant administration"),
    PermissionDefinition("admin:api_keys:read", "View API keys", "List tenant API keys without revealing their secret.", "Tenant administration"),
    PermissionDefinition("admin:api_keys:create", "Create API keys", "Create tenant-local API keys within the owner's delegation limits.", "Tenant administration"),
    PermissionDefinition("admin:api_keys:revoke", "Revoke API keys", "Revoke tenant-local API keys.", "Tenant administration"),
    PermissionDefinition("admin:settings:read", "View tenant settings", "Read tenant defaults and non-policy settings.", "Tenant administration"),
    PermissionDefinition("admin:settings:write", "Manage tenant settings", "Change tenant defaults and non-policy settings.", "Tenant administration"),
    PermissionDefinition("admin:policies:read", "View tenant policies", "Read tenant policy and governance settings.", "Tenant administration"),
    PermissionDefinition("admin:policies:write", "Manage tenant policies", "Change tenant policy and governance settings where system policy permits it.", "Tenant administration"),
)

SYSTEM_PERMISSIONS: tuple[PermissionDefinition, ...] = (
    PermissionDefinition("system:tenants:read", "View all tenants", "List tenants and system-wide membership counts.", "System administration", "system"),
    PermissionDefinition("system:tenants:create", "Create tenants", "Create new tenant spaces.", "System administration", "system"),
    PermissionDefinition("system:tenants:update", "Update tenants", "Edit tenant metadata and governance overrides.", "System administration", "system"),
    PermissionDefinition("system:tenants:suspend", "Suspend tenants", "Activate or suspend tenant spaces while preserving evidence.", "System administration", "system"),
    PermissionDefinition("system:accounts:read", "View accounts", "List global login accounts and memberships.", "System administration", "system"),
    PermissionDefinition("system:accounts:create", "Create accounts", "Create global login accounts.", "System administration", "system"),
    PermissionDefinition("system:accounts:update", "Update accounts", "Edit global account metadata.", "System administration", "system"),
    PermissionDefinition("system:accounts:suspend", "Suspend accounts", "Activate or suspend global login accounts while preserving a system owner.", "System administration", "system"),
    PermissionDefinition("system:roles:read", "View system roles", "Inspect instance-wide role definitions and their permissions.", "System administration", "system"),
    PermissionDefinition("system:roles:write", "Define system roles", "Create and edit instance-wide role definitions within delegation limits.", "System administration", "system"),
    PermissionDefinition("system:roles:assign", "Assign system roles", "Assign instance-wide roles to accounts while preserving a system owner.", "System administration", "system"),
    PermissionDefinition("system:access:read", "View system access (legacy)", "Compatibility scope for listing accounts with instance-wide roles.", "System administration", "system"),
    PermissionDefinition("system:access:assign", "Assign system access (legacy)", "Compatibility scope for assigning instance-wide roles.", "System administration", "system"),
    PermissionDefinition("system:audit:read", "View system audit", "Read audit records across tenants.", "System administration", "system"),
    PermissionDefinition("system:settings:read", "View system settings", "Read instance defaults and tenant-governance defaults.", "System administration", "system"),
    PermissionDefinition("system:settings:write", "Manage system settings", "Change instance defaults and tenant-governance defaults.", "System administration", "system"),
    PermissionDefinition("system:maintenance:access", "Access during maintenance", "Use the system while maintenance mode is active.", "System administration", "system"),
    PermissionDefinition("system:governance:read", "View governance templates", "Inspect centrally managed group and role definitions.", "System administration", "system"),
    PermissionDefinition("system:governance:write", "Manage governance templates", "Create and assign centrally managed group and role definitions.", "System administration", "system"),
)

ALL_PERMISSIONS: tuple[PermissionDefinition, ...] = TENANT_PERMISSIONS + SYSTEM_PERMISSIONS
KNOWN_SCOPES = frozenset(item.scope for item in ALL_PERMISSIONS)
TENANT_SCOPES = frozenset(item.scope for item in TENANT_PERMISSIONS)
SYSTEM_SCOPES = frozenset(item.scope for item in SYSTEM_PERMISSIONS)

DEFAULT_TENANT_ROLES: dict[str, dict[str, object]] = {
    "owner": {"name": "Owner", "description": "Full tenant access, including administration and delivery.", "permissions": ["tenant:*"], "is_builtin": True, "is_assignable": True},
    "tenant_admin": {"name": "Tenant administrator", "description": "Manage tenant settings, users, groups and roles. Real delivery remains separately delegable.", "permissions": [
        "campaign:read", "files:read", "reports:read", "audit:read",
        "admin:users:read", "admin:users:create", "admin:users:update", "admin:users:suspend",
        "admin:groups:read", "admin:groups:write", "admin:groups:manage_members",
        "admin:roles:read", "admin:roles:write", "admin:roles:assign",
        "admin:api_keys:read", "admin:api_keys:create", "admin:api_keys:revoke",
        "admin:settings:read", "admin:settings:write", "admin:policies:read", "admin:policies:write",
    ], "is_builtin": True, "is_assignable": True},
    "admin": {"name": "Administrator (legacy)", "description": "Legacy broad tenant role retained for upgraded installations.", "permissions": sorted(TENANT_SCOPES), "is_builtin": True, "is_assignable": True},
    "access_admin": {"name": "Access administrator", "description": "Manage memberships and assignments within delegation limits.", "permissions": [
        "admin:users:read", "admin:users:create", "admin:users:update", "admin:users:suspend",
        "admin:groups:read", "admin:groups:manage_members", "admin:roles:read", "admin:roles:assign",
    ], "is_builtin": True, "is_assignable": True},
    "campaign_manager": {"name": "Campaign manager", "description": "Prepare, validate and build campaigns, but do not approve or start real delivery.", "permissions": [
        "campaign:read", "campaign:create", "campaign:update", "campaign:copy", "campaign:validate", "campaign:build",
        "recipients:read", "recipients:write", "recipients:import", "files:read", "files:download", "files:upload", "files:organize", "reports:read",
    ], "is_builtin": True, "is_assignable": True},
    "reviewer": {"name": "Reviewer", "description": "Inspect and approve prepared campaign messages.", "permissions": ["campaign:read", "campaign:validate", "campaign:review", "recipients:read", "files:read", "reports:read"], "is_builtin": True, "is_assignable": True},
    "sender": {"name": "Sender", "description": "Queue, test, control, send and reconcile prepared campaigns.", "permissions": [
        "campaign:read", "campaign:send_test", "campaign:queue", "campaign:control", "campaign:send", "campaign:retry", "campaign:reconcile",
        "recipients:read", "files:read", "reports:read", "reports:send", "mail_servers:use", "mail_servers:test",
    ], "is_builtin": True, "is_assignable": True},
    "file_manager": {"name": "File manager", "description": "Manage tenant file spaces without campaign delivery rights.", "permissions": ["files:read", "files:download", "files:upload", "files:organize", "files:share", "files:delete"], "is_builtin": True, "is_assignable": True},
    "viewer": {"name": "Viewer", "description": "Read campaigns, recipient data, files and reports without changing them.", "permissions": ["campaign:read", "recipients:read", "files:read", "reports:read"], "is_builtin": True, "is_assignable": True},
    "auditor": {"name": "Auditor", "description": "Read campaigns, recipient evidence, reports and audit records.", "permissions": ["campaign:read", "recipients:read", "recipients:export", "reports:read", "reports:export", "audit:read"], "is_builtin": True, "is_assignable": True},
}

DEFAULT_SYSTEM_ROLES: dict[str, dict[str, object]] = {
    "system_owner": {
        "name": "System owner",
        "description": "Protected full instance-wide administration. At least one active account must retain this role.",
        "permissions": ["system:*"],
        "is_builtin": True,
        "is_assignable": True,
        "managed": True,
    },
    "system_admin": {
        "name": "System administrator",
        "description": "Manage tenants, accounts, system roles, settings and governance without being able to grant the protected System owner role.",
        "permissions": sorted(SYSTEM_SCOPES),
        "is_builtin": False,
        "is_assignable": True,
        "managed": False,
    },
    "system_auditor": {
        "name": "System auditor",
        "description": "Read tenant registry, accounts, system roles, settings, governance and cross-tenant audit records.",
        "permissions": [
            "system:tenants:read", "system:accounts:read", "system:roles:read", "system:access:read",
            "system:audit:read", "system:settings:read", "system:governance:read",
        ],
        "is_builtin": False,
        "is_assignable": True,
        "managed": False,
    },
    "maintenance_operator": {
        "name": "Maintenance operator",
        "description": "Access the system while maintenance mode is active.",
        "permissions": ["system:settings:read", "system:maintenance:access"],
        "is_builtin": False,
        "is_assignable": True,
        "managed": False,
    },
}


def normalize_email(value: str) -> str:
    return value.strip().casefold()


def scope_grants(granted: str, required: str) -> bool:
    if granted == required:
        return True
    if required in LEGACY_SCOPE_ALIASES.get(granted, frozenset()):
        return True
    if granted == "*":
        return True
    if granted == "tenant:*":
        return required == "tenant:*" or not required.startswith("system:")
    if granted == "system:*":
        return required.startswith("system:")
    if granted.endswith(":*") and required.startswith(granted[:-1]):
        return True
    return any(scope_grants(alias, required) for alias in LEGACY_SCOPE_ALIASES.get(granted, frozenset()) if alias != granted)


def scopes_grant(scopes: Iterable[str], required: str) -> bool:
    return any(scope_grants(scope, required) for scope in scopes)


def effective_permission_scopes(scopes: Iterable[str], *, level: str | None = None) -> set[str]:
    """Return canonical permissions granted by a possibly wildcarded scope set.

    Wildcards and compatibility aliases are presentation/assignment shorthand;
    counts and access summaries should describe the canonical capabilities they
    grant rather than counting the shorthand token itself.
    """
    if level == "tenant":
        candidates = TENANT_SCOPES
    elif level == "system":
        candidates = SYSTEM_SCOPES
    else:
        candidates = KNOWN_SCOPES
    granted = list(scopes)
    return {scope for scope in candidates if scopes_grant(granted, scope)}


def effective_permission_count(scopes: Iterable[str], *, level: str | None = None) -> int:
    return len(effective_permission_scopes(scopes, level=level))


def expand_scopes(scopes: Iterable[str], *, include_unknown: bool = True) -> list[str]:
    raw = {str(scope) for scope in scopes if scope}
    expanded: set[str] = set()
    for scope in raw:
        if scope in {"*", "tenant:*"}:
            expanded.add("tenant:*")
            expanded.update(TENANT_SCOPES)
            continue
        if scope == "system:*":
            expanded.add("system:*")
            expanded.update(SYSTEM_SCOPES)
            continue
        aliases = LEGACY_SCOPE_ALIASES.get(scope, frozenset())
        expanded.update(aliases)
        for alias in aliases:
            if alias.endswith(":*"):
                expanded.update(item for item in KNOWN_SCOPES if scope_grants(alias, item))
        if scope.endswith(":*"):
            expanded.update(item for item in KNOWN_SCOPES if scope_grants(scope, item))
            expanded.add(scope)
        elif include_unknown or scope in KNOWN_SCOPES:
            expanded.add(scope)
    return sorted(expanded)


def delegateable_tenant_scopes(scopes: Iterable[str]) -> set[str]:
    expanded = set(expand_scopes(scopes, include_unknown=False))
    if "tenant:*" in expanded:
        return set(TENANT_SCOPES)
    return {scope for scope in expanded if scope in TENANT_SCOPES}


def delegateable_system_scopes(scopes: Iterable[str]) -> set[str]:
    expanded = set(expand_scopes(scopes, include_unknown=False))
    if "system:*" in expanded:
        return set(SYSTEM_SCOPES)
    return {scope for scope in expanded if scope in SYSTEM_SCOPES}


def intersect_api_key_scopes(user_scopes: Iterable[str], key_scopes: Iterable[str]) -> list[str]:
    user = list(user_scopes)
    key = list(key_scopes)
    allowed = {scope for scope in TENANT_SCOPES if scopes_grant(user, scope) and scopes_grant(key, scope)}
    user_raw = set(expand_scopes(user))
    key_raw = set(expand_scopes(key))
    allowed.update(scope for scope in user_raw.intersection(key_raw) if not scope.startswith("system:") and scope not in {"*", "tenant:*"})
    return sorted(allowed)


def validate_tenant_permissions(scopes: Iterable[str]) -> list[str]:
    normalized = {str(scope) for scope in scopes if scope}
    if normalized.intersection({"*", "tenant:*"}):
        return ["tenant:*"]
    expanded: set[str] = set()
    invalid: list[str] = []
    for scope in sorted(normalized):
        if scope in TENANT_SCOPES:
            expanded.add(scope)
            continue
        aliases = {item for item in LEGACY_SCOPE_ALIASES.get(scope, frozenset()) if item in TENANT_SCOPES}
        if aliases:
            expanded.update(aliases)
            continue
        invalid.append(scope)
    if invalid:
        raise ValueError(f"Unsupported tenant permissions: {', '.join(invalid)}")
    return sorted(expanded)


def validate_system_permissions(scopes: Iterable[str]) -> list[str]:
    normalized = sorted({str(scope) for scope in scopes if scope})
    invalid = [scope for scope in normalized if scope not in SYSTEM_SCOPES and scope != "system:*"]
    if invalid:
        raise ValueError(f"Unsupported system permissions: {', '.join(invalid)}")
    if "system:*" in normalized:
        return ["system:*"]
    return normalized
