"""Database identity policy shared by login, session and HTTP access control."""

from __future__ import annotations

from datetime import datetime
import ipaddress
import os
from typing import Iterable, Mapping


POLICY = "ewoooc_database_identity_policy_v1"
AUTH_IDENTITY_MODES = frozenset({"auto", "shadow", "hybrid", "database"})
DEFAULT_AUTH_IDENTITY_MODE = "auto"
DEFAULT_TRUSTED_PROXY_CIDRS = (
    "127.0.0.1/32",
    "::1/128",
    "172.19.0.1/32",
)

ROLE_ADMIN = "admin"
ROLE_MANAGER = "manager"
ROLE_USER = "user"
KNOWN_ROLES = frozenset({ROLE_ADMIN, ROLE_MANAGER, ROLE_USER})

READ_ROLES = (ROLE_ADMIN, ROLE_MANAGER, ROLE_USER)
OPERATE_ROLES = (ROLE_ADMIN, ROLE_MANAGER)
ADMIN_ROLES = (ROLE_ADMIN,)
MUTATING_METHODS = frozenset({"POST", "PUT", "PATCH", "DELETE"})

# These endpoints mutate only the current identity and remain available to every
# authenticated role. Administrative identity mutations already use admin_required.
SELF_SERVICE_MUTATION_ENDPOINTS = frozenset({
    "user_bp.api_change_password",
})

# Operational and identity administration never inherit the generic read role.
ADMIN_BLUEPRINTS = frozenset({"cicd", "crawler_management", "user_bp"})
ADMIN_ENDPOINTS = frozenset({
    "alert.test_alert",
    "code_review.api_history",
    "code_review.api_status",
    "code_review.dashboard",
    "openclaw_bot.set_webhook",
    "openclaw_bot.webhook_info",
    "system_public.get_logs_api",
    "system_public.settings",
    "system_public.show_logs",
    "system_public.system_settings_page",
})


def resolve_auth_identity_mode(
    value: str | None = None,
    *,
    environ: Mapping[str, str] | None = None,
) -> str:
    """Return a validated auth mode; invalid configuration fails closed."""
    source = os.environ if environ is None else environ
    candidate = (value if value is not None else source.get("MOMO_AUTH_IDENTITY_MODE"))
    mode = (candidate or DEFAULT_AUTH_IDENTITY_MODE).strip().lower()
    if mode not in AUTH_IDENTITY_MODES:
        raise ValueError(
            "MOMO_AUTH_IDENTITY_MODE must be one of: auto, database, hybrid, shadow"
        )
    return mode


def auth_auto_cutover_min_successes(
    *,
    environ: Mapping[str, str] | None = None,
) -> int:
    source = os.environ if environ is None else environ
    try:
        value = int(source.get("MOMO_AUTH_AUTO_CUTOVER_MIN_SUCCESSES", "2"))
    except (TypeError, ValueError):
        value = 2
    return max(2, min(value, 10))


def database_auth_can_authorize(mode: str) -> bool:
    return resolve_auth_identity_mode(mode) in {"auto", "hybrid", "database"}


def database_auth_is_evaluated(mode: str) -> bool:
    return resolve_auth_identity_mode(mode) in AUTH_IDENTITY_MODES


def legacy_auth_can_authorize(mode: str) -> bool:
    return resolve_auth_identity_mode(mode) in {"auto", "shadow", "hybrid"}


def resolve_effective_auth_identity_mode(
    configured_mode: str,
    *,
    auto_cutover_ready: bool = False,
) -> str:
    mode = resolve_auth_identity_mode(configured_mode)
    if mode != "auto":
        return mode
    return "database" if auto_cutover_ready else "hybrid"


def parse_trusted_proxy_networks(
    value: str | None = None,
    *,
    environ: Mapping[str, str] | None = None,
) -> tuple[ipaddress.IPv4Network | ipaddress.IPv6Network, ...]:
    source = os.environ if environ is None else environ
    raw = value if value is not None else source.get("MOMO_TRUSTED_PROXY_CIDRS")
    cidrs = [part.strip() for part in (raw or ",".join(DEFAULT_TRUSTED_PROXY_CIDRS)).split(",")]
    networks = []
    for cidr in cidrs:
        if not cidr:
            continue
        networks.append(ipaddress.ip_network(cidr, strict=True))
    if not networks:
        raise ValueError("MOMO_TRUSTED_PROXY_CIDRS must contain at least one CIDR")
    return tuple(networks)


def resolve_client_ip(
    remote_addr: str | None,
    x_forwarded_for: str | None = None,
    x_real_ip: str | None = None,
    *,
    trusted_proxy_cidrs: str | None = None,
) -> str:
    """Trust forwarding headers only when the direct peer is allowlisted."""
    peer_text = (remote_addr or "").strip()
    try:
        peer = ipaddress.ip_address(peer_text)
    except ValueError:
        return "unknown"

    networks = parse_trusted_proxy_networks(trusted_proxy_cidrs)
    if not any(peer in network for network in networks):
        return str(peer)

    candidates = []
    if x_forwarded_for:
        candidates.extend(part.strip() for part in x_forwarded_for.split(","))
    if x_real_ip:
        candidates.append(x_real_ip.strip())
    for candidate in candidates:
        try:
            return str(ipaddress.ip_address(candidate))
        except ValueError:
            continue
    return str(peer)


def identity_version(user) -> str:
    """Create a stable revocation version from durable identity timestamps."""
    values = [
        value
        for value in (
            getattr(user, "password_changed_at", None),
            getattr(user, "updated_at", None),
            getattr(user, "created_at", None),
        )
        if isinstance(value, datetime)
    ]
    if not values:
        return "unversioned"
    return max(values).isoformat(timespec="microseconds")


