"""Unit tests for MakePython module."""

from __future__ import annotations

import tempfile
from pathlib import Path
from unittest.mock import Mock, patch

import pytest

from sfi.makepython.makepython import (
    BuildTool,
    CommandType,
    MakePythonConfig,
    ProjectInfo,
    SubprocessExecutor,
    _clean,
    _get_build_command,
    _run_command,
    _write_to_env_file,
)


class TestEnums:
    """Test enumeration classes."""

    def test_build_tool_values(self) -> None:
        """Test BuildTool enum values."""
        assert BuildTool.UV.value == "uv"
        assert BuildTool.POETRY.value == "poetry"
        assert BuildTool.HATCH.value == "hatch"

    def test_command_type_values(self) -> None:
        """Test CommandType enum values."""
        assert CommandType.BUILD.value == "build"
        assert CommandType.CLEAN.value == "clean"
        assert CommandType.TEST.value == "test"
        assert CommandType.PUBLISH.value == "publish"
        assert CommandType.BUMP_VERSION.value == "bumpversion"
        assert CommandType.TOKEN.value == "token"


class TestProjectInfo:
    """Test ProjectInfo dataclass."""

    def test_from_pyproject_with_valid_file(self) -> None:
        """Test creating ProjectInfo from valid pyproject.toml."""
        with tempfile.TemporaryDirectory() as temp_dir:
            project_dir = Path(temp_dir)
            pyproject_path = project_dir / "pyproject.toml"

            pyproject_content = """
[project]
name = "test-project"
version = "1.0.0"

[build-system]
build-backend = "hatchling.build"
"""
            pyproject_path.write_text(pyproject_content)

            project_info = ProjectInfo.from_pyproject(project_dir)

            assert project_info is not None
            assert project_info.name == "test-project"
            assert project_info.version == "1.0.0"
            assert project_info.build_tool == BuildTool.HATCH

    def test_from_pyproject_without_build_system(self) -> None:
        """Test ProjectInfo from pyproject.toml without build-system."""
        with tempfile.TemporaryDirectory() as temp_dir:
            project_dir = Path(temp_dir)
            pyproject_path = project_dir / "pyproject.toml"

            pyproject_content = """
[project]
name = "test-project"
version = "1.0.0"
"""
            pyproject_path.write_text(pyproject_content)

            project_info = ProjectInfo.from_pyproject(project_dir)

            assert project_info is not None
            assert project_info.name == "test-project"
            assert project_info.version == "1.0.0"
            assert project_info.build_tool is None

    def test_from_pyproject_nonexistent_file(self) -> None:
        """Test ProjectInfo from non-existent pyproject.toml."""
        with tempfile.TemporaryDirectory() as temp_dir:
            project_dir = Path(temp_dir)
            project_info = ProjectInfo.from_pyproject(project_dir)
            assert project_info is None

    def test_from_pyproject_invalid_toml(self) -> None:
        """Test ProjectInfo from invalid TOML file."""
        with tempfile.TemporaryDirectory() as temp_dir:
            project_dir = Path(temp_dir)
            pyproject_path = project_dir / "pyproject.toml"
            pyproject_path.write_text("invalid [ toml content")

            project_info = ProjectInfo.from_pyproject(project_dir)
            assert project_info is None


class TestMakePythonConfig:
    """Test MakePythonConfig dataclass."""

    def test_default_values(self) -> None:
        """Test default configuration values."""
        # Mock CONFIG_FILE to ensure we test default values without local config interference
        with tempfile.TemporaryDirectory() as temp_dir:
            mock_config_file = Path(temp_dir) / "nonexistent_config.json"
            with patch("sfi.makepython.makepython.CONFIG_FILE", mock_config_file):
                config = MakePythonConfig()

                assert config.default_build_tool == BuildTool.UV
                assert config.auto_detect_tool is True
                assert config.verbose_output is False
                assert config.max_retries == 3
                assert config.timeout_seconds == 300

    def test_save_and_load_config(self) -> None:
        """Test saving and loading configuration."""
        with tempfile.TemporaryDirectory() as temp_dir:
            config_file = Path(temp_dir) / "makepython.json"

            with patch("sfi.makepython.makepython.CONFIG_FILE", config_file):
                # Create and save config
                config = MakePythonConfig(
                    default_build_tool=BuildTool.POETRY,
                    verbose_output=True,
                    max_retries=5,
                )
                config.save()

                # Load config
                loaded_config = MakePythonConfig()

                assert loaded_config.default_build_tool == BuildTool.POETRY
                assert loaded_config.verbose_output is True
                assert loaded_config.max_retries == 5

    def test_cached_supported_tools(self) -> None:
        """Test cached property for supported tools."""
        config = MakePythonConfig()

        # Mock shutil.which to return available tools
        with patch("shutil.which") as mock_which:
            mock_which.side_effect = lambda x: (
                x in ["uv", "poetry"]
            )  # hatch not available

            supported = config.supported_tools

            assert len(supported) == 2
            assert BuildTool.UV in supported
            assert BuildTool.POETRY in supported
            assert BuildTool.HATCH not in supported


