from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path

from .browser_identity import (
    CookiePolicy,
    Persona,
    cookie_behavior_value,
    cookie_session_only,
    get_cookie_policy,
    get_persona,
    is_blend_persona,
)


@dataclass
class FirefoxHardening:
    # Proxy & network always-on
    force_proxy: bool = True
    disable_webrtc: bool = True   # always strongly recommended; leaks real IP

    # Hardened block mode — overrides persona spoofing
    resist_fingerprinting: bool = True
    disable_telemetry: bool = True
    first_party_isolation: bool = True
    strict_tracking_protection: bool = True
    clear_on_shutdown: bool = True
    timezone_utc: bool = True

    # Persona / cookie selectors (added in "spoof when possible" mode)
    persona_key: str = "custom"        # see browser_identity.PERSONAS
    cookie_mode: str = "block_third_party"  # see browser_identity.COOKIE_MODES

    # Optional Firefox-managed user-agent override even outside of a persona
    user_agent_override: str = ""

    # Filled in by ``apply_persona`` when persona_key starts with "blend_".
    persona: Persona | None = field(default=None, repr=False, compare=False)


def apply_persona(hard: FirefoxHardening) -> FirefoxHardening:
    """Mutate hardening flags consistent with the persona choice.

    Blend-in personas relax the "stand out" toggles (RFP, FPI, strict TP) but
    keep the IP-leakers off (WebRTC, geolocation, etc.). Cookie mode is
    honored. The result is a profile that looks like a normal browser.
    """
    if hard.persona_key == "hardened":
        hard.resist_fingerprinting = True
        hard.first_party_isolation = True
        hard.strict_tracking_protection = True
        hard.timezone_utc = True
        hard.persona = None
        return hard

    if is_blend_persona(hard.persona_key):
        hard.resist_fingerprinting = False  # RFP itself is a fingerprint
        hard.first_party_isolation = False   # breaks logins on many sites
        hard.strict_tracking_protection = False  # Firefox default ETP is enough
        hard.timezone_utc = False
        hard.persona = get_persona(hard.persona_key)
        if hard.persona and not hard.user_agent_override:
            hard.user_agent_override = hard.persona.user_agent
        return hard

    # custom — honor the individual flags as-is.
    hard.persona = None
    return hard


def _bool(v: bool) -> str:
    return "true" if v else "false"


