"""The cookies a persona already has: a decision about the persona, made here.

MOVED FROM THE CONSUMERS IN 36.x. Three byte-identical copies of this logic
lived in `invisible_playwright`, `invisible_selenium` and `invisible_puppeteer`
(`_recaptcha_seed.py`), and each took the Google CONSENT cookie's language
from the raw locale TAG the caller held, while `navigator.language` came from
the language list this package decides. Now the data is built here, as a pure
function of the persona and the session language, and each consumer keeps only
the call that hands the list to its own driver.

Consumes the Bayesian-sampled `browsing_history` from the persona Profile
(see `_fpforge/_sampler.py:derive_browsing_history`). For each visited site it
builds 1-5 realistic cookies whose composition is chosen by the site's
`cookie_profile` tag (analytics-only / consent / bot-defense / etc.). All
values are seeded deterministically from the persona seed, so a given persona
always presents the SAME cookies across sessions.

In addition it always builds 5 cookies on .google.com (NID, CONSENT, SOCS,
_GRECAPTCHA, ENID). It excludes 1P_JAR, which Google deprecated in 2022 -
including it now is an anachronism flag.

No Playwright, no driver objects: the result is a list of plain dicts in the
shape Playwright's `add_cookies` and Juggler's `setCookies` both take.
"""
from __future__ import annotations

import datetime
import random
import time
from typing import Any, List, Optional

from ._locale import SessionLocale

from .seedmix import sub_seed

# URL-safe base64 alphabet (no padding chars).
_B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
_HEX_ALPHABET = "0123456789abcdef"


#: The EEA plus the UK and Switzerland, i.e. every country where a real Google
#: CONSENT cookie carries `<lang>+<COUNTRY>` rather than the `en+FX` a non-EU
#: visitor gets. A finite, knowable set; the alternative was a 22-row timezone
#: table in the wrapper that silently answered "non-EU English" for every
#: country it did not list.
CONSENT_REGION_COUNTRIES = frozenset({
    "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR",
    "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK",
    "SI", "ES", "SE",            # EU 27
    "IS", "LI", "NO",            # EEA
    "GB", "CH",                  # UK and Switzerland behave the same way here
})


def _consent_region_lang(locale: SessionLocale) -> "tuple[str, str]":
    """`(region_token, lang)` for the Google CONSENT cookie, from the DECISION.

    ⛔ THE LANGUAGE IS `primary`'s, NEVER THE REQUESTED TAG'S. Until 36.x this
    took the tag the caller held (as the public `consent_region_lang`), while
    `navigator.language` came from the decided list: the two could name
    different languages, and a page reading both saw them disagree. Reading
    `primary` makes the cookie say the language the page reads.

    THE REGION IS `region`, NOT A PART OF `primary`, and that is deliberate.
    The CONSENT region is WHERE consent was given, which Google takes from the
    visitor's country, not from the language: a French egress decides
    "fr, fr-FR, ..." whose first entry has no region at all, and taking the
    region from `primary` would have turned every French, Czech, Slovak,
    Croatian and Irish session into a non-EU `FX` one. `region` is the region
    of the tag the decision started from (the egress country, for "auto").

    WHY IT IS NOT A TIMEZONE TABLE. This used to live in the wrapper as a
    22-row IANA timezone table (`_TZ_TO_REGION`): a Romanian session resolved
    `ro-RO` for `navigator.language` and fell through to `("FX", "en")` for the
    cookie, because `Europe/Bucharest` was not one of the 22.

    No region known means a non-EU English visitor, the `en+FX` that is also
    what a session with no language asked gets.
    """
    lang = locale.primary.split("-")[0].lower()
    if locale.region in CONSENT_REGION_COUNTRIES:
        return (locale.region, lang)
    return ("FX", lang if locale.region else "en")


def _utc_from(ts: float) -> "datetime.datetime":
    """UTC datetime for an epoch, without the deprecated constructor.

    `datetime.utcfromtimestamp` has been deprecated since 3.12 and returns a
    NAIVE datetime, which is the reason it is going away: the value carries no
    sign of being UTC, so any later arithmetic silently treats it as local.
    Only `.strftime("%Y%m%d")` is taken from it here, so the two spell the same
    string - the point is that the CI matrix now runs 3.14.
    """
    return datetime.datetime.fromtimestamp(ts, datetime.timezone.utc)


def _b64_rand(rng: random.Random, length: int) -> str:
    return "".join(rng.choice(_B64_ALPHABET) for _ in range(length))


def _hex_rand(rng: random.Random, length: int) -> str:
    return "".join(rng.choice(_HEX_ALPHABET) for _ in range(length))


def _yyyymmdd_utc(ts: int) -> str:
    return _utc_from(ts).strftime("%Y%m%d")


# ---------------------------------------------------------------------------
# .google.com cookie batch (always present, regardless of browsing history)
# ---------------------------------------------------------------------------

