from __future__ import annotations

import importlib.util
import os
import sqlite3
from pathlib import Path

import pytest
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes


MODULE_PATH = Path(__file__).parent.parent / "tme_cookie.py"


def load_module():
    spec = importlib.util.spec_from_file_location("tme_cookie_under_test", MODULE_PATH)
    module = importlib.util.module_from_spec(spec)
    assert spec.loader is not None
    spec.loader.exec_module(module)
    return module


def encrypt_v10(key: bytes, iv: bytes, plain: bytes) -> bytes:
    padder = padding.PKCS7(128).padder()
    padded = padder.update(plain) + padder.finalize()
    encryptor = Cipher(algorithms.AES(key), modes.CBC(iv)).encryptor()
    return b"v10" + iv + encryptor.update(padded) + encryptor.finalize()


def test_decrypts_rms_v10_cookie_with_embedded_iv_and_mac_prefix():
    module = load_module()
    key = bytes.fromhex("628846145161f2c0ab14544e97f0c954")
    iv = b"0123456789abcdef"
    mac_prefix = bytes(range(32))
    encrypted = encrypt_v10(key, iv, mac_prefix + b"session-value")

    assert module._decrypt_cookie_value(encrypted, key) == "session-value"


def test_cache_survives_ttl_lazy_invalidation(tmp_path, monkeypatch):
    """ADR-014 R1：cache 不按 TTL 丢弃，跨小时仍命中。"""
    module = load_module()
    monkeypatch.setattr(module, "_CACHE_DIR", tmp_path)
    module._write_cache("kapi", "a=b", now=100)

    assert module._read_cache("kapi", ttl=60, now=1_000_000) == "a=b"
    assert (tmp_path / "kapi.json").stat().st_mode & 0o777 == 0o600


def test_force_refresh_skips_cache(tmp_path, monkeypatch):
    """R1：--refresh-cookie（force_refresh）跳过 cache 读取。"""
    module = load_module()
    monkeypatch.setattr(module, "_CACHE_DIR", tmp_path)
    monkeypatch.delenv("KAPI_TMEOA_COOKIE", raising=False)
    monkeypatch.delenv("KAPI_TMEOA_COOKIE_FILE", raising=False)
    module._write_cache("kapi", "stale=1", now=100)
    monkeypatch.setattr(module, "_read_chrome_cookies", lambda d: {"fresh": "1"})

    result = module.get_cookie(
        "kapi",
        ["tmeoa.com"],
        env_prefix="KAPI_TMEOA",
        cache_name="kapi",
        allow_browser=True,
        force_refresh=True,
    )

    assert result == {"cookie": "fresh=1", "source": "keychain"}


def test_env_highest_priority(tmp_path, monkeypatch):
    module = load_module()
    monkeypatch.setattr(module, "_CACHE_DIR", tmp_path)
    monkeypatch.setenv("KAPI_TMEOA_COOKIE", "env=1")

    result = module.get_cookie("kapi", ["tmeoa.com"], env_prefix="KAPI_TMEOA")

    assert result == {"cookie": "env=1", "source": "env"}


def test_sql_exact_domain_match_and_parent_first(tmp_path, monkeypatch):
    """R2：host_key IN 精确匹配（无 LIKE 假域误配）+ 父域同名 cookie 优先。"""
    module = load_module()
    key = module._derive_aes_key(b"dummy")

    db = tmp_path / "cookies.db"
    conn = sqlite3.connect(db)
    conn.execute(
        "CREATE TABLE cookies (host_key TEXT, name TEXT, encrypted_value BLOB)"
    )
    rows = [
        (".tmeoa.com", "ticket", encrypt_v10(key, b"iv-parent-000001", b"parent-value")),
        ("kapi.tmeoa.com", "ticket", encrypt_v10(key, b"iv-child--000001", b"child-value")),
        ("evil-tmeoa.com", "ticket", encrypt_v10(key, b"iv-evil---000001", b"evil-value")),
        (".tmeoa.com", "sid", encrypt_v10(key, b"iv-sid----000001", b"sid-value")),
    ]
    conn.executemany("INSERT INTO cookies VALUES (?, ?, ?)", rows)
    conn.commit()
    conn.close()

    monkeypatch.setattr(module, "_CHROME_COOKIE_DB", db)
    monkeypatch.setattr(
        module, "_get_chrome_safe_storage_key", lambda: b"dummy"
    )

    cookies = module._read_chrome_cookies(["tmeoa.com", "kapi.tmeoa.com"])

    assert cookies["ticket"] == "parent-value"  # 父域胜出
    assert cookies["sid"] == "sid-value"
    assert "evil" not in str(cookies.values())  # 假域不进结果


