"""Tests for deployment file override resolution."""

import logging
from pathlib import Path

import pytest

from entr_adapter_core.defaults.resolve import (
    COMPOSE_FILES,
    COMPOSE_OVERRIDE_FILENAME,
    DEFAULT_OVERRIDE_DIR,
    SHADOW_FILES,
    ResolvedFile,
    log_resolution_summary,
    resolve_all_files,
    resolve_compose_files,
    resolve_file,
)


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def override_dir(tmp_path: Path) -> Path:
    """Create a temporary override directory."""
    d = tmp_path / "deploy"
    d.mkdir()
    return d


@pytest.fixture
def override_dir_str(override_dir: Path) -> str:
    """Return the override directory as a string path."""
    return str(override_dir)


# ---------------------------------------------------------------------------
# Tests: resolve_file (shadow mechanism)
# ---------------------------------------------------------------------------

class TestResolveFileShadow:
    """AC1, AC3: Shadow resolution for Dockerfile, nginx.conf, fluent-bit.conf."""

    @pytest.mark.parametrize("filename", SHADOW_FILES)
    def test_fallback_to_package_default(self, override_dir_str: str, filename: str):
        """When no override exists, returns package default path."""
        result = resolve_file(filename, override_dir=override_dir_str)
        assert result.is_file()
        assert "entr_adapter_core" in str(result)
        assert result.name == filename

    @pytest.mark.parametrize("filename", SHADOW_FILES)
    def test_override_takes_precedence(self, override_dir: Path, filename: str):
        """When override file exists in deploy/, it wins entirely."""
        override_file = override_dir / filename
        override_file.write_text(f"# custom {filename}")

        result = resolve_file(filename, override_dir=str(override_dir))
        assert result == override_file.resolve()

    def test_nonexistent_override_dir_falls_back(self, tmp_path: Path):
        """When override_dir doesn't exist, falls back to package default."""
        nonexistent = str(tmp_path / "nonexistent_deploy")
        result = resolve_file("Dockerfile", override_dir=nonexistent)
        assert result.is_file()
        assert result.name == "Dockerfile"

    def test_returns_absolute_path(self, override_dir_str: str):
        """Returned path is always absolute."""
        result = resolve_file("Dockerfile", override_dir=override_dir_str)
        assert result.is_absolute()

    def test_override_returns_absolute_path(self, override_dir: Path):
        """Override path is resolved to absolute."""
        (override_dir / "Dockerfile").write_text("# custom")
        result = resolve_file("Dockerfile", override_dir=str(override_dir))
        assert result.is_absolute()


# ---------------------------------------------------------------------------
# Tests: resolve_compose_files (compose-native merge)
# ---------------------------------------------------------------------------

class TestResolveComposeFiles:
    """AC2: Compose-native override merging."""

    def test_dev_mode_returns_dev_compose(self, override_dir_str: str):
        """Dev mode returns the dev compose file."""
        result = resolve_compose_files(mode="dev", override_dir=override_dir_str)
        assert len(result) == 1
        assert result[0].name == "docker-compose.dev.yml"

    def test_prod_mode_returns_prod_compose(self, override_dir_str: str):
        """Prod mode returns the prod compose file."""
        result = resolve_compose_files(mode="prod", override_dir=override_dir_str)
        assert len(result) == 1
        assert result[0].name == "docker-compose.prod.yml"

    def test_override_appended_when_present(self, override_dir: Path):
        """When docker-compose.override.yml exists, it's appended."""
        override_file = override_dir / COMPOSE_OVERRIDE_FILENAME
        override_file.write_text("services:\n  adapter:\n    environment:\n      DEBUG: 'true'\n")

        result = resolve_compose_files(mode="dev", override_dir=str(override_dir))
        assert len(result) == 2
        assert result[-1] == override_file.resolve()

    def test_no_override_file_returns_only_base(self, override_dir_str: str):
        """Without override file, only the base file is returned."""
        result = resolve_compose_files(mode="dev", override_dir=override_dir_str)
        filenames = [p.name for p in result]
        assert COMPOSE_OVERRIDE_FILENAME not in filenames

    def test_order_is_base_then_override(self, override_dir: Path):
        """Override is always last (for -f base -f override chaining)."""
        (override_dir / COMPOSE_OVERRIDE_FILENAME).write_text("services: {}")
        result = resolve_compose_files(mode="dev", override_dir=str(override_dir))
        assert result[-1].name == COMPOSE_OVERRIDE_FILENAME
        assert "entr_adapter_core" in str(result[0])

    def test_nonexistent_override_dir(self, tmp_path: Path):
        """Nonexistent override dir returns only the base file."""
        result = resolve_compose_files(mode="dev", override_dir=str(tmp_path / "nope"))
        assert len(result) == 1

    def test_all_paths_are_absolute(self, override_dir_str: str):
        """All returned paths are absolute."""
        result = resolve_compose_files(mode="dev", override_dir=override_dir_str)
        for p in result:
            assert p.is_absolute()

    def test_default_mode_is_dev(self, override_dir_str: str):
        """Default mode parameter is 'dev'."""
        import inspect
        sig = inspect.signature(resolve_compose_files)
        assert sig.parameters["mode"].default == "dev"


