import json
import math
import tempfile
from pathlib import Path

import pytest
import yaml
from rich.console import Console

from crackerjack.services.unified_config import (
    CrackerjackConfig,
    EnvironmentConfigSource,
    FileConfigSource,
    OptionsConfigSource,
    UnifiedConfigurationService,
)


class MockOptions:
    def __init__(self, **kwargs) -> None:
        for key, value in kwargs.items():
            setattr(self, key, value)


class TestCrackerjackConfig:
    def test_default_config(self) -> None:
        config = CrackerjackConfig()

        assert config.cache_enabled is True
        assert config.cache_size == 1000
        assert config.autofix is True
        assert config.log_level == "INFO"
        assert config.test_workers >= 1
        assert config.min_coverage == 10.0

    def test_config_validation_log_level(self) -> None:
        config = CrackerjackConfig(log_level="DEBUG")
        assert config.log_level == "DEBUG"

        with pytest.raises(ValueError, match="Invalid log level"):
            CrackerjackConfig(log_level="INVALID")

    def test_config_validation_test_workers(self) -> None:
        config = CrackerjackConfig(test_workers=8)
        assert config.test_workers == 8

        config = CrackerjackConfig(test_workers=0)
        assert config.test_workers == 1

        config = CrackerjackConfig(test_workers=32)
        assert config.test_workers == 16

    def test_config_validation_coverage(self) -> None:
        config = CrackerjackConfig(min_coverage=80.0)
        assert config.min_coverage == 80.0

        config = CrackerjackConfig(min_coverage=-10.0)
        assert config.min_coverage == 0.0

        config = CrackerjackConfig(min_coverage=150.0)
        assert config.min_coverage == 100.0


class TestEnvironmentConfigSource:
    def test_load_environment_config(self, monkeypatch) -> None:
        monkeypatch.setenv("CRACKERJACK_CACHE_ENABLED", "false")
        monkeypatch.setenv("CRACKERJACK_CACHE_SIZE", "500")
        monkeypatch.setenv("CRACKERJACK_LOG_LEVEL", "DEBUG")
        monkeypatch.setenv("CRACKERJACK_AUTOFIX", "true")

        source = EnvironmentConfigSource()
        config = source.load()

        assert config["cache_enabled"] is False
        assert config["cache_size"] == 500
        assert config["log_level"] == "DEBUG"
        assert config["autofix"] is True

    def test_environment_value_conversion(self) -> None:
        source = EnvironmentConfigSource()

        assert source._convert_value("true") is True
        assert source._convert_value("false") is False
        assert source._convert_value("1") is True
        assert source._convert_value("0") is False

        assert source._convert_value("42") == 42
        assert source._convert_value("- 10") == -10

        assert source._convert_value("3.141592653589793") == math.pi
        assert source._convert_value("- 2.5") == -2.5

        assert source._convert_value("hello") == "hello"


class TestFileConfigSource:
    def test_load_yaml_config(self) -> None:
        config_data = {
            "cache_enabled": False,
            "log_level": "DEBUG",
            "test_workers": 2,
        }

        with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
            yaml.dump(config_data, f)
            config_path = Path(f.name)

        try:
            source = FileConfigSource(config_path)
            loaded_config = source.load()

            assert loaded_config == config_data
        finally:
            config_path.unlink(missing_ok=True)

    def test_load_json_config(self) -> None:
        config_data = {
            "cache_enabled": False,
            "log_level": "WARNING",
            "autofix": True,
        }

        with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
            json.dump(config_data, f)
            config_path = Path(f.name)

        try:
            source = FileConfigSource(config_path)
            loaded_config = source.load()

            assert loaded_config == config_data
        finally:
            config_path.unlink(missing_ok=True)

    def test_load_nonexistent_file(self) -> None:
        nonexistent_path = Path("/ nonexistent / config.yaml")
        source = FileConfigSource(nonexistent_path)

        assert source.is_available() is False
        assert source.load() == {}


