#!/usr/bin/env python3
"""utils/context_refs.py 테스트 스위트 (M-05 @ 참조 시스템)

모리건(테스터) 작성 — TDD RED 단계
"""

from __future__ import annotations

import subprocess
import sys
import tempfile
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from utils.context_refs import parse_refs, resolve_refs


class TestParseRefs:
    """parse_refs() — @ 참조 파싱 테스트"""

    def test_parse_file_ref_simple(self) -> None:
        """@file:path 단일 파싱"""
        refs = parse_refs("hello @file:src/main.py world")
        assert len(refs) == 1
        assert refs[0]["type"] == "file"
        assert refs[0]["path"] == "src/main.py"
        assert refs[0]["raw"] == "@file:src/main.py"

    def test_parse_file_ref_absolute_path(self) -> None:
        """@file:/abs/path.py 절대 경로 파싱"""
        refs = parse_refs("@file:/home/jay/workspace/utils/config_loader.py")
        assert refs[0]["type"] == "file"
        assert refs[0]["path"] == "/home/jay/workspace/utils/config_loader.py"

    def test_parse_folder_ref(self) -> None:
        """@folder:path 파싱"""
        refs = parse_refs("참조: @folder:utils/")
        assert len(refs) == 1
        assert refs[0]["type"] == "folder"
        assert refs[0]["path"] == "utils/"

    def test_parse_diff_ref(self) -> None:
        """@diff 파싱"""
        refs = parse_refs("변경 사항: @diff 확인해줘")
        assert len(refs) == 1
        assert refs[0]["type"] == "diff"
        assert refs[0]["path"] is None
        assert refs[0]["raw"] == "@diff"

    def test_parse_staged_ref(self) -> None:
        """@staged 파싱"""
        refs = parse_refs("staged 파일 목록: @staged")
        assert len(refs) == 1
        assert refs[0]["type"] == "staged"
        assert refs[0]["path"] is None

    def test_parse_multiple_refs(self) -> None:
        """여러 @ 참조 동시 파싱"""
        text = "@file:a.py 그리고 @file:b.py, @diff 도 포함"
        refs = parse_refs(text)
        types = [r["type"] for r in refs]
        assert types.count("file") == 2
        assert types.count("diff") == 1
        assert len(refs) == 3

    def test_parse_no_refs_returns_empty(self) -> None:
        """@ 참조 없으면 빈 목록"""
        refs = parse_refs("hello world, no refs here")
        assert refs == []

    def test_parse_empty_string(self) -> None:
        """빈 문자열은 빈 목록"""
        refs = parse_refs("")
        assert refs == []

    def test_parse_ref_raw_preserved(self) -> None:
        """raw 필드가 원본 텍스트를 보존"""
        text = "@folder:/home/jay/workspace/utils"
        refs = parse_refs(text)
        assert refs[0]["raw"] == "@folder:/home/jay/workspace/utils"

    def test_parse_file_ref_with_spaces_around(self) -> None:
        """공백으로 둘러싸인 @file 참조"""
        refs = parse_refs("  @file:config.yaml  ")
        assert len(refs) == 1
        assert refs[0]["path"] == "config.yaml"

    def test_parse_unknown_at_not_parsed(self) -> None:
        """미지원 @ 태그는 파싱하지 않음"""
        refs = parse_refs("@unknown:something @email이나 다른 것")
        # @file, @folder, @diff, @staged 이외는 무시
        assert all(r["type"] in ("file", "folder", "diff", "staged") for r in refs)