# ---------------------------------------------------------------------------
# Tests: resolve_all_files (bulk resolver)
# ---------------------------------------------------------------------------

class TestResolveAllFiles:
    """AC1, AC4: Bulk resolution of all deployment files."""

    def test_returns_all_known_files(self, override_dir_str: str):
        """Returns entries for all shadow + compose files."""
        result = resolve_all_files(override_dir=override_dir_str)
        for filename in SHADOW_FILES:
            assert filename in result
        for filename in COMPOSE_FILES:
            assert filename in result

    def test_all_defaults_when_no_overrides(self, override_dir_str: str):
        """All files resolve to package_default when no overrides exist."""
        result = resolve_all_files(override_dir=override_dir_str)
        for rf in result.values():
            assert rf.source == "package_default"

    def test_shadow_override_detected(self, override_dir: Path):
        """Shadow override is correctly identified."""
        (override_dir / "nginx.conf").write_text("# custom nginx")
        result = resolve_all_files(override_dir=str(override_dir))
        assert result["nginx.conf"].source == "adapter_override"
        assert result["nginx.conf"].mechanism == "shadow"
        # Others still default
        assert result["Dockerfile"].source == "package_default"

    def test_compose_override_detected(self, override_dir: Path):
        """Compose override file is included when present."""
        (override_dir / COMPOSE_OVERRIDE_FILENAME).write_text("services: {}")
        result = resolve_all_files(override_dir=str(override_dir))
        assert COMPOSE_OVERRIDE_FILENAME in result
        assert result[COMPOSE_OVERRIDE_FILENAME].source == "adapter_override"
        assert result[COMPOSE_OVERRIDE_FILENAME].mechanism == "compose_merge"

    def test_compose_defaults_always_package_default(self, override_dir: Path):
        """Compose base files are always from package, even with override."""
        (override_dir / COMPOSE_OVERRIDE_FILENAME).write_text("services: {}")
        result = resolve_all_files(override_dir=str(override_dir))
        for filename in COMPOSE_FILES:
            assert result[filename].source == "package_default"
            assert result[filename].mechanism == "compose_merge"

    def test_resolved_file_dataclass_fields(self, override_dir_str: str):
        """ResolvedFile has all expected fields."""
        result = resolve_all_files(override_dir=override_dir_str)
        rf = result["Dockerfile"]
        assert isinstance(rf, ResolvedFile)
        assert rf.filename == "Dockerfile"
        assert isinstance(rf.resolved_path, Path)
        assert rf.source in ("package_default", "adapter_override")
        assert rf.mechanism in ("shadow", "compose_merge")

    def test_mechanisms_are_correct(self, override_dir_str: str):
        """Shadow files have shadow mechanism, compose files have compose_merge."""
        result = resolve_all_files(override_dir=override_dir_str)
        for filename in SHADOW_FILES:
            assert result[filename].mechanism == "shadow"
        for filename in COMPOSE_FILES:
            assert result[filename].mechanism == "compose_merge"

    def test_mixed_overrides(self, override_dir: Path):
        """Multiple override types detected simultaneously."""
        (override_dir / "Dockerfile").write_text("FROM scratch")
        (override_dir / "fluent-bit.conf").write_text("[SERVICE]")
        (override_dir / COMPOSE_OVERRIDE_FILENAME).write_text("services: {}")

        result = resolve_all_files(override_dir=str(override_dir))
        assert result["Dockerfile"].source == "adapter_override"
        assert result["fluent-bit.conf"].source == "adapter_override"
        assert result["nginx.conf"].source == "package_default"
        assert COMPOSE_OVERRIDE_FILENAME in result


# ---------------------------------------------------------------------------
# Tests: log_resolution_summary
# ---------------------------------------------------------------------------

