"""Tests for token-tracker.py (TDD - RED phase first)"""

import importlib
import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))
tt = importlib.import_module("token-tracker")

# fmt: off
_A_JSON = '{"type":"assistant","sessionId":"s1","message":{"model":"claude-sonnet-4-6","role":"assistant","usage":{"input_tokens":1000,"cache_creation_input_tokens":500,"cache_read_input_tokens":2000,"output_tokens":300}},"timestamp":"2026-03-25T10:00:00Z"}'
_U_JSON = '{"type":"user","sessionId":"s1","message":{"role":"user","content":"작업 ID: task-930.1\\n팀: dev1-team\\n수행하세요."},"timestamp":"2026-03-25T09:59:00Z"}'
_UL_JSON = '{"type":"user","sessionId":"s2","message":{"role":"user","content":[{"type":"text","text":"작업 ID: task-930.2\\n팀: dev2-team"}]},"timestamp":"2026-03-25T10:01:00Z"}'
_A2_JSON = '{"type":"assistant","sessionId":"s1","message":{"model":"claude-sonnet-4-6","role":"assistant","usage":{"input_tokens":200,"cache_creation_input_tokens":0,"cache_read_input_tokens":100,"output_tokens":50}},"timestamp":"2026-03-25T10:01:00Z"}'
_UX_JSON = '{"type":"user","sessionId":"x","message":{"role":"user","content":"hi"},"timestamp":"2026-03-25T10:00:00Z"}'
# fmt: on

A = json.loads(_A_JSON)
U = json.loads(_U_JSON)
UL = json.loads(_UL_JSON)
A2 = json.loads(_A2_JSON)
UX = json.loads(_UX_JSON)


def sess(tmp: Path, *lines: dict) -> str:
    p = tmp / "s.jsonl"
    p.write_text("\n".join(json.dumps(l, ensure_ascii=False) for l in lines), encoding="utf-8")
    return str(p)


def ledger(tmp: Path, tasks: dict) -> str:
    total = sum(t["total_tokens"] for t in tasks.values())
    n = len(tasks)
    p = tmp / "l.json"
    avg = total / n if n else 0
    p.write_text(
        json.dumps({"tasks": tasks, "summary": {"total_tokens": total, "total_tasks": n, "avg_tokens_per_task": avg}})
    )
    return str(p)


class TestParseSession:
    def test_aggregation(self, tmp_path: Path) -> None:
        r = tt.parse_session(sess(tmp_path, U, A))
        assert (
            r["input_tokens"],
            r["cache_creation_tokens"],
            r["cache_read_tokens"],
            r["output_tokens"],
            r["message_count"],
        ) == (
            1000,
            500,
            2000,
            300,
            1,
        )  # noqa: E501

    def test_multi_assistant(self, tmp_path: Path) -> None:
        r = tt.parse_session(sess(tmp_path, U, A, A2))
        assert r["input_tokens"] == 1200 and r["output_tokens"] == 350

    def test_required_keys(self, tmp_path: Path) -> None:
        r = tt.parse_session(sess(tmp_path, U, A))
        assert all(
            k in r
            for k in [
                "input_tokens",
                "cache_creation_tokens",
                "cache_read_tokens",
                "output_tokens",
                "message_count",
                "model",
                "session_id",
                "task_id",
                "timestamp",
            ]
        )  # noqa: E501

    def test_empty_file(self, tmp_path: Path) -> None:
        p = tmp_path / "e.jsonl"
        p.write_text("")
        assert tt.parse_session(str(p))["message_count"] == 0


class TestTaskMapping:
    def test_task_id_string(self, tmp_path: Path) -> None:
        assert tt.parse_session(sess(tmp_path, U, A))["task_id"] == "task-930.1"

    def test_task_id_list(self, tmp_path: Path) -> None:
        assert tt.parse_session(sess(tmp_path, UL, A))["task_id"] == "task-930.2"

    def test_no_task_id(self, tmp_path: Path) -> None:
        assert tt.parse_session(sess(tmp_path, UX, A))["task_id"] == ""

    def test_model(self, tmp_path: Path) -> None:
        assert tt.parse_session(sess(tmp_path, U, A))["model"] == "claude-sonnet-4-6"

    def test_timestamp(self, tmp_path: Path) -> None:
        assert tt.parse_session(sess(tmp_path, U, A))["timestamp"] == "2026-03-25T09:59:00Z"


