"""Session management — create/read/delete/encrypt/decrypt + CSRF.

Server-side Firestore sessions with AES-256-GCM encrypted __Host-session
cookie and sliding 7-day expiry.
"""

from __future__ import annotations

import base64
import hashlib
import hmac
import logging
import os
import secrets
from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from fastapi import Request

_log = logging.getLogger(__name__)

_SESSION_EXPIRY_DAYS = 7
_SESSION_MAX_AGE = 604800  # 7 days in seconds
_COOKIE_NAME = "__Host-session"

_encryption_key: bytes | None = None


def _get_encryption_key() -> bytes:
    """Load AES-256 key from Secret Manager (cached)."""
    global _encryption_key  # noqa: PLW0603
    if _encryption_key is not None:
        return _encryption_key

    from google.cloud import secretmanager

    client = secretmanager.SecretManagerServiceClient()
    name = "projects/complyform-prod/secrets/session-encryption-key/versions/latest"
    response = client.access_secret_version(request={"name": name})
    _encryption_key = response.payload.data
    return _encryption_key


def _encrypt_session_id(session_id: str) -> str:
    """Encrypt session_id with AES-256-GCM.

    Returns base64(nonce + ciphertext + tag).
    """
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM

    key = _get_encryption_key()
    nonce = os.urandom(12)
    aesgcm = AESGCM(key)
    ciphertext = aesgcm.encrypt(nonce, session_id.encode(), None)
    return base64.b64encode(nonce + ciphertext).decode()


def _decrypt_session_id(encrypted_value: str) -> str | None:
    """Decrypt cookie value. Returns session_id or None on failure."""
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM

    try:
        raw = base64.b64decode(encrypted_value)
        if len(raw) < 13:
            return None
        nonce = raw[:12]
        ciphertext = raw[12:]
        key = _get_encryption_key()
        aesgcm = AESGCM(key)
        plaintext = aesgcm.decrypt(nonce, ciphertext, None)
        return plaintext.decode()
    except Exception:
        _log.debug("Session decryption failed", exc_info=True)
        return None


def create_session(
    email: str,
    customer_id: str,
    tier: str,
    request: Request,
) -> tuple[str, str, dict[str, Any], str]:
    """Create Firestore session data + return components.

    Returns (session_id, encrypted_cookie_value, session_doc, csrf_token).
    """
    session_id = secrets.token_hex(32)
    csrf_token = secrets.token_urlsafe(32)

    client_host = request.client.host if request.client else "unknown"
    ip_hash = hashlib.sha256(client_host.encode()).hexdigest()
    user_agent = request.headers.get("user-agent", "")
    user_agent_hash = hashlib.sha256(user_agent.encode()).hexdigest()

    now = datetime.now(UTC)
    expires_at = now + timedelta(days=_SESSION_EXPIRY_DAYS)

    session_doc: dict[str, Any] = {
        "session_id": session_id,
        "customer_id": customer_id,
        "email": email,
        "tier": tier,
        "csrf_token": csrf_token,
        "ip_hash": ip_hash,
        "user_agent_hash": user_agent_hash,
        "created_at": now.isoformat(),
        "last_accessed_at": now.isoformat(),
        "expires_at": expires_at,
        "theme_preference": None,
    }

    encrypted_value = _encrypt_session_id(session_id)
    return session_id, encrypted_value, session_doc, csrf_token


async def write_session(
    session_id: str,
    session_doc: dict[str, Any],
) -> None:
    """Write session document to Firestore."""
    from backend.shared.firestore import get_db

    await get_db().collection("sessions").document(session_id).set(session_doc)


async def get_session(encrypted_cookie: str) -> dict[str, Any] | None:
    """Decrypt cookie → extract session_id → Firestore lookup.

    Returns session data or None if invalid/expired.
    """
    from backend.shared.firestore import get_db

    session_id = _decrypt_session_id(encrypted_cookie)
    if session_id is None:
        return None

    doc = await get_db().collection("sessions").document(session_id).get()
    if not doc.exists:
        return None

    data: dict[str, Any] = doc.to_dict()  # type: ignore[assignment]

    expires_at = data.get("expires_at")
    if expires_at is None:
        return None

    now = datetime.now(UTC)
    if hasattr(expires_at, "timestamp"):
        # Firestore Timestamp
        exp_dt = (
            expires_at.replace(tzinfo=UTC) if expires_at.tzinfo is None else expires_at
        )
        if exp_dt < now:
            return None
    elif isinstance(expires_at, str):
        try:
            exp_dt = datetime.fromisoformat(expires_at)
            if exp_dt < now:
                return None
        except ValueError, TypeError:
            return None

    return data


async def touch_session(session_id: str) -> None:
    """Update sliding expiry — extend by 7 days."""
    from backend.shared.firestore import get_db

    now = datetime.now(UTC)
    new_expires = now + timedelta(days=_SESSION_EXPIRY_DAYS)
    await (
        get_db()
        .collection("sessions")
        .document(session_id)
        .update(
            {
                "last_accessed_at": now.isoformat(),
                "expires_at": new_expires,
            }
        )
    )


async def delete_session(session_id: str) -> None:
    """Delete Firestore session document."""
    from backend.shared.firestore import get_db

    await get_db().collection("sessions").document(session_id).delete()


def validate_csrf(request_token: str, session_token: str) -> bool:
    """Validate CSRF token using timing-safe comparison."""
    return hmac.compare_digest(request_token, session_token)


def get_cookie_name() -> str:
    """Return the session cookie name."""
    return _COOKIE_NAME


def get_cookie_max_age() -> int:
    """Return cookie Max-Age in seconds."""
    return _SESSION_MAX_AGE