def _google_cookies(rng: random.Random, now: int,
                    locale: SessionLocale) -> List[dict]:
    consent_age = rng.randint(60, 720) * 86400
    region, lang = _consent_region_lang(locale)
    # NID 3-digit prefix range broadened to 100-540 to cover historical NID
    # versions (137, 105, 511, 525 etc. observed in real captures).
    return [
        {"name": "NID",
         "value": f"{rng.randint(100, 540)}={_b64_rand(rng, 178)}",
         "domain": ".google.com", "path": "/",
         "expires": now + 180 * 86400,
         "httpOnly": True, "secure": True, "sameSite": "None"},
        {"name": "CONSENT",
         "value": f"YES+cb.{_yyyymmdd_utc(now - consent_age)}-"
                  f"{rng.randint(10, 19):02d}-p{rng.randint(0, 9)}."
                  f"{lang}+{region}+{rng.randint(100, 999)}",
         "domain": ".google.com", "path": "/",
         "expires": now + 395 * 86400,
         "secure": True, "sameSite": "Lax"},
        # 1P_JAR removed: Google deprecated it in 2022. Including it now is
        # an anachronism flag for fingerprinters that look at cookie freshness.
        {"name": "SOCS",
         "value": f"CAES{_b64_rand(rng, 56)}",
         "domain": ".google.com", "path": "/",
         "expires": now + 395 * 86400,
         "secure": True, "sameSite": "Lax"},
        {"name": "_GRECAPTCHA",
         "value": _b64_rand(rng, 124),
         "domain": ".google.com", "path": "/",
         "expires": now + 180 * 86400,
         "secure": True, "sameSite": "None"},
        {"name": "ENID",
         "value": _b64_rand(rng, 252),
         "domain": ".google.com", "path": "/",
         "expires": now + 395 * 86400,
         "httpOnly": True, "secure": True, "sameSite": "Lax"},
    ]


# ---------------------------------------------------------------------------
# Per-site cookie generators (recipes keyed by site["cookie_profile"])
# ---------------------------------------------------------------------------

def _norm_domain(domain: str) -> str:
    return domain if domain.startswith(".") else "." + domain


def _ga_cookie(rng: random.Random, now: int, domain: str) -> dict:
    first_age = rng.randint(7, 395) * 86400
    return {"name": "_ga",
            "value": f"GA1.2.{rng.randint(100000000, 999999999)}.{now - first_age}",
            "domain": domain, "path": "/",
            "expires": now + 395 * 86400,
            "secure": True, "sameSite": "Lax"}


def _gid_cookie(rng: random.Random, now: int, domain: str) -> dict:
    return {"name": "_gid",
            "value": f"GA1.2.{rng.randint(100000000, 999999999)}.{now - rng.randint(60, 86400)}",
            "domain": domain, "path": "/",
            "expires": now + 86400,
            "secure": True, "sameSite": "Lax"}


def _cf_bm_cookie(rng: random.Random, now: int, domain: str) -> dict:
    return {"name": "__cf_bm",
            "value": f"{_b64_rand(rng, 43)}.{rng.randint(1700000000, now)}-1-1-1-1",
            "domain": domain, "path": "/",
            "expires": now + 1800,
            "secure": True, "sameSite": "None"}


def _onetrust_cookie(rng: random.Random, now: int, domain: str) -> dict:
    age_d = rng.randint(7, 365)
    iso = _utc_from(now - age_d * 86400).strftime(
        "%Y-%m-%dT%H:%M:%S.000Z"
    )
    return {"name": "OptanonAlertBoxClosed",
            "value": iso,
            "domain": domain, "path": "/",
            "expires": now + 395 * 86400,
            "secure": True, "sameSite": "Lax"}


def _cookieyes_cookie(rng: random.Random, now: int, domain: str) -> dict:
    return {"name": "cookieyes-consent",
            "value": "consentid:" + _b64_rand(rng, 28) +
                     ",consent:yes,action:yes,necessary:yes,functional:yes,analytics:yes",
            "domain": domain, "path": "/",
            "expires": now + 395 * 86400,
            "secure": True, "sameSite": "Lax"}


def _clarity_cookie(rng: random.Random, now: int, domain: str) -> dict:
    return {"name": "_clck",
            "value": f"{_hex_rand(rng, 8)}|2|f{rng.randint(10, 99)}|0|"
                     f"{now - rng.randint(60, 180) * 86400}",
            "domain": domain, "path": "/",
            "expires": now + 365 * 86400,
            "secure": True, "sameSite": "Lax"}


def _fbp_cookie(rng: random.Random, now: int, domain: str) -> dict:
    """Facebook Pixel _fbp = fb.<subdomain_index>.<unix_ms>.<random_int>"""
    return {"name": "_fbp",
            "value": f"fb.1.{(now - rng.randint(60, 30*86400)) * 1000}."
                     f"{rng.randint(100000000, 9999999999)}",
            "domain": domain, "path": "/",
            "expires": now + 90 * 86400,
            "secure": True, "sameSite": "Lax"}