class TestLogResolutionSummary:
    """AC4: Startup summary logging."""

    def test_logs_at_info_level(self, override_dir_str: str, caplog):
        """Summary is logged at INFO level."""
        resolved = resolve_all_files(override_dir=override_dir_str)
        with caplog.at_level(logging.INFO, logger="entr_adapter_core.defaults.resolve"):
            log_resolution_summary(resolved)
        assert len(caplog.records) == 1
        assert caplog.records[0].levelno == logging.INFO

    def test_logs_all_filenames(self, override_dir_str: str, caplog):
        """Every known file appears in the log output."""
        resolved = resolve_all_files(override_dir=override_dir_str)
        with caplog.at_level(logging.INFO, logger="entr_adapter_core.defaults.resolve"):
            log_resolution_summary(resolved)
        output = caplog.text
        for filename in SHADOW_FILES:
            assert filename in output
        for filename in COMPOSE_FILES:
            assert filename in output

    def test_logs_source_labels(self, override_dir_str: str, caplog):
        """Log output contains source labels."""
        resolved = resolve_all_files(override_dir=override_dir_str)
        with caplog.at_level(logging.INFO, logger="entr_adapter_core.defaults.resolve"):
            log_resolution_summary(resolved)
        assert "package default" in caplog.text

    def test_logs_mechanism_labels(self, override_dir_str: str, caplog):
        """Log output contains mechanism labels."""
        resolved = resolve_all_files(override_dir=override_dir_str)
        with caplog.at_level(logging.INFO, logger="entr_adapter_core.defaults.resolve"):
            log_resolution_summary(resolved)
        assert "shadow" in caplog.text
        assert "compose merge" in caplog.text

    def test_override_shown_in_log(self, override_dir: Path, caplog):
        """Override source is visible in log when override exists."""
        (override_dir / "nginx.conf").write_text("# custom")
        resolved = resolve_all_files(override_dir=str(override_dir))
        with caplog.at_level(logging.INFO, logger="entr_adapter_core.defaults.resolve"):
            log_resolution_summary(resolved)
        assert "adapter override" in caplog.text

    def test_table_formatting(self, override_dir_str: str, caplog):
        """Log output includes table border characters."""
        resolved = resolve_all_files(override_dir=override_dir_str)
        with caplog.at_level(logging.INFO, logger="entr_adapter_core.defaults.resolve"):
            log_resolution_summary(resolved)
        output = caplog.text
        assert "┌" in output
        assert "┘" in output
        assert "│" in output


# ---------------------------------------------------------------------------
# Tests: DEFAULT_OVERRIDE_DIR constant
# ---------------------------------------------------------------------------

class TestDefaultOverrideDir:
    """AC5: Public API constant for override directory."""

    def test_value_is_deploy(self):
        """Constant is 'deploy/'."""
        assert DEFAULT_OVERRIDE_DIR == "deploy/"

    def test_used_as_default_in_resolve_file(self):
        """resolve_file uses DEFAULT_OVERRIDE_DIR as default."""
        import inspect
        sig = inspect.signature(resolve_file)
        assert sig.parameters["override_dir"].default == DEFAULT_OVERRIDE_DIR

    def test_used_as_default_in_resolve_compose_files(self):
        """resolve_compose_files uses DEFAULT_OVERRIDE_DIR as default."""
        import inspect
        sig = inspect.signature(resolve_compose_files)
        assert sig.parameters["override_dir"].default == DEFAULT_OVERRIDE_DIR

    def test_used_as_default_in_resolve_all_files(self):
        """resolve_all_files uses DEFAULT_OVERRIDE_DIR as default."""
        import inspect
        sig = inspect.signature(resolve_all_files)
        assert sig.parameters["override_dir"].default == DEFAULT_OVERRIDE_DIR


# ---------------------------------------------------------------------------
# Tests: Top-level exports
# ---------------------------------------------------------------------------

class TestExports:
    """AC6: Public API exports."""

    def test_defaults_init_exports(self):
        """defaults/__init__.py exports all public symbols."""
        from entr_adapter_core.defaults import (
            DEFAULT_OVERRIDE_DIR,
            ResolvedFile,
            log_resolution_summary,
            resolve_all_files,
            resolve_compose_files,
            resolve_file,
        )
        assert resolve_file is not None
        assert resolve_compose_files is not None
        assert resolve_all_files is not None
        assert log_resolution_summary is not None
        assert ResolvedFile is not None
        assert DEFAULT_OVERRIDE_DIR is not None

    def test_top_level_exports(self):
        """Top-level entr_adapter_core exports resolve symbols."""
        from entr_adapter_core import (
            DEFAULT_OVERRIDE_DIR,
            ResolvedFile,
            log_resolution_summary,
            resolve_all_files,
            resolve_compose_files,
            resolve_file,
        )
        assert resolve_file is not None
