"""
TDD 테스트: doc_parser.py
RED → GREEN → REFACTOR 순서로 구현
"""

import json
import pathlib
from unittest.mock import MagicMock, patch

import pytest

# 실제 테스트 PDF 경로
TEST_PDF_PATH = "/home/jay/.cokacdir/workspace/autoset/" "금융소비자_보호에_관한_법률법률제21065호20260102.pdf"


@pytest.fixture
def pdf_bytes() -> bytes:
    """실제 PDF 파일을 바이트로 읽어 반환"""
    with open(TEST_PDF_PATH, "rb") as f:
        return f.read()


# ---------------------------------------------------------------------------
# 1. ParseResult 데이터클래스 인스턴스 생성 테스트
# ---------------------------------------------------------------------------
class TestParseResult:
    def test_parse_result_instantiation(self) -> None:
        """ParseResult 데이터클래스를 기본값으로 생성할 수 있다"""
        from doc_parser import ParseResult

        result = ParseResult(
            text="hello",
            pages=["page1", "page2"],
            tables=[{"headers": ["A", "B"], "rows": [["1", "2"]]}],
            metadata={"page_count": 2},
        )
        assert result.text == "hello"
        assert result.pages == ["page1", "page2"]
        assert len(result.tables) == 1
        assert result.metadata["page_count"] == 2

    def test_parse_result_default_fields(self) -> None:
        """ParseResult 필드 기본값(빈 컨테이너)으로도 생성 가능하다"""
        from doc_parser import ParseResult

        result = ParseResult(text="", pages=[], tables=[], metadata={})
        assert isinstance(result.pages, list)
        assert isinstance(result.tables, list)
        assert isinstance(result.metadata, dict)


# ---------------------------------------------------------------------------
# 2~5. parse_pdf 테스트
# ---------------------------------------------------------------------------
class TestParsePdf:
    def test_parse_pdf_returns_parse_result(self, pdf_bytes: bytes) -> None:
        """parse_pdf가 유효한 PDF bytes를 받아 ParseResult를 반환한다"""
        from doc_parser import ParseResult, parse_pdf

        result = parse_pdf(pdf_bytes)
        assert isinstance(result, ParseResult)

    def test_parse_pdf_text_not_empty(self, pdf_bytes: bytes) -> None:
        """parse_pdf 결과의 text가 비어있지 않다"""
        from doc_parser import parse_pdf

        result = parse_pdf(pdf_bytes)
        assert isinstance(result.text, str)
        assert len(result.text) > 0

    def test_parse_pdf_pages_is_nonempty_list(self, pdf_bytes: bytes) -> None:
        """parse_pdf 결과의 pages가 리스트이고 비어있지 않다"""
        from doc_parser import parse_pdf

        result = parse_pdf(pdf_bytes)
        assert isinstance(result.pages, list)
        assert len(result.pages) > 0

    def test_parse_pdf_metadata_has_page_count(self, pdf_bytes: bytes) -> None:
        """parse_pdf 결과의 metadata에 page_count 키가 있다"""
        from doc_parser import parse_pdf

        result = parse_pdf(pdf_bytes)
        assert "page_count" in result.metadata
        assert isinstance(result.metadata["page_count"], int)
        assert result.metadata["page_count"] > 0

    def test_parse_pdf_invalid_bytes_raises_exception(self) -> None:
        """잘못된 bytes를 넣었을 때 예외가 발생한다"""
        from doc_parser import parse_pdf

        with pytest.raises(Exception):
            parse_pdf(b"this is not a valid pdf file content")


# ---------------------------------------------------------------------------
# 6. parse_document 포맷 감지 테스트
# ---------------------------------------------------------------------------
class TestParseDocument:
    def test_parse_document_detects_pdf_by_filename(self, pdf_bytes: bytes) -> None:
        """parse_document가 .pdf 파일명으로 포맷을 감지하여 ParseResult를 반환한다"""
        from doc_parser import ParseResult, parse_document

        result = parse_document(pdf_bytes, "document.pdf")
        assert isinstance(result, ParseResult)
        assert len(result.text) > 0

    def test_parse_document_unsupported_extension_raises(self) -> None:
        """지원하지 않는 확장자는 예외를 발생시킨다"""
        from doc_parser import parse_document

        with pytest.raises((ValueError, Exception)):
            parse_document(b"some bytes", "file.xyz")

    def test_parse_document_case_insensitive_extension(self, pdf_bytes: bytes) -> None:
        """확장자 대소문자 구분 없이 포맷 감지가 동작한다"""
        from doc_parser import ParseResult, parse_document

        result = parse_document(pdf_bytes, "document.PDF")
        assert isinstance(result, ParseResult)


# ---------------------------------------------------------------------------
# opendataloader-pdf mock 헬퍼
# ---------------------------------------------------------------------------
def _make_mock_odl_output() -> dict:
    """opendataloader-pdf JSON 출력을 흉내 내는 dict를 반환"""
    return {
        "file name": "test.pdf",
        "number of pages": 1,
        "author": None,
        "title": None,
        "kids": [
            {
                "type": "heading",
                "id": 1,
                "page number": 1,
                "bounding box": [0, 0, 100, 100],
                "heading level": 1,
                "content": "테스트 PDF 텍스트",
            },
            {
                "type": "paragraph",
                "id": 2,
                "page number": 1,
                "bounding box": [0, 0, 100, 50],
                "content": "페이지1 텍스트",
            },
        ],
    }


def _mock_convert_side_effect(output_data: dict):
    """opendataloader_pdf.convert()의 side_effect: JSON 파일을 output_dir에 생성"""

    def side_effect(input_path, output_dir, **kwargs):
        pathlib.Path(output_dir).mkdir(parents=True, exist_ok=True)
        src = input_path if isinstance(input_path, str) else input_path[0]
        fname = pathlib.Path(src).stem + ".json"
        (pathlib.Path(output_dir) / fname).write_text(
            json.dumps(output_data, ensure_ascii=False)
        )

    return side_effect