def _gtm_cookie(rng: random.Random, now: int, domain: str) -> dict:
    """_dc_gtm_<container_id>=1 - Google Tag Manager throttle flag."""
    container = f"UA-{rng.randint(10000000, 99999999)}-{rng.randint(1, 9)}"
    return {"name": f"_dc_gtm_{container}",
            "value": "1",
            "domain": domain, "path": "/",
            "expires": now + 60,
            "secure": True, "sameSite": "Lax"}


def _hssrc_cookie(rng: random.Random, now: int, domain: str) -> dict:
    """HubSpot referrer flag - small int."""
    return {"name": "__hssrc",
            "value": str(rng.randint(1, 5)),
            "domain": domain, "path": "/",
            "expires": now + 1800,
            "secure": True, "sameSite": "Lax"}


def _cookies_for_profile(profile: str, rng: random.Random,
                         now: int, domain: str) -> List[dict]:
    """Map a cookie_profile tag (from browsing_pool.json) to concrete cookies.

    Each recipe is a realistic combination observed on real production sites
    in that category. Cookie age and sub-recipe variance (e.g., OneTrust vs
    CookieYes for consent banner) are deterministic from rng.
    """
    domain = _norm_domain(domain)
    if profile == "minimal":
        return [_ga_cookie(rng, now, domain)]
    if profile == "ga_only":
        out = [_ga_cookie(rng, now, domain), _gid_cookie(rng, now, domain)]
        # 30% chance of GTM helper paired with GA
        if rng.random() < 0.3:
            out.append(_gtm_cookie(rng, now, domain))
        return out
    if profile == "ga_cf":
        return [_ga_cookie(rng, now, domain), _cf_bm_cookie(rng, now, domain)]
    if profile == "ga_consent":
        out = [_ga_cookie(rng, now, domain), _gid_cookie(rng, now, domain)]
        out.append(_onetrust_cookie(rng, now, domain) if rng.random() < 0.5
                   else _cookieyes_cookie(rng, now, domain))
        if rng.random() < 0.4:
            out.append(_gtm_cookie(rng, now, domain))
        return out
    if profile == "ga_consent_clarity":
        # Heavy-tracking site profile: GA + Clarity + consent + often FB pixel
        out = [_ga_cookie(rng, now, domain), _gid_cookie(rng, now, domain),
               _clarity_cookie(rng, now, domain)]
        out.append(_onetrust_cookie(rng, now, domain) if rng.random() < 0.5
                   else _cookieyes_cookie(rng, now, domain))
        if rng.random() < 0.5:
            out.append(_fbp_cookie(rng, now, domain))
        if rng.random() < 0.4:
            out.append(_gtm_cookie(rng, now, domain))
        if rng.random() < 0.25:
            out.append(_hssrc_cookie(rng, now, domain))
        return out
    # Unknown profile -> safe fallback
    return [_ga_cookie(rng, now, domain)]


# ---------------------------------------------------------------------------
# Public builder
# ---------------------------------------------------------------------------

def persona_cookies(profile: Any,
                    locale: "SessionLocale | str | None",
                    *,
                    now: Optional[int] = None) -> List[dict]:
    """The full cookie list a persona arrives with, as plain dicts.

    Args:
        profile: the persona, a ``Profile`` from ``generate_profile`` (or any
            object with ``seed`` and ``browsing_history``). The seed drives
            every value; the history, a list of ``{name, category,
            cookie_profile}`` dicts, adds the per-site cookies. An empty
            history gives only the 5 .google.com cookies.
        locale: the session language, the ``SessionLocale`` a launch decided
            (``prepare_session_geo(...).locale``). A tag is accepted and
            decided through ``decide_session_locale``, the same derivation;
            "auto" is refused. The CONSENT cookie's ``lang+region`` token reads
            its ``primary`` language and its region ("it-IT" -> "it+IT",
            "en-US" -> "en+FX").
        now: unix-seconds timestamp; defaults to the current time. Pin it for
            tests.

    Deterministic: the same seed, history, locale and ``now`` give the same
    list. Handing it to a browser is the consumer's part, and the only part
    it keeps.
    """
    session_locale = SessionLocale.of(locale)
    seed = int(getattr(profile, "seed"))
    history = list(getattr(profile, "browsing_history", None) or [])
    ts = now if now is not None else int(time.time())
    cookies: List[dict] = []

    # 5 .google.com cookies (always) - CONSENT lang from the decided language
    rng_g = random.Random(sub_seed(seed, "google"))
    cookies.extend(_google_cookies(rng_g, ts, session_locale))

    # Per-site cookies (deterministic from seed x domain)
    for site in history:
        rng_d = random.Random(sub_seed(seed, f"dom:{site['name']}"))
        cookies.extend(_cookies_for_profile(
            site.get("cookie_profile", "minimal"), rng_d, ts, site["name"]
        ))
    return cookies
