"""DomClick session cookie management — load/save/invalidate encrypted cookies.

Empirically verified live (2026-07-04): DomClick's QRATOR anti-bot reputation-block
(даже на прокси-IP, уже помеченном как suspicious) полностью обходится инъекцией
cookies валидной аутентифицированной test-аккаунт сессии (Sber ID login) перед
навигацией на карточку. Cookies хранятся зашифрованно (pgp_sym_encrypt) в
domclick_session_cookies (зеркалит cian_session_cookies, но account_cas_id вместо
account_user_id).

MVP: без verify_session (в отличие от app.services.cian_session). DomClick-
верификация требует реального browser-фетча (SSR-стейт страницы), а не простого
curl_cffi-запроса как у Cian Valuation Calculator — вне рамок этой итерации,
future enhancement.
"""

from __future__ import annotations

import json
import logging

from sqlalchemy import text
from sqlalchemy.orm import Session

from app.core.config import settings

logger = logging.getLogger(__name__)

# Cookies критичные для DomClick auth (Sber ID) — фильтр перед сохранением.
# Список составлен по реальному DevTools/Cookie-Editor дампу авторизованной
# test-аккаунт сессии (Sber ID login), 2026-07-04.
DOMCLICK_REQUIRED_COOKIES: set[str] = {
    "qrator_jsid2",
    "CAS_ID",
    "CAS_ID_SIGNED",
    "sessionId",
    "UNIQ_SESSION_ID",
    "_visitId",
    "ns_session",
    "RETENTION_COOKIES_NAME",
    "regionName",
    "region",
    "currentRegionGuid",
    "currentLocalityGuid",
    "adtech_uid",
    "ftgl_cookie_id",
    "_ym_uid",
    "_ym_d",
}


def save_session(
    db: Session,
    account_cas_id: int,
    cookies: dict[str, str],
    ttl_days: int = 30,
) -> None:
    """Encrypt cookies via pgp_sym_encrypt and UPSERT into domclick_session_cookies.

    Uses settings.cookie_encryption_key as the encryption secret.
    Никогда не логирует сырые значения cookies.
    """
    cookies_json = json.dumps(cookies)
    db.execute(
        text("""
            INSERT INTO domclick_session_cookies (
                account_cas_id,
                cookies_encrypted,
                expires_at_estimate,
                uploaded_at
            ) VALUES (
                CAST(:cas_id AS bigint),
                pgp_sym_encrypt(:cookies_json, :key),
                NOW() + (CAST(:ttl_days AS int) || ' days')::interval,
                NOW()
            )
            ON CONFLICT (account_cas_id) DO UPDATE SET
                cookies_encrypted    = EXCLUDED.cookies_encrypted,
                expires_at_estimate  = EXCLUDED.expires_at_estimate,
                uploaded_at          = NOW(),
                last_invalid_at      = NULL
        """),
        {
            "cas_id": account_cas_id,
            "cookies_json": cookies_json,
            "key": settings.cookie_encryption_key,
            "ttl_days": ttl_days,
        },
    )
    db.commit()
    logger.info(
        "DomClick cookies saved for casId=%s (count=%d, ttl=%d days)",
        account_cas_id,
        len(cookies),
        ttl_days,
    )


def load_session(db: Session) -> dict[str, str] | None:
    """Load most-recently-uploaded valid DomClick cookies (decrypt).

    Returns dict[cookie_name, cookie_value] или None если нет валидной session.
    Выбирает только записи где expires_at_estimate > NOW() и сессия не
    была инвалидирована после последнего upload'а.
    """
    row = (
        db.execute(
            text("""
            SELECT
                account_cas_id,
                pgp_sym_decrypt(cookies_encrypted, :key)::text AS cookies_json,
                expires_at_estimate
            FROM domclick_session_cookies
            WHERE expires_at_estimate > NOW()
              AND (last_invalid_at IS NULL OR last_invalid_at < uploaded_at)
            ORDER BY uploaded_at DESC
            LIMIT 1
        """),
            {"key": settings.cookie_encryption_key},
        )
        .mappings()
        .first()
    )

    if row is None:
        logger.warning("No valid DomClick session cookies in DB")
        return None

    cookies: dict[str, str] = json.loads(row["cookies_json"])

    # Обновляем last_used_at — не критично, игнорируем ошибки.
    try:
        db.execute(
            text(
                "UPDATE domclick_session_cookies SET last_used_at = NOW()"
                " WHERE account_cas_id = CAST(:cas_id AS bigint)"
            ),
            {"cas_id": row["account_cas_id"]},
        )
        db.commit()
    except Exception as exc:
        logger.warning("Failed to update last_used_at for casId=%s: %s", row["account_cas_id"], exc)

    logger.info(
        "DomClick cookies loaded for casId=%s (count=%d)",
        row["account_cas_id"],
        len(cookies),
    )
    return cookies


def mark_session_invalid(db: Session, account_cas_id: int) -> None:
    """Flag session как expired/invalid (например после блока во время scrape)."""
    db.execute(
        text(
            "UPDATE domclick_session_cookies SET last_invalid_at = NOW()"
            " WHERE account_cas_id = CAST(:cas_id AS bigint)"
        ),
        {"cas_id": account_cas_id},
    )
    db.commit()
    logger.warning("DomClick session marked invalid for casId=%s", account_cas_id)