# ---------------------------------------------------------------------------
# 7. 캐시 기능 테스트
# ---------------------------------------------------------------------------
class TestParseCache:
    def test_cache_hit_returns_same_result(
        self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """같은 bytes로 parse_pdf 2회 호출 시 같은 결과 반환, convert 호출은 1회만 발생"""
        import doc_parser
        from doc_parser import parse_pdf

        monkeypatch.setattr(doc_parser, "CACHE_DIR", tmp_path / "parse_cache")

        output_data = _make_mock_odl_output()

        with patch("opendataloader_pdf.convert") as mock_convert:
            mock_convert.side_effect = _mock_convert_side_effect(output_data)
            file_bytes = b"%PDF-1.4 fake pdf content"
            result1 = parse_pdf(file_bytes)
            result2 = parse_pdf(file_bytes)

        # 같은 결과
        assert result1.text == result2.text
        assert result1.pages == result2.pages
        assert result1.tables == result2.tables
        # convert 호출은 1번만
        assert mock_convert.call_count == 1

    def test_cache_miss_different_bytes(
        self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """다른 bytes는 다른 캐시 — 각각 convert 호출이 발생한다"""
        import doc_parser
        from doc_parser import parse_pdf

        monkeypatch.setattr(doc_parser, "CACHE_DIR", tmp_path / "parse_cache")

        output_data = _make_mock_odl_output()

        with patch("opendataloader_pdf.convert") as mock_convert:
            mock_convert.side_effect = _mock_convert_side_effect(output_data)
            parse_pdf(b"%PDF-1.4 content A")
            parse_pdf(b"%PDF-1.4 content B")

        # 두 번 모두 convert 호출
        assert mock_convert.call_count == 2

    def test_use_cache_false_forces_reparse(
        self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """`use_cache=False`로 호출 시 캐시를 무시하고 다시 파싱한다"""
        import doc_parser
        from doc_parser import parse_pdf

        monkeypatch.setattr(doc_parser, "CACHE_DIR", tmp_path / "parse_cache")

        output_data = _make_mock_odl_output()
        file_bytes = b"%PDF-1.4 use_cache false test"

        with patch("opendataloader_pdf.convert") as mock_convert:
            mock_convert.side_effect = _mock_convert_side_effect(output_data)
            # 첫 번째 호출 (캐시에 저장)
            parse_pdf(file_bytes)
            # 두 번째 호출 — use_cache=False이므로 캐시 무시
            parse_pdf(file_bytes, use_cache=False)

        # 2번 모두 convert 호출
        assert mock_convert.call_count == 2

    def test_clear_cache_removes_all(
        self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """`clear_cache()` 후 캐시 디렉토리가 비어 있다"""
        import doc_parser
        from doc_parser import clear_cache, parse_pdf

        cache_dir = tmp_path / "parse_cache"
        monkeypatch.setattr(doc_parser, "CACHE_DIR", cache_dir)

        output_data = _make_mock_odl_output()

        with patch("opendataloader_pdf.convert") as mock_convert:
            mock_convert.side_effect = _mock_convert_side_effect(output_data)
            parse_pdf(b"%PDF-1.4 clear cache test A")
            parse_pdf(b"%PDF-1.4 clear cache test B")

        # 캐시 파일이 존재함을 확인
        assert cache_dir.exists()
        json_files = list(cache_dir.glob("*.json"))
        assert len(json_files) >= 1

        # clear_cache 후 비어 있어야 함
        clear_cache()
        remaining = list(cache_dir.glob("*.json"))
        assert len(remaining) == 0

    def test_cache_stores_to_disk(
        self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """캐시 파일이 실제 디스크에 생성된다"""
        import doc_parser
        from doc_parser import parse_pdf

        cache_dir = tmp_path / "parse_cache"
        monkeypatch.setattr(doc_parser, "CACHE_DIR", cache_dir)

        output_data = _make_mock_odl_output()
        file_bytes = b"%PDF-1.4 disk cache test"

        with patch("opendataloader_pdf.convert") as mock_convert:
            mock_convert.side_effect = _mock_convert_side_effect(output_data)
            parse_pdf(file_bytes)

        # 캐시 디렉토리가 생성되고 .json 파일이 존재해야 함
        assert cache_dir.exists()
        json_files = list(cache_dir.glob("*.json"))
        assert len(json_files) == 1

    def test_cached_result_matches_original(
        self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """캐시된 결과와 원본 결과가 동일하다 (text, pages, tables, metadata 비교)"""
        import doc_parser
        from doc_parser import parse_pdf

        monkeypatch.setattr(doc_parser, "CACHE_DIR", tmp_path / "parse_cache")

        output_data = _make_mock_odl_output()
        file_bytes = b"%PDF-1.4 result match test"

        with patch("opendataloader_pdf.convert") as mock_convert:
            mock_convert.side_effect = _mock_convert_side_effect(output_data)
            # 첫 번째 호출 — 파싱 후 캐시 저장
            original = parse_pdf(file_bytes)

        with patch("opendataloader_pdf.convert") as mock_no_call:
            mock_no_call.side_effect = _mock_convert_side_effect(output_data)
            # 두 번째 호출 — 캐시에서 로드 (convert 호출 없어야 함)
            cached = parse_pdf(file_bytes)
            mock_no_call.assert_not_called()

        assert original.text == cached.text
        assert original.pages == cached.pages
        assert original.tables == cached.tables
        assert original.metadata == cached.metadata
