"""JWT authentication and API key management for PenBot API.

Provides:
- API key generation and validation (for CI/agent use)
- JWT token issuance and verification (for dashboard sessions)
- FastAPI dependency for protecting routes
"""

from __future__ import annotations

import hashlib
import json
import secrets
from datetime import datetime, timezone, timedelta
from pathlib import Path
from typing import Optional

from fastapi import Depends, HTTPException, Security, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer, APIKeyHeader

import jwt

from src.utils.config import settings
from src.utils.logging import get_logger

logger = get_logger(__name__)

_bearer_scheme = HTTPBearer(auto_error=False)
_api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)

API_KEYS_FILE = Path("data/api_keys.json")


# ---------------------------------------------------------------------------
# Data model
# ---------------------------------------------------------------------------


class AuthUser:
    """Authenticated user context."""

    def __init__(self, user_id: str, role: str = "user", via: str = "jwt"):
        self.user_id = user_id
        self.role = role
        self.via = via  # "jwt" | "api_key"

    @property
    def is_admin(self) -> bool:
        return self.role == "admin"


# ---------------------------------------------------------------------------
# API key management
# ---------------------------------------------------------------------------


def _load_api_keys() -> dict:
    if API_KEYS_FILE.exists():
        try:
            return json.loads(API_KEYS_FILE.read_text(encoding="utf-8"))
        except Exception:
            pass
    return {}


def _save_api_keys(keys: dict) -> None:
    API_KEYS_FILE.parent.mkdir(parents=True, exist_ok=True)
    API_KEYS_FILE.write_text(json.dumps(keys, indent=2), encoding="utf-8")


def _hash_key(raw: str) -> str:
    return hashlib.sha256(raw.encode()).hexdigest()


def create_api_key(user_id: str, label: str = "default") -> str:
    """Generate a new API key for a user. Returns the raw key (only shown once)."""
    raw = f"pb_{secrets.token_urlsafe(32)}"
    hashed = _hash_key(raw)

    keys = _load_api_keys()
    keys[hashed] = {
        "user_id": user_id,
        "label": label,
        "created_at": datetime.now(timezone.utc).isoformat(),
        "role": "user",
    }
    _save_api_keys(keys)

    logger.info("api_key_created", user_id=user_id, label=label)
    return raw


def validate_api_key(raw: str) -> Optional[AuthUser]:
    """Validate a raw API key and return the user, or None."""
    hashed = _hash_key(raw)
    keys = _load_api_keys()
    entry = keys.get(hashed)
    if entry:
        return AuthUser(
            user_id=entry["user_id"],
            role=entry.get("role", "user"),
            via="api_key",
        )
    return None


def revoke_api_key(raw: str) -> bool:
    """Revoke an API key. Returns True if found and removed."""
    hashed = _hash_key(raw)
    keys = _load_api_keys()
    if hashed in keys:
        del keys[hashed]
        _save_api_keys(keys)
        return True
    return False


# ---------------------------------------------------------------------------
# JWT tokens
# ---------------------------------------------------------------------------


def create_access_token(user_id: str, role: str = "user") -> str:
    """Create a JWT access token."""
    expire = datetime.now(timezone.utc) + timedelta(minutes=settings.access_token_expire_minutes)
    payload = {
        "sub": user_id,
        "role": role,
        "exp": expire,
        "iat": datetime.now(timezone.utc),
    }
    return jwt.encode(payload, settings.jwt_secret_key, algorithm=settings.jwt_algorithm)


def decode_access_token(token: str) -> Optional[AuthUser]:
    """Decode and validate a JWT token. Returns AuthUser or None."""
    try:
        payload = jwt.decode(
            token,
            settings.jwt_secret_key,
            algorithms=[settings.jwt_algorithm],
        )
        return AuthUser(
            user_id=payload["sub"],
            role=payload.get("role", "user"),
            via="jwt",
        )
    except jwt.ExpiredSignatureError:
        logger.debug("jwt_expired")
        return None
    except jwt.InvalidTokenError as e:
        logger.debug("jwt_invalid", error=str(e))
        return None


# ---------------------------------------------------------------------------
# FastAPI dependencies
# ---------------------------------------------------------------------------


async def get_current_user(
    bearer: Optional[HTTPAuthorizationCredentials] = Security(_bearer_scheme),
    api_key: Optional[str] = Security(_api_key_header),
) -> AuthUser:
    """FastAPI dependency that authenticates via JWT or API key.

    In development mode (environment=development), authentication is
    optional and falls back to a default admin user.
    """
    # Try API key first
    if api_key:
        user = validate_api_key(api_key)
        if user:
            return user
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid API key",
        )

    # Try JWT bearer token
    if bearer:
        user = decode_access_token(bearer.credentials)
        if user:
            return user
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or expired token",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # No credentials provided
    if settings.is_development:
        return AuthUser(user_id="dev-user", role="admin", via="dev_fallback")

    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Authentication required (Bearer token or X-API-Key header)",
        headers={"WWW-Authenticate": "Bearer"},
    )


async def require_admin(user: AuthUser = Depends(get_current_user)) -> AuthUser:
    """FastAPI dependency that requires admin role."""
    if not user.is_admin:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Admin privileges required",
        )
    return user