class TestComputeCost:
    def u(self, i: int = 0, cr: int = 0, cc: int = 0, o: int = 0) -> dict:
        return {"input_tokens": i, "cache_read_tokens": cr, "cache_creation_tokens": cc, "output_tokens": o}

    def test_sonnet(self) -> None:
        assert abs(tt.compute_cost(self.u(1_000_000, o=1_000_000), "claude-sonnet-4-6") - 18.0) < 0.001

    def test_opus(self) -> None:
        assert abs(tt.compute_cost(self.u(1_000_000, o=1_000_000), "claude-opus-4-6") - 90.0) < 0.001

    def test_haiku(self) -> None:
        assert abs(tt.compute_cost(self.u(1_000_000, o=1_000_000), "claude-haiku-4-5-20251001") - 4.8) < 0.001

    def test_cache_read(self) -> None:
        assert abs(tt.compute_cost(self.u(cr=1_000_000), "claude-sonnet-4-6") - 0.3) < 0.001

    def test_cache_creation(self) -> None:
        assert abs(tt.compute_cost(self.u(cc=1_000_000), "claude-sonnet-4-6") - 0.75) < 0.001

    def test_unknown_model(self) -> None:
        assert tt.compute_cost(self.u(1_000_000, o=1_000_000), "unknown") == 0.0


class TestDetectAnomaly:
    def test_above_2x(self, tmp_path: Path) -> None:
        # avg=(100+100+500)/3≈233; 500>=233*2 → anomaly
        t = {
            "t1": {"total_tokens": 100, "cost_estimate_usd": 0.01},
            "t2": {"total_tokens": 100, "cost_estimate_usd": 0.01},
            "t3": {"total_tokens": 500, "cost_estimate_usd": 0.05},
        }  # noqa: E501
        assert any(a["task_id"] == "t3" for a in tt.detect_anomaly(ledger(tmp_path, t)))

    def test_uniform_no_anomaly(self, tmp_path: Path) -> None:
        t = {
            "t1": {"total_tokens": 100, "cost_estimate_usd": 0.01},
            "t2": {"total_tokens": 110, "cost_estimate_usd": 0.01},
        }
        assert tt.detect_anomaly(ledger(tmp_path, t)) == []

    def test_empty(self, tmp_path: Path) -> None:
        p = tmp_path / "l.json"
        p.write_text(json.dumps({"tasks": {}, "summary": {"avg_tokens_per_task": 0}}))
        assert tt.detect_anomaly(str(p)) == []


class TestCLICommands:
    def test_scan_creates_ledger(self, tmp_path: Path) -> None:
        lp = tmp_path / "ledger.json"
        tt.scan(glob_paths=[sess(tmp_path, U, A)], ledger_path=str(lp))
        assert "task-930.1" in json.loads(lp.read_text())["tasks"]

    def test_get_task_found(self, tmp_path: Path) -> None:
        t = {"task-930.1": {"total_tokens": 1000, "cost_estimate_usd": 0.1}}
        r = tt.get_task("task-930.1", ledger(tmp_path, t))
        assert r is not None and r["total_tokens"] == 1000

    def test_get_task_missing(self, tmp_path: Path) -> None:
        p = tmp_path / "l.json"
        p.write_text(json.dumps({"tasks": {}, "summary": {}}))
        assert tt.get_task("task-999.9", str(p)) is None

    def test_get_summary(self, tmp_path: Path) -> None:
        p = tmp_path / "l.json"
        p.write_text(json.dumps({"tasks": {}, "summary": {"total_tokens": 5000}}))
        assert tt.get_summary(str(p))["total_tokens"] == 5000

    def test_anomaly_command(self, tmp_path: Path) -> None:
        # avg=(100+100+1000)/3≈400; 1000>=400*2 → anomaly
        t = {
            "t1": {"total_tokens": 100, "cost_estimate_usd": 0.01},
            "t2": {"total_tokens": 100, "cost_estimate_usd": 0.01},
            "t3": {"total_tokens": 1000, "cost_estimate_usd": 0.10},
        }  # noqa: E501
        a = tt.detect_anomaly(ledger(tmp_path, t))
        assert len(a) == 1 and a[0]["task_id"] == "t3"

    def test_enrich(self, tmp_path: Path) -> None:
        # fmt: off
        td = json.loads('{"tasks":{"task-930.1":{"task_id":"task-930.1","team_id":"dev1-team","status":"completed"}}}')
        ld = json.loads('{"tasks":{"task-930.1":{"input_tokens":1000,"output_tokens":300,"total_tokens":3800,"cost_estimate_usd":0.05}},"summary":{}}')
        # fmt: on
        tp = tmp_path / "timers.json"
        tp.write_text(json.dumps(td))
        lp = tmp_path / "ledger.json"
        lp.write_text(json.dumps(ld))
        tt.enrich(str(tp), str(lp))
        task = json.loads(tp.read_text())["tasks"]["task-930.1"]
        assert task["token_usage"]["total_tokens"] == 3800
        assert task["team_id"] == "dev1-team" and task["status"] == "completed"


