"""test_changelog.py - TDD 테스트 (RED 단계)"""

import json
import sys
import tempfile
import unittest
from datetime import datetime
from pathlib import Path

# 모듈 경로 추가
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))

from scripts.autoresearch.changelog import (
    add_round,
    create_log,
    finalize_log,
    get_recent_changelog,
    load_log,
    save_log,
)


class TestCreateLog(unittest.TestCase):
    """create_log() 테스트"""

    def test_create_log_structure(self) -> None:
        """기본 구조 확인"""
        log = create_log("my-skill")
        self.assertEqual(log["skill"], "my-skill")
        self.assertIn("started_at", log)
        self.assertEqual(log["rounds"], [])
        self.assertEqual(log["final_score"], 0)
        self.assertEqual(log["total_rounds"], 0)
        self.assertEqual(log["kept"], 0)
        self.assertEqual(log["reverted"], 0)

    def test_create_log_timestamp_is_iso(self) -> None:
        """타임스탬프가 ISO 8601 형식인지 확인"""
        log = create_log("test-skill")
        # ISO 형식이면 datetime.fromisoformat()으로 파싱 가능
        try:
            datetime.fromisoformat(log["started_at"])
        except ValueError:
            self.fail(f"started_at is not ISO 8601 format: {log['started_at']}")

    def test_create_log_different_skills(self) -> None:
        """다른 스킬 이름으로 생성"""
        log1 = create_log("skill-a")
        log2 = create_log("skill-b")
        self.assertEqual(log1["skill"], "skill-a")
        self.assertEqual(log2["skill"], "skill-b")


class TestAddRound(unittest.TestCase):
    """add_round() 테스트"""

    def setUp(self) -> None:
        self.log = create_log("test-skill")

    def test_add_round_appends_to_rounds(self) -> None:
        """라운드가 rounds 리스트에 추가됨"""
        result = add_round(
            log=self.log,
            round_num=1,
            mutation_type="rewrite",
            mutation_description="프롬프트 재작성",
            score_before=0.5,
            score_after=0.7,
            items_detail=[],
            decision="kept",
        )
        self.assertEqual(len(result["rounds"]), 1)

    def test_add_round_data_structure(self) -> None:
        """추가된 라운드의 데이터 구조 확인"""
        result = add_round(
            log=self.log,
            round_num=1,
            mutation_type="append",
            mutation_description="내용 추가",
            score_before=0.3,
            score_after=0.6,
            items_detail=[{"item": "a", "score": 0.6}],
            decision="kept",
            input_tokens=100,
            output_tokens=200,
        )
        round_data = result["rounds"][0]
        self.assertEqual(round_data["round"], 1)
        self.assertEqual(round_data["mutation_type"], "append")
        self.assertEqual(round_data["mutation_description"], "내용 추가")
        self.assertAlmostEqual(round_data["score_before"], 0.3)
        self.assertAlmostEqual(round_data["score_after"], 0.6)
        self.assertEqual(round_data["items_detail"], [{"item": "a", "score": 0.6}])
        self.assertEqual(round_data["decision"], "kept")
        self.assertEqual(round_data["input_tokens"], 100)
        self.assertEqual(round_data["output_tokens"], 200)

    def test_add_round_kept_increments_kept(self) -> None:
        """decision='kept'이면 kept 카운트 증가"""
        result = add_round(
            log=self.log,
            round_num=1,
            mutation_type="rewrite",
            mutation_description="재작성",
            score_before=0.5,
            score_after=0.8,
            items_detail=[],
            decision="kept",
        )
        self.assertEqual(result["kept"], 1)
        self.assertEqual(result["reverted"], 0)

    def test_add_round_reverted_increments_reverted(self) -> None:
        """decision='reverted'이면 reverted 카운트 증가"""
        result = add_round(
            log=self.log,
            round_num=1,
            mutation_type="rewrite",
            mutation_description="재작성",
            score_before=0.5,
            score_after=0.3,
            items_detail=[],
            decision="reverted",
        )
        self.assertEqual(result["reverted"], 1)
        self.assertEqual(result["kept"], 0)

    def test_add_multiple_rounds(self) -> None:
        """여러 라운드 추가"""
        log = self.log
        for i in range(3):
            log = add_round(
                log=log,
                round_num=i + 1,
                mutation_type="rewrite",
                mutation_description=f"라운드 {i+1}",
                score_before=float(i) * 0.1,
                score_after=float(i + 1) * 0.1,
                items_detail=[],
                decision="kept",
            )
        self.assertEqual(len(log["rounds"]), 3)
        self.assertEqual(log["kept"], 3)

    def test_add_round_returns_log(self) -> None:
        """add_round는 수정된 log를 반환"""
        result = add_round(
            log=self.log,
            round_num=1,
            mutation_type="rewrite",
            mutation_description="테스트",
            score_before=0.5,
            score_after=0.6,
            items_detail=[],
            decision="kept",
        )
        self.assertIsInstance(result, dict)
        self.assertIn("rounds", result)


