# services/auth.py
from typing import Optional, Dict, Any
import secrets

from flask import request, abort, session

from config import BULK_API_KEY
from token_store import load_token, save_token
from tiktok_client import ensure_fresh_token_bundle


def _norm_alias(alias: str) -> str:
    # Canonicalización mínima (no forzamos lower() por si quieres alias "Canal ES")
    a = (alias or "").strip()
    if not a:
        raise RuntimeError("empty_alias")
    return a


def require_bulk_api_key() -> None:
    """
    Autorización para endpoints bulk.
    Acepta X-Api-Key (recomendado) y, para facilitar curl/cron, también api_key en form/query.
    """
    if not BULK_API_KEY:
        # Evita dejar el endpoint abierto por mala config
        abort(401)

    k = (
        request.headers.get("X-Api-Key")
        or request.headers.get("X-API-Key")
        or request.form.get("api_key")
        or request.args.get("api_key")
        or ""
    ).strip()

    # compare_digest evita timing leaks y es más robusto que == para strings
    if not k or not secrets.compare_digest(k, str(BULK_API_KEY)):
        abort(401)


def get_access_token_for_alias(alias: str, *, refresh_if_lt_seconds: int = 300) -> str:
    """
    Devuelve un access_token válido para el alias:
    - load_token(alias)
    - ensure_fresh_token_bundle(...) (refresca si está cerca de expirar)
    - save_token(alias, tok) (persistimos rotación/expiraciones)
    """
    alias = _norm_alias(alias)

    tok = load_token(alias)
    if not tok:
        raise RuntimeError(f"no_token_for_alias:{alias} (haz login OAuth con ?alias={alias})")

    tok = ensure_fresh_token_bundle(tok, refresh_if_lt_seconds=refresh_if_lt_seconds)
    save_token(alias, tok)  # por si rotó refresh_token o cambió expires_at

    at = (tok.get("access_token") or "").strip()
    if not at:
        raise RuntimeError(f"missing_access_token_for_alias:{alias}")

    return at


def require_login_bundle(*, refresh_if_lt_seconds: int = 300) -> Optional[Dict[str, Any]]:
    """
    Devuelve el bundle en sesión (refrescado si hace falta) o None si no hay login.
    Importante: si hay oauth_alias en sesión, persistimos el bundle en token_store
    para que worker/cron tenga siempre refresh_token/expiraciones actualizadas.
    """
    token_data = session.get("tiktok_token")
    if not token_data:
        return None

    try:
        token_data = ensure_fresh_token_bundle(token_data, refresh_if_lt_seconds=refresh_if_lt_seconds)
        session["tiktok_token"] = token_data
        session.modified = True

        # Persistencia server-side para worker/cron
        alias = (session.get("oauth_alias") or "").strip()
        if alias:
            save_token(_norm_alias(alias), token_data)

        return token_data
    except Exception:
        session.pop("tiktok_token", None)
        session.modified = True
        return None


def require_login_access_token(*, refresh_if_lt_seconds: int = 300) -> Optional[str]:
    b = require_login_bundle(refresh_if_lt_seconds=refresh_if_lt_seconds)
    if not b:
        return None

    at = (b.get("access_token") or "").strip()
    if not at:
        # Si el bundle quedó inconsistente, limpiamos sesión
        session.pop("tiktok_token", None)
        session.modified = True
        return None

    return at
