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

import logging
import tempfile
from datetime import timedelta
from pathlib import Path

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

from pytola.system.filelevel.cli 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: Path, sample_config: FileLevelConfig):
        """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: str, mark: str, expected_result: str):
        """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: Path, sample_config: FileLevelConfig):
        """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 TestProcespytolales:
    """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):
        """Test processing nonexistent file generates warning."""
        with pytest.raises(ValueError, match="No valid target files found"):
            process_files([Path("nonexistent.txt")], level=1)

    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):
        """Test runner with empty targets."""
        runner = ParallelRunner()
        runner.run(lambda x: x, [])


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"]),
    )
    @settings(deadline=timedelta(milliseconds=1000), max_examples=20)  # Increase deadline and reduce examples
    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
