from __future__ import annotations

from base64 import urlsafe_b64decode, urlsafe_b64encode
from datetime import datetime, timedelta, timezone
import hashlib
import hmac
import json
import os
from typing import Callable

from fastapi import Depends, Header, HTTPException

from services.shared.core import Role


def _bool_env(name: str, default: bool) -> bool:
    raw = os.getenv(name)
    if raw is None:
        return default
    return raw.strip().lower() in {"1", "true", "yes", "on"}


def legacy_header_auth_allowed() -> bool:
    return _bool_env("ALLOW_LEGACY_HEADER_AUTH", True)


def _app_token_secret() -> str:
    return os.getenv("APP_TOKEN_SECRET", "dev-secret-change-me")


def _app_token_ttl_seconds() -> int:
    raw = os.getenv("APP_TOKEN_TTL_SECONDS", "3600").strip()
    try:
        value = int(raw)
    except ValueError:
        value = 3600
    return max(value, 60)


def _b64url_encode(raw: bytes) -> str:
    return urlsafe_b64encode(raw).decode("utf-8").rstrip("=")


def _b64url_decode(raw: str) -> bytes:
    padding = "=" * (-len(raw) % 4)
    return urlsafe_b64decode((raw + padding).encode("utf-8"))


def _sign(message: str) -> str:
    digest = hmac.new(
        _app_token_secret().encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256,
    ).digest()
    return _b64url_encode(digest)


def issue_app_token(
    *,
    subject: str,
    username: str,
    role: str,
    auth_source: str,
    provider: str | None = None,
    full_name: str | None = None,
    email: str | None = None,
    tenant_id: str | None = None,
    ttl_seconds: int | None = None,
) -> str:
    now = datetime.now(timezone.utc)
    ttl = ttl_seconds if ttl_seconds is not None else _app_token_ttl_seconds()
    header = {"alg": "HS256", "typ": "JWT"}
    payload = {
        "sub": subject,
        "username": username,
        "role": role,
        "auth_source": auth_source,
        "iat": int(now.timestamp()),
        "exp": int((now + timedelta(seconds=ttl)).timestamp()),
    }
    if provider:
        payload["provider"] = provider
    if full_name:
        payload["full_name"] = full_name
    if email:
        payload["email"] = email
    if tenant_id:
        payload["tenant_id"] = tenant_id

    encoded_header = _b64url_encode(json.dumps(header, separators=(",", ":")).encode("utf-8"))
    encoded_payload = _b64url_encode(json.dumps(payload, separators=(",", ":")).encode("utf-8"))
    message = f"{encoded_header}.{encoded_payload}"
    signature = _sign(message)
    return f"{message}.{signature}"


def decode_app_token(token: str) -> dict:
    try:
        encoded_header, encoded_payload, encoded_signature = token.split(".")
    except ValueError as exc:
        raise HTTPException(status_code=401, detail="Invalid token format") from exc

    message = f"{encoded_header}.{encoded_payload}"
    expected_signature = _sign(message)
    if not hmac.compare_digest(encoded_signature, expected_signature):
        raise HTTPException(status_code=401, detail="Invalid token signature")

    try:
        payload = json.loads(_b64url_decode(encoded_payload).decode("utf-8"))
    except Exception as exc:  # noqa: BLE001
        raise HTTPException(status_code=401, detail="Invalid token payload") from exc

    exp = int(payload.get("exp", 0) or 0)
    now_ts = int(datetime.now(timezone.utc).timestamp())
    if exp and exp < now_ts:
        raise HTTPException(status_code=401, detail="Token expired")

    return payload


def get_actor(
    authorization: str | None = Header(default=None, alias="Authorization"),
    x_user: str | None = Header(default=None, alias="X-User"),
    x_role: str | None = Header(default=None, alias="X-Role"),
    x_tenant_id: str | None = Header(default=None, alias="X-Tenant-ID"),
) -> dict:
    if authorization:
        scheme, _, value = authorization.partition(" ")
        if scheme.lower() != "bearer" or not value.strip():
            raise HTTPException(status_code=401, detail="Invalid authorization header")
        payload = decode_app_token(value.strip())
        tenant_id = str(payload.get("tenant_id") or "").strip()
        tenant_source = "token" if tenant_id else None
        if not tenant_id:
            tenant_id = str(x_tenant_id or "").strip()
            tenant_source = "header" if tenant_id else None
        return {
            "sub": payload.get("sub", ""),
            "user": payload.get("username", "anonymous"),
            "role": str(payload.get("role", "anonymous")).strip().lower() or "anonymous",
            "auth_source": payload.get("auth_source", "token"),
            "provider": payload.get("provider"),
            "full_name": payload.get("full_name"),
            "email": payload.get("email"),
            "tenant_id": tenant_id or None,
            "tenant_source": tenant_source,
        }

    if legacy_header_auth_allowed():
        role = (x_role or "").strip().lower()
        tenant_id = str(x_tenant_id or "").strip()
        return {
            "user": (x_user or "anonymous").strip(),
            "role": role or "anonymous",
            "auth_source": "legacy",
            "tenant_id": tenant_id or None,
            "tenant_source": "header" if tenant_id else None,
        }

    return {"user": "anonymous", "role": "anonymous", "auth_source": "none", "tenant_id": None, "tenant_source": None}


def require_roles(*allowed: Role) -> Callable:
    allowed_values = {a.value for a in allowed}

    def dependency(actor: dict = Depends(get_actor)) -> dict:
        if actor["role"] not in allowed_values:
            raise HTTPException(status_code=403, detail="Insufficient role")
        return actor

    return dependency