class TestResolveRefs:
    """resolve_refs() — @ 참조 치환 테스트"""

    def test_resolve_file_ref_content(self, tmp_path: Path) -> None:
        """@file:path를 파일 내용으로 치환"""
        f = tmp_path / "hello.txt"
        f.write_text("hello content\n", encoding="utf-8")
        result = resolve_refs(f"before @file:{f} after", base_dir=tmp_path)
        assert "hello content" in result
        # 원본 @file:path 패턴이 그대로 남지 않아야 함 (주석 안 포함은 괜찮음)
        assert f"@file:{f} after" not in result

    def test_resolve_file_not_found(self) -> None:
        """존재하지 않는 파일은 not found 코멘트로 치환"""
        result = resolve_refs("@file:/nonexistent/path/file.txt")
        assert "not found" in result
        assert "@file:/nonexistent/path/file.txt" in result

    def test_resolve_file_size_limit(self, tmp_path: Path) -> None:
        """50KB 초과 파일은 not found 또는 잘라서 처리"""
        big_file = tmp_path / "big.txt"
        big_file.write_bytes(b"x" * (51 * 1024))  # 51KB
        result = resolve_refs(f"@file:{big_file}", base_dir=tmp_path)
        # 초과 파일은 에러 메시지로 대체
        assert "too large" in result or "not found" in result or len(result) < 55000

    def test_resolve_multiple_file_refs(self, tmp_path: Path) -> None:
        """여러 @file 참조를 모두 치환"""
        f1 = tmp_path / "a.txt"
        f2 = tmp_path / "b.txt"
        f1.write_text("content A", encoding="utf-8")
        f2.write_text("content B", encoding="utf-8")
        result = resolve_refs(f"@file:{f1} and @file:{f2}")
        assert "content A" in result
        assert "content B" in result

    def test_resolve_folder_ref(self, tmp_path: Path) -> None:
        """@folder:path 치환 — 파일 목록/내용 포함"""
        sub = tmp_path / "mydir"
        sub.mkdir()
        (sub / "file1.py").write_text("print('one')", encoding="utf-8")
        (sub / "file2.py").write_text("print('two')", encoding="utf-8")
        result = resolve_refs(f"@folder:{sub}", base_dir=tmp_path)
        # 폴더 내 파일 이름이 포함되어야 함
        assert "file1.py" in result or "file2.py" in result

    def test_resolve_folder_max_5_files(self, tmp_path: Path) -> None:
        """@folder는 최대 5개 파일만 포함"""
        sub = tmp_path / "big_dir"
        sub.mkdir()
        for i in range(8):
            (sub / f"file{i}.py").write_text(f"# file{i}", encoding="utf-8")
        result = resolve_refs(f"@folder:{sub}")
        # 8개 파일 중 5개만 처리 — 결과에 많아도 5개 파일 내용
        file_count = sum(1 for i in range(8) if f"# file{i}" in result)
        assert file_count <= 5

    def test_resolve_diff_ref(self, tmp_path: Path) -> None:
        """@diff 치환 — git diff 출력 포함 (git 없어도 에러 안 남)"""
        # git 없는 환경에서도 에러 없이 처리, 원본 텍스트보다 확장되어야 함
        result = resolve_refs("check @diff here")
        # @diff가 치환되면 결과에 git 관련 내용 또는 안내 메시지가 포함됨
        assert "check" in result and "here" in result
        # 치환 결과가 원본보다 길어야 함 (무언가 삽입됨)
        assert len(result) > len("check @diff here")

    def test_resolve_staged_ref(self) -> None:
        """@staged 치환 — git staged 파일 목록 포함 (git 없어도 에러 안 남)"""
        result = resolve_refs("staged: @staged")
        # 치환 결과가 원본보다 길어야 함 (무언가 삽입됨)
        assert len(result) > len("staged: @staged")

    def test_resolve_base_dir_relative_path(self, tmp_path: Path) -> None:
        """base_dir 기준으로 상대 경로 해석"""
        f = tmp_path / "rel.txt"
        f.write_text("relative content", encoding="utf-8")
        result = resolve_refs("@file:rel.txt", base_dir=tmp_path)
        assert "relative content" in result

    def test_resolve_no_refs_unchanged(self) -> None:
        """@ 참조 없으면 원본 텍스트 그대로"""
        text = "hello world, no refs"
        result = resolve_refs(text)
        assert result == text

    def test_resolve_total_size_limit(self, tmp_path: Path) -> None:
        """컨텍스트 합계 100KB 제한"""
        files = []
        for i in range(4):
            f = tmp_path / f"large{i}.txt"
            f.write_bytes(b"y" * (30 * 1024))  # 30KB each → 120KB total
            files.append(f)
        text = " ".join(f"@file:{f}" for f in files)
        result = resolve_refs(text, base_dir=tmp_path)
        # 결과 크기가 100KB를 심각히 초과하지 않아야 함
        assert len(result.encode("utf-8")) < 110 * 1024

    def test_resolve_base_dir_as_path_object(self, tmp_path: Path) -> None:
        """base_dir를 Path 객체로 전달"""
        f = tmp_path / "path_test.txt"
        f.write_text("path object content", encoding="utf-8")
        result = resolve_refs("@file:path_test.txt", base_dir=Path(tmp_path))
        assert "path object content" in result

    def test_resolve_file_ref_line_limit(self, tmp_path: Path) -> None:
        """폴더 내 파일은 200줄 제한"""
        sub = tmp_path / "limitdir"
        sub.mkdir()
        lines = "\n".join(f"line {i}" for i in range(300))
        (sub / "big.py").write_text(lines, encoding="utf-8")
        result = resolve_refs(f"@folder:{sub}", base_dir=tmp_path)
        # 300줄짜리 파일에서 최대 200줄만 포함
        included_lines = [f"line {i}" in result for i in range(300)]
        # 200줄 이후 줄은 포함되지 않을 수 있음 (정확히 200줄 제한)
        assert sum(included_lines) <= 200

    def test_resolve_folder_not_found(self) -> None:
        """존재하지 않는 폴더는 not found 메시지"""
        result = resolve_refs("@folder:/nonexistent/dir/")
        assert "not found" in result
