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

import logging
import tempfile
from datetime import timedelta
from pathlib import Path
from unittest.mock import patch

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

from ..cli import (
    BRACKETS,
    LEVELS,
    MARK_BRACKETS,
    FileLevelConfig,
    FileProcessor,
    process_file,
)

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, sample_config)
        assert processor.src == temp_file
        assert processor.filestem == temp_file.stem  # Now uses the stem from the file path
        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, sample_config)
        processor.filestem = "document"  # Set the stem manually
        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, sample_config)
        processor.filestem = "document(PUB)"  # Set the stem manually
        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, sample_config)
        # Ensure the filestem is what we expect
        assert processor.filestem == original_name

        # 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 TestProcessFile:
    """Test cases for process_file function."""

    def test_process_file_invalid_level(self):
        """Test processing with invalid level logs error."""
        with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as tmp:
            tmp_path = Path(tmp.name)

        # Mock the logger to capture log calls
        with patch("pytola.system.filelevel.cli.logger") as mock_logger:
            process_file(tmp_path, level=5)  # Invalid level
            # Verify that error was logged
            mock_logger.error.assert_called_once()
            # Check that the error message contains the expected text
            args, _kwargs = mock_logger.error.call_args
            assert "Invalid level 5, must be between 0 and 4" in args[0]

        # Clean up
        tmp_path.unlink(missing_ok=True)

    def test_process_file_nonexistent_file(self):
        """Test processing nonexistent file generates error."""
        nonexistent_file = Path("nonexistent.txt")

        # Mock the logger to capture log calls
        with patch("pytola.system.filelevel.cli.logger") as mock_logger:
            process_file(nonexistent_file, level=1)
            # Verify that error was logged
            mock_logger.error.assert_called_once()
            # Check that the error message contains the expected text
            args, _kwargs = mock_logger.error.call_args
            assert "File does not exist: nonexistent.txt" in args[0]

    def test_process_single_file(self):
        """Test processing a single file."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            # Create test file
            file1 = temp_path / "document1.txt"
            file1.write_text("content1")

            # Process file
            process_file(file1, level=3)

            # Verify results
            assert not file1.exists()

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


# Removed - no longer exists in the main module


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, config)
            processor.filestem = stem  # Set the stem manually
            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