class TestTeamRE:
    """TEAM_RE 정규식 엣지 케이스 테스트 (task-1007.1)"""

    def _make_user_msg(self, text: str) -> str:
        """팀 정보가 포함된 user 메시지 JSON 문자열 생성"""
        import json as _json

        return _json.dumps(
            {
                "type": "user",
                "sessionId": "test",
                "message": {"role": "user", "content": text},
                "timestamp": "2026-03-25T10:00:00Z",
            }
        )

    def test_dev_team_pattern(self, tmp_path: Path) -> None:
        """dev1-team, dev2-team 등 정상 매칭"""
        p = tmp_path / "s.jsonl"
        p.write_text(self._make_user_msg("task-1.1\n팀: dev1-team") + "\n" + _A_JSON)
        r = tt.parse_session(str(p))
        assert r["team_id"] == "dev1-team"

    def test_marketing_pattern(self, tmp_path: Path) -> None:
        """marketing 명시 패턴 매칭"""
        p = tmp_path / "s.jsonl"
        p.write_text(self._make_user_msg("task-1.1\nteam: marketing") + "\n" + _A_JSON)
        r = tt.parse_session(str(p))
        assert r["team_id"] == "marketing"

    def test_anu_direct_pattern(self, tmp_path: Path) -> None:
        """anu-direct 명시 패턴 매칭"""
        p = tmp_path / "s.jsonl"
        p.write_text(self._make_user_msg("task-1.1\nteam_id: anu-direct") + "\n" + _A_JSON)
        r = tt.parse_session(str(p))
        assert r["team_id"] == "anu-direct"

    def test_str_comma_not_captured(self, tmp_path: Path) -> None:
        """team_id: str, 같은 타입 어노테이션이 team_id로 캡처되지 않아야 함"""
        p = tmp_path / "s.jsonl"
        # 실제 문제 케이스: Python 코드에 team_id: str, 이 포함된 경우
        p.write_text(self._make_user_msg("task-4.4\nteam_id: str, team: str") + "\n" + _A_JSON)
        r = tt.parse_session(str(p))
        assert r["team_id"] != "str,"
        assert r["team_id"] != "str"

    def test_type_annotation_int_not_captured(self, tmp_path: Path) -> None:
        """team: int 같은 타입 이름이 team_id로 캡처되지 않아야 함"""
        p = tmp_path / "s.jsonl"
        p.write_text(self._make_user_msg("task-1.1\nteam: int") + "\n" + _A_JSON)
        r = tt.parse_session(str(p))
        assert r["team_id"] != "int"

    def test_optional_type_not_captured(self, tmp_path: Path) -> None:
        """team_id: Optional[str] 이 team_id로 캡처되지 않아야 함"""
        p = tmp_path / "s.jsonl"
        p.write_text(self._make_user_msg("task-1.1\nteam_id: Optional[str]") + "\n" + _A_JSON)
        r = tt.parse_session(str(p))
        assert r["team_id"] != "Optional[str]"

    def test_future_team_with_hyphen(self, tmp_path: Path) -> None:
        """미래 팀 이름 (하이픈 포함)도 매칭"""
        p = tmp_path / "s.jsonl"
        p.write_text(self._make_user_msg("task-1.1\n팀: qa-team") + "\n" + _A_JSON)
        r = tt.parse_session(str(p))
        assert r["team_id"] == "qa-team"

    def test_korean_colon(self, tmp_path: Path) -> None:
        """한글 콜론(：) 매칭"""
        p = tmp_path / "s.jsonl"
        p.write_text(self._make_user_msg("task-1.1\n팀：dev2-team") + "\n" + _A_JSON)
        r = tt.parse_session(str(p))
        assert r["team_id"] == "dev2-team"

    def test_team_id_with_trailing_comma(self, tmp_path: Path) -> None:
        """dev1-team, 뒤에 쉼표가 있어도 정확히 team 이름만 캡처"""
        p = tmp_path / "s.jsonl"
        p.write_text(self._make_user_msg("task-1.1\nteam: dev1-team, other info") + "\n" + _A_JSON)
        r = tt.parse_session(str(p))
        assert r["team_id"] == "dev1-team"