class TestFinalizeLog(unittest.TestCase):
    """finalize_log() 테스트"""

    def test_finalize_updates_final_score(self) -> None:
        """final_score 업데이트"""
        log = create_log("test-skill")
        log = add_round(log, 1, "rewrite", "테스트", 0.5, 0.8, [], "kept")
        log = add_round(log, 2, "append", "추가", 0.8, 0.9, [], "kept")

        result = finalize_log(log, final_score=0.9)
        self.assertAlmostEqual(result["final_score"], 0.9)

    def test_finalize_updates_total_rounds(self) -> None:
        """total_rounds가 rounds 리스트 길이와 일치"""
        log = create_log("test-skill")
        log = add_round(log, 1, "rewrite", "r1", 0.5, 0.7, [], "kept")
        log = add_round(log, 2, "rewrite", "r2", 0.7, 0.8, [], "reverted")

        result = finalize_log(log, final_score=0.7)
        self.assertEqual(result["total_rounds"], 2)

    def test_finalize_returns_log(self) -> None:
        """finalize_log는 log dict를 반환"""
        log = create_log("test-skill")
        result = finalize_log(log, 0.0)
        self.assertIsInstance(result, dict)


class TestSaveAndLoadLog(unittest.TestCase):
    """save_log() & load_log() 테스트"""

    def setUp(self) -> None:
        self.tmpdir = tempfile.mkdtemp()

    def test_save_log_creates_file(self) -> None:
        """save_log가 파일을 생성"""
        log = create_log("test-skill")
        saved_path = save_log(log, "test-skill", evals_dir=self.tmpdir)
        self.assertTrue(Path(saved_path).exists())

    def test_save_log_returns_path_string(self) -> None:
        """save_log가 경로 문자열 반환"""
        log = create_log("test-skill")
        result = save_log(log, "test-skill", evals_dir=self.tmpdir)
        self.assertIsInstance(result, str)

    def test_save_log_valid_json(self) -> None:
        """저장된 파일이 유효한 JSON"""
        log = create_log("test-skill")
        log = add_round(log, 1, "rewrite", "테스트", 0.5, 0.7, [], "kept")
        saved_path = save_log(log, "test-skill", evals_dir=self.tmpdir)

        with open(saved_path, "r", encoding="utf-8") as f:
            loaded = json.load(f)
        self.assertEqual(loaded["skill"], "test-skill")

    def test_load_log_returns_dict(self) -> None:
        """load_log가 저장된 로그를 반환"""
        log = create_log("test-skill")
        log = finalize_log(log, 0.75)
        save_log(log, "test-skill", evals_dir=self.tmpdir)

        loaded = load_log("test-skill", evals_dir=self.tmpdir)
        self.assertIsNotNone(loaded)
        assert loaded is not None
        self.assertEqual(loaded["skill"], "test-skill")
        self.assertAlmostEqual(loaded["final_score"], 0.75)

    def test_load_log_none_if_not_exists(self) -> None:
        """없는 스킬이면 None 반환"""
        result = load_log("nonexistent-skill", evals_dir=self.tmpdir)
        self.assertIsNone(result)

    def test_save_load_roundtrip(self) -> None:
        """저장 후 로드하면 원본과 동일"""
        log = create_log("roundtrip-skill")
        log = add_round(log, 1, "rewrite", "첫 번째 라운드", 0.4, 0.6, [{"item": "x"}], "kept", 50, 100)
        log = finalize_log(log, 0.6)
        save_log(log, "roundtrip-skill", evals_dir=self.tmpdir)

        loaded = load_log("roundtrip-skill", evals_dir=self.tmpdir)
        assert loaded is not None
        self.assertEqual(loaded["skill"], "roundtrip-skill")
        self.assertEqual(len(loaded["rounds"]), 1)
        self.assertEqual(loaded["rounds"][0]["mutation_type"], "rewrite")
        self.assertAlmostEqual(loaded["final_score"], 0.6)


