"""Tests for library I/O and paper factor imports."""

import json
from pathlib import Path

import numpy as np
import pytest

from src.factorminer.core.factor_library import Factor, FactorLibrary
from src.factorminer.core.library_io import (
    import_from_paper,
    load_library,
    save_library,
)


class TestSaveLoadLibrary:
    """测试 FactorLibrary 的序列化与反序列化."""

    def test_save_library_ignores_save_signals(self, tmp_path: Path) -> None:
        """save_signals=True 也不应生成 .npz 文件."""
        library = FactorLibrary()
        factor = Factor(
            id=0,
            name="test_factor",
            formula="close / ts_delay(close, 1) - 1",
            category="Momentum",
            ic_mean=0.05,
            icir=0.5,
            ic_win_rate=0.55,
            max_correlation=0.1,
            batch_number=1,
        )
        # 即使给一个信号矩阵，也不应保存
        factor.signals = np.ones((10, 20))
        library.admit_factor(factor)

        base_path = tmp_path / "test_lib"
        save_library(library, str(base_path), save_signals=True)

        assert (base_path.with_suffix(".json")).exists()
        assert not (Path(str(base_path) + "_signals.npz")).exists()

    def test_load_library_restores_metadata_and_unsupported(
        self, tmp_path: Path
    ) -> None:
        """加载 JSON 后应恢复 metadata，并对 # TODO 公式标记 unsupported."""
        library = FactorLibrary()
        f1 = Factor(
            id=0,
            name="ok_factor",
            formula="cs_rank(close)",
            category="Test",
            ic_mean=0.0,
            icir=0.0,
            ic_win_rate=0.0,
            max_correlation=0.0,
            batch_number=0,
            metadata={"author": "ai"},
        )
        f2 = Factor(
            id=0,
            name="todo_factor",
            formula="# TODO: Neg(CsRank(Decay(close, 10)))",
            category="Test",
            ic_mean=0.0,
            icir=0.0,
            ic_win_rate=0.0,
            max_correlation=0.0,
            batch_number=0,
        )
        library.admit_factor(f1)
        library.admit_factor(f2)

        base_path = tmp_path / "meta_lib"
        save_library(library, str(base_path))

        loaded = load_library(str(base_path))
        assert loaded.size == 2

        f1_loaded = loaded.get_factor(1)
        assert f1_loaded.metadata.get("author") == "ai"
        assert not f1_loaded.metadata.get("unsupported", False)

        f2_loaded = loaded.get_factor(2)
        assert f2_loaded.metadata.get("unsupported") is True

    def test_factor_round_trip_with_metadata(self) -> None:
        """Factor.to_dict / from_dict 应正确传递 metadata."""
        factor = Factor(
            id=1,
            name="round_trip",
            formula="ts_mean(close, 20)",
            category="Momentum",
            ic_mean=0.1,
            icir=1.0,
            ic_win_rate=0.6,
            max_correlation=0.2,
            batch_number=2,
            metadata={"unsupported": True, "tags": ["test"]},
        )
        d = factor.to_dict()
        restored = Factor.from_dict(d)
        assert restored.metadata == factor.metadata


class TestImportFromPaper:
    """测试从内置 paper catalog 导入因子."""

    def test_import_from_paper_includes_all_translated_factors(self) -> None:
        """内置 PAPER_FACTORS 应全部成功导入."""
        library = import_from_paper()
        assert library.size > 0
        # 当前 catalog 中已有因子应全部被 admit
        for factor in library.list_factors():
            assert factor.id > 0
            assert factor.name
            assert factor.formula
            assert factor.category

    def test_import_from_paper_marks_todo_as_unsupported(self, tmp_path: Path) -> None:
        """对 # TODO 公式应在 metadata 中标记 unsupported."""
        custom_path = tmp_path / "custom_factors.json"
        custom_data = [
            {
                "name": "Normal Factor",
                "formula": "cs_rank(close)",
                "category": "Test",
            },
            {
                "name": "Unsupported Factor",
                "formula": "# TODO: Neg(CsRank(Decay(close, 10)))",
                "category": "Test",
            },
        ]
        custom_path.write_text(json.dumps(custom_data), encoding="utf-8")

        library = import_from_paper(str(custom_path))
        assert library.size == 2

        normal = library.list_factors()[0]
        todo = library.list_factors()[1]

        assert normal.metadata.get("unsupported") is None
        assert todo.metadata.get("unsupported") is True

    def test_import_from_paper_path_override(self, tmp_path: Path) -> None:
        """通过 path 参数加载外部 JSON 列表."""
        custom_path = tmp_path / "override.json"
        custom_data = [
            {"name": "custom_1", "formula": "open + close", "category": "Custom"},
        ]
        custom_path.write_text(json.dumps(custom_data), encoding="utf-8")

        library = import_from_paper(str(custom_path))
        assert library.size == 1
        assert library.list_factors()[0].name == "custom_1"