class TestOptionsConfigSource:
    def test_load_options_config(self) -> None:
        options = MockOptions(
            testing=True,
            autofix=False,
            skip_hooks=True,
            test_workers=4,
            log_level="ERROR",
        )

        source = OptionsConfigSource(options)
        config = source.load()

        assert config["test_mode"] is True
        assert config["autofix"] is False
        assert config["skip_hooks"] is True
        assert config["test_workers"] == 4
        assert config["log_level"] == "ERROR"

    def test_load_partial_options(self) -> None:
        options = MockOptions(autofix=True)

        source = OptionsConfigSource(options)
        config = source.load()

        assert config["autofix"] is True
        assert len(config) == 1


class TestUnifiedConfigurationService:
    @pytest.fixture
    def temp_dir(self):
        with tempfile.TemporaryDirectory() as tmp_dir:
            yield Path(tmp_dir)

    @pytest.fixture
    def console(self):
        return Console(force_terminal=False)

    @pytest.fixture
    def basic_service(self, console, temp_dir):
        return UnifiedConfigurationService(console, temp_dir)

    def test_service_initialization(self, basic_service, temp_dir) -> None:
        assert basic_service.console is not None
        assert basic_service.pkg_path == temp_dir
        assert len(basic_service.sources) > 0

    def test_get_default_config(self, basic_service) -> None:
        config = basic_service.get_config()

        assert isinstance(config, CrackerjackConfig)
        assert config.cache_enabled is True
        assert config.autofix is True
        assert config.log_level == "INFO"

    def test_config_merging_priority(self, console, temp_dir) -> None:
        pyproject_path = temp_dir / "pyproject.toml"
        pyproject_content = """
[tool.crackerjack]
cache_enabled = false
log_level = "DEBUG"
"""
        pyproject_path.write_text(pyproject_content)

        options = MockOptions(log_level="ERROR")

        service = UnifiedConfigurationService(console, temp_dir, options)
        config = service.get_config()

        assert config.cache_enabled is False
        assert config.log_level == "ERROR"

    def test_get_specialized_configs(self, basic_service) -> None:
        hook_config = basic_service.get_hook_execution_config()
        assert "batch_size" in hook_config
        assert "timeout" in hook_config
        assert "max_concurrent" in hook_config

        test_config = basic_service.get_testing_config()
        assert "timeout" in test_config
        assert "workers" in test_config
        assert "min_coverage" in test_config

        cache_config = basic_service.get_cache_config()
        assert "enabled" in cache_config
        assert "size" in cache_config
        assert "ttl" in cache_config

        logging_config = basic_service.get_logging_config()
        assert "level" in logging_config
        assert "json_output" in logging_config

    def test_config_validation(self, basic_service) -> None:
        assert basic_service.validate_current_config() is True

    def test_config_reload(self, console, temp_dir) -> None:
        service = UnifiedConfigurationService(console, temp_dir)

        config1 = service.get_config()

        pyproject_file = temp_dir / "pyproject.toml"
        pyproject_content = """
[tool.crackerjack]
cache_enabled = false
"""
        pyproject_file.write_text(pyproject_content)

        config2 = service.get_config(reload=True)

        assert config1.cache_enabled is True
        assert config2.cache_enabled is False

    def test_precommit_config_mode(self, basic_service) -> None:
        mode = basic_service.get_precommit_config_mode()
        assert mode in ("comprehensive", "fast", "experimental")


class TestConfigIntegration:
    def test_full_config_integration(self, monkeypatch) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            temp_dir = Path(tmp_dir)
            console = Console(force_terminal=False)

            pyproject_path = temp_dir / "pyproject.toml"
            pyproject_content = """
[tool.crackerjack]
cache_size = 500
log_level = "DEBUG"
autofix = false
test_workers = 2
"""
            pyproject_path.write_text(pyproject_content)

            monkeypatch.setenv("CRACKERJACK_ENABLE_ASYNC_HOOKS", "false")

            options = MockOptions(skip_hooks=True)

            service = UnifiedConfigurationService(console, temp_dir, options)
            config = service.get_config()

            assert config.cache_size == 500
            assert config.log_level == "DEBUG"
            assert config.autofix is False
            assert config.test_workers == 2
            assert config.enable_async_hooks is False
            assert config.skip_hooks is True