def role_is_allowed(role: str | None, allowed_roles: Iterable[str]) -> bool:
    allowed = frozenset(allowed_roles)
    return bool(role in KNOWN_ROLES and role in allowed)


def required_roles_for_request(
    method: str,
    endpoint: str | None,
    blueprint: str | None,
) -> tuple[str, ...]:
    """Map protected HTTP requests to least-privilege role tiers."""
    normalized_method = (method or "GET").upper()
    if endpoint in SELF_SERVICE_MUTATION_ENDPOINTS:
        return READ_ROLES
    if blueprint in ADMIN_BLUEPRINTS or endpoint in ADMIN_ENDPOINTS:
        return ADMIN_ROLES
    if normalized_method in MUTATING_METHODS:
        return OPERATE_ROLES
    return READ_ROLES


def public_policy_readback(mode: str | None = None) -> dict:
    resolved_mode = resolve_auth_identity_mode(mode)
    return {
        "policy": POLICY,
        "auth_identity_mode": resolved_mode,
        "database_auth_evaluated": database_auth_is_evaluated(resolved_mode),
        "database_auth_authoritative": database_auth_can_authorize(resolved_mode),
        "legacy_shared_password_authoritative": legacy_auth_can_authorize(resolved_mode),
        "session_revocation_source": "users.password_changed_at_or_updated_at",
        "durable_lockout_source": "login_history",
        "identity_mutation_audit_source": "login_history",
        "role_matrix": {
            ROLE_ADMIN: ["read", "operate", "administer"],
            ROLE_MANAGER: ["read", "operate"],
            ROLE_USER: ["read"],
        },
        "trusted_proxy_network_count": len(parse_trusted_proxy_networks()),
    }


def build_auth_identity_runtime_readback(db_session, mode: str | None = None) -> dict:
    """Build a no-secret runtime receipt from the durable identity store."""
    from sqlalchemy import func, inspect

    from database.permission_models import UserPermission
    from services.user_service import UserService

    configured_mode = resolve_auth_identity_mode(mode)
    inspector = inspect(db_session.get_bind())
    required_tables = ("users", "login_history", "user_permissions")
    table_state = {table: inspector.has_table(table) for table in required_tables}
    user_service = UserService(db_session)
    inventory = user_service.get_identity_inventory()
    auto_cutover = user_service.get_auth_auto_cutover_state(
        min_successes=auth_auto_cutover_min_successes()
    )
    resolved_mode = resolve_effective_auth_identity_mode(
        configured_mode,
        auto_cutover_ready=auto_cutover["ready"],
    )
    permission_assignments = (
        db_session.query(func.count(UserPermission.id)).scalar() or 0
        if table_state["user_permissions"]
        else 0
    )
    checks = {
        "required_identity_tables_present": all(table_state.values()),
        "active_admin_available": inventory["active_admin_count"] >= 1,
        "database_auth_evaluated": database_auth_is_evaluated(resolved_mode),
        "database_auth_authoritative": database_auth_can_authorize(resolved_mode),
        "durable_lockout_configured": table_state["login_history"],
        "session_revocation_configured": table_state["users"],
        "identity_mutation_audit_configured": table_state["login_history"],
        "least_privilege_role_matrix_configured": True,
        "trusted_proxy_policy_configured": bool(parse_trusted_proxy_networks()),
        "legacy_shared_password_retired": not legacy_auth_can_authorize(resolved_mode),
    }
    canary_ready = all(
        value
        for key, value in checks.items()
        if key != "legacy_shared_password_retired"
    )
    completed = canary_ready and checks["legacy_shared_password_retired"]
    return {
        "receipt_kind": "auth_identity_runtime_readback",
        "policy": POLICY,
        "status": "completed" if completed else "canary_ready" if canary_ready else "blocked",
        "work_item_id": "SEC-P0-002",
        "configured_auth_identity_mode": configured_mode,
        "auth_identity_mode": resolved_mode,
        "inventory": {
            **inventory,
            "permission_assignments": permission_assignments,
        },
        "tables": table_state,
        "auto_cutover": auto_cutover,
        "checks": checks,
        "canary_ready": canary_ready,
        "runtime_closed": completed,
        "remaining_blocker": (
            None
            if completed
            else "legacy_shared_password_authority_still_enabled"
            if canary_ready
            else "identity_canary_preconditions_failed"
        ),
        "next_machine_action": (
            "advance_to_SEC-P0-003"
            if completed
            else "capture_database_admin_login_receipts_for_automatic_cutover"
            if configured_mode == "auto"
            else "capture_database_login_canary_then_switch_to_database_mode"
            if canary_ready
            else "repair_identity_schema_or_active_admin_inventory"
        ),
        "safety": {
            "read_only": True,
            "secret_values_read": False,
            "password_hashes_returned": False,
            "raw_sessions_returned": False,
        },
    }


__all__ = [
    "ADMIN_ROLES",
    "AUTH_IDENTITY_MODES",
    "KNOWN_ROLES",
    "OPERATE_ROLES",
    "POLICY",
    "READ_ROLES",
    "auth_auto_cutover_min_successes",
    "database_auth_can_authorize",
    "database_auth_is_evaluated",
    "build_auth_identity_runtime_readback",
    "identity_version",
    "legacy_auth_can_authorize",
    "parse_trusted_proxy_networks",
    "public_policy_readback",
    "required_roles_for_request",
    "resolve_auth_identity_mode",
    "resolve_effective_auth_identity_mode",
    "resolve_client_ip",
    "role_is_allowed",
]
