"""
Centralized authorization for DTA services.

Single rule: given a normalized user payload (Identity from cache or DTA JWT),
organization, tenant, and required roles, returns whether the user is authorized.

God mode: Identity with payload.super or DTA with payload.super/is_super
→ authorized regardless of roles.

High-level entry point: authorize_with_token(token, required_roles, org, tenant)
uses cache.py (Identity) and jwt.py (DTA) internally so services only pass
the JWT and the required roles.
"""
import os
from typing import Any, Dict, List, Optional, Tuple, Union
from .cache import get_identity_user_from_cache
from .jwt import detect_jwt_type, validate_dta_jwt
from .constants import DTA_ISSUER, DTARoles
from .exceptions import AuthError


def _strip_bearer(token: str) -> str:
    if token.startswith("Bearer "):
        return token[7:].strip()
    return token.strip()


def _normalize_required_roles(
    required_roles: Optional[List[Union[str, DTARoles]]],
) -> List[str]:
    """Convert required_roles to list of string role names."""
    if not required_roles:
        return []
    return [str(r) for r in required_roles]


def is_identity_super_user(payload: Dict[str, Any]) -> bool:
    """
    True if the payload is from Identity (cache) and has super (god mode).

    Use this when you only need to know if the user can bypass role checks.
    """
    return bool(payload.get("super")) and "dta_roles" in payload


def _tenant_present_in_payload(
    payload: Dict[str, Any],
    jwt_type: str,
    tenant: Optional[str],
) -> bool:
    """
    Tenant-only check for super/god mode bypass.

    - If tenant is not provided, allow bypass (no tenant scoping).
    - If tenant is provided, it must exist in the payload:
      - identity: payload["dta_roles"]
      - dta: payload["roles"]
    """
    if not tenant or not str(tenant).strip():
        return True
    tenant_str = str(tenant).strip()
    if jwt_type == "identity":
        entries = payload.get("dta_roles") or []
    else:
        entries = payload.get("roles") or []
    return any(r.get("tenant") == tenant_str for r in entries)


def authorize(
    payload: Dict[str, Any],
    org: Optional[str],
    tenant: Optional[str],
    required_roles: Optional[List[Union[str, DTARoles]]],
    jwt_type: str,
) -> bool:
    """
    Decide if the user is authorized for the given org/tenant and required roles.

    Works for both Identity (OAuth/cache) and DTA (SAML) payloads so all
    services can reuse the same logic.

    Args:
        payload: Normalized user payload.
            - Identity (from get_identity_user_from_cache): name, identifier,
              dta_roles [{tenant, role}], exp, org, super.
            - DTA (from validate_dta_jwt): name, identifier, organization,
              super or is_super, org_admin, roles [{tenant, role, rights}], exp.
        org: Requested organization (from host/header). Can be None to skip org check.
        tenant: Requested tenant/project (from path/header). Can be None.
        required_roles: List of role names (e.g. DTARoles.TENANT_ADMIN or "TENANT_ADMIN").
            Empty/None means "any authenticated user" (authorized if org matches).
        jwt_type: "identity" or "dta".

    Returns:
        True if authorized, False otherwise.

    God mode:
        - Identity: payload.super is True → authorized.
        - DTA: payload.super or payload.is_super is True → authorized.
    """
    if jwt_type not in ("identity", "dta"):
        return False

    roles_list = _normalize_required_roles(required_roles)

    # --- God mode ---
    if jwt_type == "identity":
        if payload.get("super") is True:
            return _tenant_present_in_payload(payload, jwt_type, tenant)
    else:
        if payload.get("super") is True or payload.get("is_super") is True:
            return _tenant_present_in_payload(payload, jwt_type, tenant)

    # --- Org check ---
    if org is not None and org:
        payload_org = (
            (payload.get("organization") or payload.get("org") or "")
            .lower()
            .strip()
        )
        if payload_org != org.lower().strip():
            return False

    # --- No role required ---
    if not roles_list:
        return True

    # --- ORG_ADMIN without tenant (DTA only) ---
    if (
        jwt_type == "dta"
        and (not tenant or not str(tenant).strip())
        and "ORG_ADMIN" in roles_list
    ):
        if payload.get("org_admin") is True:
            return True

    # --- Tenant + role check ---
    if not tenant or not str(tenant).strip():
        return False

    tenant_str = str(tenant).strip()

    if jwt_type == "identity":
        dta_roles = payload.get("dta_roles") or []
        for r in dta_roles:
            if r.get("tenant") == tenant_str and r.get("role") in roles_list:
                return True
        return False

    # DTA
    for r in payload.get("roles") or []:
        if r.get("tenant") != tenant_str:
            continue
        role_name = r.get("role")
        if role_name in roles_list:
            return True
        # Map proxy ADMIN to TENANT_ADMIN for compatibility
        rights = r.get("rights") or {}
        if rights.get("proxy") == "ADMIN" and "TENANT_ADMIN" in roles_list:
            return True
    return False


def authorize_with_token(
    token: str,
    required_roles: Optional[List[Union[str, DTARoles]]],
    org: Optional[str] = None,
    tenant: Optional[str] = None,
    *,
    dta_secret: Optional[str] = None,
    dta_issuer: Optional[str] = None,
) -> Tuple[bool, Optional[Dict[str, Any]], str]:
    """
    Authorize from the raw JWT and required roles. Uses cache (Identity) and
    jwt validation (DTA) internally so services only pass token + roles + context.

    Args:
        token: Raw JWT (with or without "Bearer " prefix).
        required_roles: List of role names (e.g. DTARoles.TENANT_ADMIN).
        org: Requested organization (from host/header). Optional.
        tenant: Requested tenant/project (from path/header). Optional.
        dta_secret: Secret for DTA JWT. If None, uses JWT_SECRET or JWT_SECRET_KEY env.
        dta_issuer: Issuer for DTA JWT. If None, uses default (dta.totvs.ai).

    Returns:
        (authorized, payload, jwt_type). payload is the normalized user payload when
        authorized, None when not. jwt_type is "identity" or "dta".

    Raises:
        AuthError: If token is missing, invalid, expired, or wrong type (401/500).
    """

    raw = _strip_bearer(token)
    if not raw:
        raise AuthError("Token not found", status_code=401)

    jwt_type = detect_jwt_type(raw)
    if jwt_type == "identity":
        payload = get_identity_user_from_cache(raw)
    else:
        secret = dta_secret or os.environ.get("JWT_SECRET") or os.environ.get("JWT_SECRET_KEY") or ""
        issuer = dta_issuer or DTA_ISSUER
        payload = validate_dta_jwt(raw, secret, issuer=issuer)
        # DTA raw payload uses "super"; some callers also set "is_super" on UserData
        if "is_super" not in payload and "super" in payload:
            payload = dict(payload)
            payload["is_super"] = payload["super"]

    ok = authorize(payload, org, tenant, required_roles, jwt_type)
    return (ok, payload if ok else None, jwt_type)