class TestSubprocessExecutor:
    """Test SubprocessExecutor class."""

    def test_execute_successful_command(self) -> None:
        """Test executing a successful command."""
        config = MakePythonConfig()
        executor = SubprocessExecutor(config)

        with patch("subprocess.run") as mock_run:
            mock_result = Mock()
            mock_result.stdout = "success output"
            mock_result.stderr = ""
            mock_run.return_value = mock_result

            result = executor.execute(["echo", "hello"], Path("."))

            assert result == mock_result
            mock_run.assert_called_once_with(
                ["echo", "hello"],
                cwd=Path("."),
                capture_output=True,
                text=True,
                encoding="utf-8",
                errors="replace",
                timeout=300,
                check=True,
            )

    def test_execute_failing_command(self) -> None:
        """Test executing a failing command."""
        config = MakePythonConfig()
        executor = SubprocessExecutor(config)

        with patch("subprocess.run") as mock_run:
            mock_run.side_effect = Exception("Command failed")

            with pytest.raises(Exception):
                executor.execute(["false"], Path("."))


class TestUtilityFunctions:
    """Test utility functions."""

    def test_clean_function(self) -> None:
        """Test _clean function."""
        with tempfile.TemporaryDirectory() as temp_dir:
            test_dir = Path(temp_dir)

            # Create some files to clean
            (test_dir / "dist").mkdir()
            (test_dir / "build").mkdir()
            (test_dir / "test.egg-info").touch()

            # This should not raise an exception even if files don't exist
            _clean(test_dir)

            # Files should still exist (this test just ensures no exceptions)
            assert test_dir.exists()

    def test_write_to_env_file_windows(self) -> None:
        """Test writing environment variables on Windows."""
        with patch("sfi.makepython.makepython.IS_WINDOWS", True):
            with patch("subprocess.run") as mock_run:
                _write_to_env_file("TEST_VAR", "test_value")
                mock_run.assert_called_once()

    def test_write_to_env_file_unix(self) -> None:
        """Test writing environment variables on Unix-like systems."""
        with patch("sfi.makepython.makepython.IS_WINDOWS", False), patch(
            "sfi.makepython.makepython._write_to_shell_config"
        ) as mock_write:
            _write_to_env_file("TEST_VAR", "test_value")
            mock_write.assert_called_once_with("export TEST_VAR='test_value'")

    def test_run_command_success(self) -> None:
        """Test successful command execution."""
        with patch("subprocess.run") as mock_run:
            mock_result = Mock()
            mock_result.stdout = "output"
            mock_result.stderr = ""
            mock_run.return_value = mock_result

            _run_command(["echo", "test"], Path("."))

            # Should print the output
            mock_run.assert_called_once()

    def test_get_build_command_integration(self) -> None:
        """Test getting build command integration."""
        with tempfile.TemporaryDirectory() as temp_dir:
            project_dir = Path(temp_dir)
            pyproject_path = project_dir / "pyproject.toml"

            pyproject_content = """
[project]
name = "test-project"

[build-system]
build-backend = "poetry.core.masonry.api"
"""
            pyproject_path.write_text(pyproject_content)

            config = MakePythonConfig()
            result = _get_build_command(project_dir, config)
            assert result == "poetry"

    def test_get_build_command_with_config(self) -> None:
        """Test getting build command with configuration."""
        with tempfile.TemporaryDirectory() as temp_dir:
            project_dir = Path(temp_dir)

            config = MakePythonConfig()

            # Mock supported tools
            with patch.object(config, "supported_tools", [BuildTool.UV]):
                result = _get_build_command(project_dir, config)
                assert result == "uv"


class TestIntegration:
    """Integration tests."""

    def test_complete_workflow(self) -> None:
        """Test a complete workflow scenario."""
        with tempfile.TemporaryDirectory() as temp_dir:
            project_dir = Path(temp_dir)

            # Create a basic project structure
            pyproject_path = project_dir / "pyproject.toml"
            pyproject_content = """
[project]
name = "integration-test"
version = "0.1.0"

[build-system]
build-backend = "hatchling.build"
"""
            pyproject_path.write_text(pyproject_content)

            # Test project info extraction
            project_info = ProjectInfo.from_pyproject(project_dir)
            assert project_info is not None
            assert project_info.name == "integration-test"
            assert project_info.build_tool == BuildTool.HATCH

            # Test configuration with mocked config file to avoid local config interference
            mock_config_file = Path(temp_dir) / "nonexistent_config.json"
            with patch("sfi.makepython.makepython.CONFIG_FILE", mock_config_file):
                config = MakePythonConfig()
                assert config.default_build_tool == BuildTool.UV

            # Test build command detection
            build_cmd = _get_build_command(project_dir, config)
            assert build_cmd == "hatch"  # Should detect from pyproject.toml


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