"""Unit tests for filelevel module."""

import logging
import tempfile
from pathlib import Path

import pytest
from hypothesis import given
from hypothesis import strategies as st

from sfi.filelevel.filelevel import (
    BRACKETS,
    LEVELS,
    MARK_BRACKETS,
    FileLevelConfig,
    FileProcessor,
    ParallelRunner,
    process_files,
)

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)


class TestFileLevelConfig:
    """Test cases for FileLevelConfig class."""

    def test_config_initialization(self):
        """Test configuration initialization with default values."""
        config = FileLevelConfig()
        assert config.levels == LEVELS
        assert config.brackets == BRACKETS
        assert config.mark_brackets == MARK_BRACKETS

    def test_config_custom_values(self):
        """Test configuration with custom values."""
        custom_levels = {"0": "", "1": "TEST"}
        custom_brackets = ["[", "]"]
        custom_mark_brackets = ["{", "}"]

        config = FileLevelConfig(
            levels=custom_levels,
            brackets=custom_brackets,
            mark_brackets=custom_mark_brackets,
        )

        assert config.levels == custom_levels
        assert config.brackets == custom_brackets
        assert config.mark_brackets == custom_mark_brackets


class TestFileProcessor:
    """Test cases for FileProcessor class."""

    @pytest.fixture
    def sample_config(self):
        """Sample configuration fixture."""
        return FileLevelConfig()

    @pytest.fixture
    def temp_file(self):
        """Create temporary file for testing."""
        with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as tmp:
            tmp.write(b"test content")
            tmp_path = Path(tmp.name)
        yield tmp_path
        # 安全地删除文件，如果文件已被移动则忽略错误
        try:
            if tmp_path.exists():
                tmp_path.unlink()
        except FileNotFoundError:
            pass  # 文件可能已被重命名，忽略错误

    def test_processor_initialization(self, temp_file, sample_config):
        """Test FileProcessor initialization."""
        processor = FileProcessor(temp_file, "test_file", sample_config)
        assert processor.src == temp_file
        assert processor.filestem == "test_file"
        assert processor.config == sample_config

    @pytest.mark.parametrize(
        "level,expected_marker",
        [
            (0, ""),
            (1, "(PUB)"),
            (2, "(INT)"),
            (3, "(CON)"),
            (4, "(CLA)"),
        ],
    )
    def test_add_level_mark(self, temp_file, sample_config, level, expected_marker):
        """Test adding level markers to filenames."""
        processor = FileProcessor(temp_file, "document", sample_config)
        processor._add_level_mark(level)

        if level == 0:
            assert processor.filestem == "document"
        else:
            assert expected_marker in processor.filestem

    def test_remove_marks(self, temp_file, sample_config):
        """Test removing marks from filenames."""
        processor = FileProcessor(temp_file, "document(PUB)", sample_config)
        processor._remove_marks(marks=["PUB", "INT", "CON", "CLA"])
        assert processor.filestem == "document"

    @pytest.mark.parametrize(
        "input_stem,mark,expected_result",
        [
            ("document(PUB)", "PUB", "document"),
            ("file(INT)", "INT", "file"),
            ("report(CON)", "CON", "report"),
            ("secret(CLA)", "CLA", "secret"),
            ("normal_file", "PUB", "normal_file"),  # No mark to remove
        ],
    )
    def test_remove_mark_static(self, input_stem, mark, expected_result):
        """Test static method for removing marks."""
        result = FileProcessor._remove_mark(input_stem, mark, BRACKETS)
        assert result == expected_result

    def test_rename_integration(self, temp_file, sample_config):
        """Integration test for rename functionality."""
        original_name = temp_file.stem
        processor = FileProcessor(temp_file, original_name, sample_config)

        # Add level 2 marking
        processor.rename(level=2)

        # Verify file was renamed
        new_path = temp_file.parent / f"{original_name}(INT){temp_file.suffix}"
        assert new_path.exists()
        # Note: Original file path object still points to old location
        # but the actual file has been moved


class TestProcessFiles:
    """Test cases for process_files function."""

    def test_process_files_invalid_level(self):
        """Test processing with invalid level raises ValueError."""
        with pytest.raises(ValueError, match="Level must be between 0 and 4"):
            process_files([Path("test.txt")], level=5)

    def test_process_files_empty_targets(self):
        """Test processing with empty targets raises ValueError."""
        with pytest.raises(ValueError, match="At least one target file is required"):
            process_files([], level=1)

    def test_process_files_nonexistent_file(self, caplog):
        """Test processing nonexistent file generates warning."""
        with caplog.at_level(logging.WARNING), pytest.raises(
            ValueError, match="No valid target files found"
        ):
            process_files([Path("nonexistent.txt")], level=1)

        assert "File does not exist: nonexistent.txt" in caplog.text

    def test_process_multiple_files(self):
        """Test processing multiple files."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            # Create test files
            file1 = temp_path / "document1.txt"
            file2 = temp_path / "document2.txt"
            file1.write_text("content1")
            file2.write_text("content2")

            # Process files
            process_files([file1, file2], level=3)

            # Verify results
            assert not file1.exists()
            assert not file2.exists()

            new_file1 = temp_path / "document1(CON).txt"
            new_file2 = temp_path / "document2(CON).txt"
            assert new_file1.exists()
            assert new_file2.exists()


class TestParallelRunner:
    """Test cases for ParallelRunner class."""

    def test_runner_initialization(self):
        """Test ParallelRunner initialization."""
        runner = ParallelRunner(max_workers=4)
        assert runner.max_workers == 4

    def test_runner_default_workers(self):
        """Test ParallelRunner default worker count."""
        runner = ParallelRunner()
        assert runner.max_workers == 8

    def test_runner_empty_targets(self, caplog):
        """Test runner with empty targets."""
        runner = ParallelRunner()
        with caplog.at_level(logging.ERROR):
            runner.run(lambda x: x, [])

        assert "No valid targets to process" in caplog.text


class TestPropertyBased:
    """Property-based tests using Hypothesis."""

    @given(
        st.text(
            alphabet=st.characters(min_codepoint=97, max_codepoint=122),
            min_size=1,
            max_size=20,
        ),
        st.sampled_from([0, 1, 2, 3, 4]),
        st.sampled_from(["txt", "pdf", "docx"]),
    )
    def test_rename_property_based(self, stem, level, extension):
        """Property-based test for file renaming."""
        filename = f"{stem}.{extension}"

        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            test_file = temp_path / filename
            test_file.write_text("test content")

            config = FileLevelConfig()
            processor = FileProcessor(test_file, stem, config)
            processor.rename(level=level)

            # Check that a file with the expected name exists
            if level == 0:
                # Level 0 should preserve original name (no markers added)
                expected_files = list(temp_path.glob(f"{stem}.{extension}"))
                assert len(expected_files) >= 1
                # Original file should still exist (or be renamed to same name)
            else:
                level_marker = ["", "PUB", "INT", "CON", "CLA"][level]
                expected_files = list(
                    temp_path.glob(f"{stem}({level_marker}).{extension}")
                )
                assert len(expected_files) >= 1
                # Original file should not exist with original name
                original_files = list(temp_path.glob(f"{stem}.{extension}"))
                # Filter out files that might have the same stem but different suffix
                original_exact = [f for f in original_files if f.name == filename]
                assert len(original_exact) == 0


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