def test_keychain_failure_raises_without_cdp_by_default(tmp_path, monkeypatch):
    """R3：默认链路零 CDP——Keychain 失败直接报错，不调 node 子进程。"""
    module = load_module()
    monkeypatch.setattr(module, "_CACHE_DIR", tmp_path)
    monkeypatch.delenv("KAPI_TMEOA_COOKIE", raising=False)
    monkeypatch.delenv("KAPI_TMEOA_COOKIE_FILE", raising=False)
    monkeypatch.setattr(
        module,
        "_read_chrome_cookies",
        lambda _domains: (_ for _ in ()).throw(module.CookieError("keychain failed")),
    )
    cdp_calls = []
    monkeypatch.setattr(
        module,
        "_read_cookies_via_cdp",
        lambda _domains, recover=False: cdp_calls.append(recover) or {"x": "1"},
    )

    with pytest.raises(module.CookieError) as exc:
        module.get_cookie(
            "kapi",
            ["tmeoa.com"],
            env_prefix="KAPI_TMEOA",
            cache_name="kapi",
            allow_browser=True,
        )

    assert cdp_calls == []
    msg = str(exc.value)
    assert "Chrome 中登录" in msg  # 指引 1：重登
    assert "KAPI_TMEOA_COOKIE" in msg  # 指引 2：env 覆盖
    assert "--login" in msg  # 指引 3：显式浏览器恢复


def test_invalid_keychain_session_raises_without_browser_hijack(tmp_path, monkeypatch):
    """R4：keychain cookie session 无效 → 先试 CDP 只读（不点登录），失败才报错。

    断言：recover=True 永不被调；CDP 只读也无效时报 CookieError。
    """
    module = load_module()
    monkeypatch.setattr(module, "_CACHE_DIR", tmp_path)
    monkeypatch.delenv("KAPI_TMEOA_COOKIE", raising=False)
    monkeypatch.delenv("KAPI_TMEOA_COOKIE_FILE", raising=False)
    monkeypatch.setattr(module, "_read_chrome_cookies", lambda _domains: {"old": "bad"})
    recover_calls = []

    def read_cdp(_domains, recover=False):
        recover_calls.append(recover)
        if recover:
            raise AssertionError("登录恢复绝不允许自动触发")
        return {"stale": "also-bad"}

    monkeypatch.setattr(module, "_read_cookies_via_cdp", read_cdp)

    with pytest.raises(module.CookieError) as exc:
        module.get_cookie(
            "kapi",
            ["tmeoa.com"],
            env_prefix="KAPI_TMEOA",
            cache_name="kapi",
            validator=lambda cookie: False,  # 一切 session 都无效
        )

    assert recover_calls == [False]  # 只有 --read，无 recover
    assert "session" in str(exc.value)


def test_keychain_stale_but_cdp_read_valid(tmp_path, monkeypatch):
    """ADR-014 补充：DB 落库滞后时，Keychain 无效 → CDP 只读拿到新票。"""
    module = load_module()
    monkeypatch.setattr(module, "_CACHE_DIR", tmp_path)
    monkeypatch.delenv("KAPI_TMEOA_COOKIE", raising=False)
    monkeypatch.delenv("KAPI_TMEOA_COOKIE_FILE", raising=False)
    monkeypatch.setattr(module, "_read_chrome_cookies", lambda _domains: {"old": "bad"})
    monkeypatch.setattr(
        module, "_read_cookies_via_cdp", lambda _domains, recover=False: {"fresh": "good"}
    )

    result = module.get_cookie(
        "kapi",
        ["tmeoa.com"],
        env_prefix="KAPI_TMEOA",
        cache_name="kapi",
        validator=lambda cookie: "fresh=good" in cookie,
    )

    assert result == {"cookie": "fresh=good", "source": "cdp"}


def test_login_flag_reaches_cdp(tmp_path, monkeypatch):
    """R3：仅 allow_cdp=True（--login）可达 CDP。"""
    module = load_module()
    monkeypatch.setattr(module, "_CACHE_DIR", tmp_path)
    monkeypatch.delenv("KAPI_TMEOA_COOKIE", raising=False)
    monkeypatch.delenv("KAPI_TMEOA_COOKIE_FILE", raising=False)
    monkeypatch.setattr(module, "_read_chrome_cookies", lambda _domains: {"old": "bad"})
    monkeypatch.setattr(
        module, "_read_cookies_via_cdp", lambda _domains, recover=False: {"new": "good"}
    )

    result = module.get_cookie(
        "kapi",
        ["tmeoa.com"],
        env_prefix="KAPI_TMEOA",
        cache_name="kapi",
        allow_cdp=True,
        validator=lambda cookie: cookie == "new=good",
    )

    assert result == {"cookie": "new=good", "source": "cdp"}