class TestGetRecentChangelog(unittest.TestCase):
    """get_recent_changelog() 테스트"""

    def _make_log_with_rounds(self, n: int) -> dict:
        log = create_log("test-skill")
        for i in range(n):
            log = add_round(
                log=log,
                round_num=i + 1,
                mutation_type="rewrite",
                mutation_description=f"Round {i+1} mutation",
                score_before=float(i) * 0.1,
                score_after=float(i + 1) * 0.1,
                items_detail=[],
                decision="kept" if i % 2 == 0 else "reverted",
            )
        return log

    def test_returns_string(self) -> None:
        """문자열 반환"""
        log = self._make_log_with_rounds(3)
        result = get_recent_changelog(log, count=3)
        self.assertIsInstance(result, str)

    def test_returns_empty_for_no_rounds(self) -> None:
        """라운드 없으면 적절한 메시지 또는 빈 문자열"""
        log = create_log("empty-skill")
        result = get_recent_changelog(log, count=5)
        self.assertIsInstance(result, str)

    def test_returns_recent_n_rounds(self) -> None:
        """최근 N개 라운드만 반환"""
        log = self._make_log_with_rounds(10)
        result = get_recent_changelog(log, count=3)
        # 최근 3개: round 8, 9, 10 포함
        self.assertIn("Round 10", result)
        self.assertIn("Round 9", result)
        self.assertIn("Round 8", result)
        # 오래된 것은 포함 안 됨 - "Round 7 " 처럼 공백이나 구분자로 구분하여 확인
        self.assertNotIn("Round 7", result)
        self.assertNotIn("Round 2 ", result)

    def test_content_includes_mutation_info(self) -> None:
        """변경 정보가 텍스트에 포함됨"""
        log = create_log("info-skill")
        log = add_round(log, 1, "append", "중요한 변경", 0.5, 0.7, [], "kept")
        result = get_recent_changelog(log, count=5)

        self.assertIn("append", result)
        self.assertIn("중요한 변경", result)

    def test_count_larger_than_rounds(self) -> None:
        """count가 라운드 수보다 크면 전체 반환"""
        log = self._make_log_with_rounds(2)
        result = get_recent_changelog(log, count=10)
        self.assertIn("Round 1", result)
        self.assertIn("Round 2", result)


class TestAddRoundTokenBreakdown(unittest.TestCase):
    """add_round() 토큰 세분화 테스트"""

    def setUp(self) -> None:
        self.log = create_log("test-skill")

    def test_new_token_fields_stored(self) -> None:
        """새 토큰 필드가 라운드에 저장됨"""
        result = add_round(
            log=self.log,
            round_num=1,
            mutation_type="rewrite",
            mutation_description="테스트",
            score_before=0.5,
            score_after=0.7,
            items_detail=[],
            decision="kept",
            input_tokens=300,
            output_tokens=200,
            mutation_input_tokens=100,
            mutation_output_tokens=80,
            execution_input_tokens=150,
            execution_output_tokens=70,
            judge_input_tokens=50,
            judge_output_tokens=50,
        )
        rd = result["rounds"][0]
        self.assertEqual(rd["mutation_input_tokens"], 100)
        self.assertEqual(rd["mutation_output_tokens"], 80)
        self.assertEqual(rd["execution_input_tokens"], 150)
        self.assertEqual(rd["execution_output_tokens"], 70)
        self.assertEqual(rd["judge_input_tokens"], 50)
        self.assertEqual(rd["judge_output_tokens"], 50)

    def test_new_fields_default_zero(self) -> None:
        """새 필드 미지정 시 기본값 0"""
        result = add_round(
            log=self.log,
            round_num=1,
            mutation_type="rewrite",
            mutation_description="테스트",
            score_before=0.5,
            score_after=0.7,
            items_detail=[],
            decision="kept",
        )
        rd = result["rounds"][0]
        self.assertEqual(rd["mutation_input_tokens"], 0)
        self.assertEqual(rd["judge_input_tokens"], 0)

    def test_backward_compat_existing_tests(self) -> None:
        """기존 파라미터만 사용해도 동작함"""
        result = add_round(
            log=self.log,
            round_num=1,
            mutation_type="append",
            mutation_description="내용 추가",
            score_before=0.3,
            score_after=0.6,
            items_detail=[],
            decision="kept",
            input_tokens=100,
            output_tokens=200,
        )
        self.assertEqual(len(result["rounds"]), 1)
        self.assertEqual(result["rounds"][0]["input_tokens"], 100)


if __name__ == "__main__":
    unittest.main()