def build_user_js(
    proxy_host: str,
    proxy_port: int,
    hard: FirefoxHardening,
) -> str:
    hard = apply_persona(hard)
    lines: list[str] = [
        "// Managed by Proxy God. Changes are overwritten on next launch.",
        # Always-on baseline (no UX cost)
        'user_pref("app.normandy.enabled", false);',
        'user_pref("app.shield.optoutstudies.enabled", false);',
        'user_pref("browser.newtabpage.activity-stream.feeds.telemetry", false);',
        'user_pref("browser.newtabpage.activity-stream.telemetry", false);',
        'user_pref("browser.ping-centre.telemetry", false);',
        'user_pref("toolkit.telemetry.archive.enabled", false);',
        'user_pref("toolkit.telemetry.bhrPing.enabled", false);',
        'user_pref("toolkit.telemetry.enabled", false);',
        'user_pref("toolkit.telemetry.firstShutdownPing.enabled", false);',
        'user_pref("toolkit.telemetry.hybridContent.enabled", false);',
        'user_pref("toolkit.telemetry.newProfilePing.enabled", false);',
        'user_pref("toolkit.telemetry.shutdownPingSender.enabled", false);',
        'user_pref("toolkit.telemetry.unified", false);',
        'user_pref("datareporting.healthreport.uploadEnabled", false);',
        'user_pref("datareporting.policy.dataSubmissionEnabled", false);',
        'user_pref("network.trr.mode", 5);',
        'user_pref("network.captive-portal-service.enabled", false);',
        'user_pref("geo.enabled", false);',
        'user_pref("media.navigator.enabled", false);',
        'user_pref("dom.battery.enabled", false);',
        'user_pref("dom.gamepad.enabled", false);',
        'user_pref("dom.netinfo.enabled", false);',
        # WebGL allowed by default (blocking it makes you stand out massively)
        'user_pref("webgl.enable-debug-renderer-info", false);',
    ]

    if hard.force_proxy:
        lines.extend(
            [
                'user_pref("network.proxy.type", 1);',
                'user_pref("network.proxy.share_proxy_settings", true);',
                f'user_pref("network.proxy.http", "{proxy_host}");',
                f'user_pref("network.proxy.http_port", {int(proxy_port)});',
                f'user_pref("network.proxy.ssl", "{proxy_host}");',
                f'user_pref("network.proxy.ssl_port", {int(proxy_port)});',
                'user_pref("network.proxy.ftp", "");',
                'user_pref("network.proxy.ftp_port", 0);',
                'user_pref("network.proxy.socks", "");',
                'user_pref("network.proxy.socks_port", 0);',
                'user_pref("network.proxy.socks_version", 5);',
                'user_pref("network.proxy.socks_remote_dns", false);',
                'user_pref("network.proxy.no_proxies_on", "");',
            ]
        )

    if hard.disable_webrtc:
        # Off regardless of persona — WebRTC leaks real IP through STUN.
        lines.extend(
            [
                'user_pref("media.peerconnection.enabled", false);',
                'user_pref("media.peerconnection.ice.default_address_only", true);',
                'user_pref("media.peerconnection.ice.no_host", true);',
            ]
        )

    if hard.resist_fingerprinting:
        lines.extend(
            [
                'user_pref("privacy.resistFingerprinting", true);',
                'user_pref("privacy.resistFingerprinting.letterboxing", true);',
                'user_pref("privacy.window.maxInnerWidth", 1600);',
                'user_pref("privacy.window.maxInnerHeight", 900);',
            ]
        )

    if hard.first_party_isolation:
        lines.append('user_pref("privacy.firstparty.isolate", true);')

    if hard.disable_telemetry:
        lines.append('user_pref("browser.send_pings", false);')

    if hard.strict_tracking_protection:
        lines.extend(
            [
                'user_pref("privacy.trackingprotection.enabled", true);',
                'user_pref("privacy.trackingprotection.pbmode.enabled", true);',
            ]
        )

    # Persona spoofing (UA, accept-language, screen, platform)
    if hard.persona is not None:
        p = hard.persona
        lines.extend(
            [
                f'user_pref("general.useragent.override", "{p.user_agent}");',
                f'user_pref("intl.accept_languages", "{p.accept_language}");',
                f'user_pref("general.useragent.locale", "{p.locale}");',
                'user_pref("javascript.use_us_english_locale", true);',
                f'user_pref("privacy.window.maxInnerWidth", {int(p.screen_w)});',
                f'user_pref("privacy.window.maxInnerHeight", {int(p.screen_h)});',
                # Hint timezone — Firefox honors TZ env var at launch (set by
                # the launcher) but this pref nudges some site detection too.
                f'user_pref("intl.locale.requested", "{p.locale}");',
            ]
        )
    elif hard.user_agent_override:
        lines.append(
            f'user_pref("general.useragent.override", "{hard.user_agent_override}");'
        )

    # ── Cookie policy ────────────────────────────────────────────────────────
    cp: CookiePolicy = get_cookie_policy(hard.cookie_mode)

    lines.extend([
        f'user_pref("network.cookie.cookieBehavior", {cp.behavior});',
        # pbmode = private-browsing mode uses same behavior
        f'user_pref("network.cookie.cookieBehavior.pbmode", {cp.behavior});',
        # lifetime: 0 = normal, 2 = session-only
        f'user_pref("network.cookie.lifetimePolicy", {cp.lifetime});',
    ])

    # Total Cookie Protection (TCP / Firefox partitioning)
    if cp.partition:
        lines.extend([
            'user_pref("network.cookie.cookieBehavior.optInPartitioning", true);',
            # Also isolate cache, localStorage, IndexedDB per top-level site
            'user_pref("privacy.partition.serviceWorkers", true);',
            'user_pref("privacy.partition.network_state", true);',
            'user_pref("privacy.partition.bloburl_by_default", true);',
        ])
    else:
        lines.extend([
            'user_pref("network.cookie.cookieBehavior.optInPartitioning", false);',
        ])

    # SameSite=None cookies must be Secure (good hygiene regardless of mode)
    lines.append('user_pref("network.cookie.sameSite.noneRequiresSecure", true);')

    # ── Sanitize-on-shutdown ─────────────────────────────────────────────────
    # The CookiePolicy sanitize flag takes priority; the hard.clear_on_shutdown
    # toggle acts as an additional override for cookies+cache even when the
    # cookie mode doesn't request it.
    do_sanitize = cp.sanitize or hard.clear_on_shutdown
    if do_sanitize:
        lines.append('user_pref("privacy.sanitize.sanitizeOnShutdown", true);')
        # Cookies
        lines.append(f'user_pref("privacy.clearOnShutdown.cookies", {_bool(cp.clear_cookies or hard.clear_on_shutdown)});')
        # Cache
        lines.append(f'user_pref("privacy.clearOnShutdown.cache", {_bool(cp.clear_cache or hard.clear_on_shutdown)});')
        # History
        lines.append(f'user_pref("privacy.clearOnShutdown.history", {_bool(cp.clear_history or hard.clear_on_shutdown)});')
        # Downloads list
        lines.append('user_pref("privacy.clearOnShutdown.downloads", true);')
        # Form / search bar autofill data
        lines.append(f'user_pref("privacy.clearOnShutdown.formdata", {_bool(cp.clear_formdata or hard.clear_on_shutdown)});')
        # Active sessions (HTTP auth, logged-in sites)
        lines.append('user_pref("privacy.clearOnShutdown.sessions", true);')
        # localStorage + IndexedDB
        lines.append(f'user_pref("privacy.clearOnShutdown.localStorage", {_bool(cp.clear_ls or hard.clear_on_shutdown)});')
        lines.append(f'user_pref("privacy.clearOnShutdown.indexedDB", {_bool(cp.clear_ls or hard.clear_on_shutdown)});')
        # Service worker registrations
        lines.append('user_pref("privacy.clearOnShutdown.serviceWorkers", true);')
        # Offline web app cache
        lines.append('user_pref("privacy.clearOnShutdown.offlineApps", true);')
    else:
        lines.append('user_pref("privacy.sanitize.sanitizeOnShutdown", false);')

    if hard.timezone_utc:
        lines.append('user_pref("privacy.resistFingerprinting.reduceTimerPrecision", true);')

    lines.append("")
    return "\n".join(lines)


def persona_env(hard: FirefoxHardening) -> dict[str, str]:
    """Environment overrides applied when launching Firefox under a persona.

    Only the timezone needs the OS-level env var (TZ) — Firefox reads it
    even when running under Windows.
    """
    hard = apply_persona(hard)
    if hard.persona is None:
        return {}
    return {"TZ": hard.persona.timezone}


def ensure_firefox_profile(
    profile_dir: Path,
    proxy_host: str,
    proxy_port: int,
    hard: FirefoxHardening,
) -> None:
    profile_dir.mkdir(parents=True, exist_ok=True)
    (profile_dir / "user.js").write_text(
        build_user_js(proxy_host, proxy_port, hard),
        encoding="utf-8",
    )
