"""Tests for profile loading and application."""

import pytest

from gaol.models.container import ContainerConfig, NetworkConfig
from gaol.models.profile import Profile
from gaol.profiles.applicator import MergedProfile, ProfileApplicator, get_profile_applicator
from gaol.profiles.loader import BUILTIN_PROFILES, ProfileLoader, get_profile_loader


class TestBuiltinProfiles:
    """Tests for built-in profiles."""

    def test_base_profile_exists(self) -> None:
        """Test that base profile is defined."""
        assert "base" in BUILTIN_PROFILES
        assert BUILTIN_PROFILES["base"]["name"] == "base"

    def test_all_builtin_profiles_valid(self) -> None:
        """Test that all built-in profiles can be loaded."""
        loader = ProfileLoader()
        for name in BUILTIN_PROFILES:
            profile = loader.get(name)
            assert profile.name == name

    def test_tailscale_profile(self) -> None:
        """Test tailscale profile configuration."""
        loader = ProfileLoader()
        profile = loader.get("tailscale")
        assert profile.requires_root is True
        assert "NET_ADMIN" in profile.capabilities_add
        assert "tailscaled" in profile.services

    def test_gui_profile(self) -> None:
        """Test gui profile configuration."""
        loader = ProfileLoader()
        profile = loader.get("gui")
        assert "DISPLAY" in profile.environment
        assert "/tmp/.X11-unix" in profile.volumes

    def test_dev_python_profile(self) -> None:
        """Test dev-python profile configuration."""
        loader = ProfileLoader()
        profile = loader.get("dev-python")
        assert "python3" in profile.packages
        assert "git" in profile.packages


class TestProfileLoader:
    """Tests for ProfileLoader class."""

    def test_list_available(self) -> None:
        """Test listing available profiles."""
        loader = ProfileLoader()
        available = loader.list_available()
        assert "base" in available
        assert "tailscale" in available
        assert "gui" in available

    def test_get_nonexistent_profile(self) -> None:
        """Test getting a profile that doesn't exist."""
        loader = ProfileLoader()
        with pytest.raises(KeyError, match="Profile not found"):
            loader.get("nonexistent-profile-xyz")

    def test_profile_caching(self) -> None:
        """Test that profiles are cached."""
        loader = ProfileLoader()
        profile1 = loader.get("base")
        profile2 = loader.get("base")
        assert profile1 is profile2  # Same object due to caching

    def test_resolve_dependencies_simple(self) -> None:
        """Test resolving simple dependencies."""
        loader = ProfileLoader()
        profiles = loader.resolve_dependencies(["tailscale"])
        names = [p.name for p in profiles]
        # base should come before tailscale
        assert names.index("base") < names.index("tailscale")

    def test_resolve_dependencies_chain(self) -> None:
        """Test resolving chained dependencies."""
        loader = ProfileLoader()
        # sunshine depends on gui, which depends on base
        profiles = loader.resolve_dependencies(["sunshine"])
        names = [p.name for p in profiles]
        assert names.index("base") < names.index("gui")
        assert names.index("gui") < names.index("sunshine")

    def test_resolve_dependencies_no_duplicates(self) -> None:
        """Test that dependencies aren't duplicated."""
        loader = ProfileLoader()
        profiles = loader.resolve_dependencies(["tailscale", "dev-python"])
        names = [p.name for p in profiles]
        # base should only appear once
        assert names.count("base") == 1


class TestGlobalLoader:
    """Tests for global profile loader."""

    def test_get_profile_loader(self) -> None:
        """Test getting global loader instance."""
        loader1 = get_profile_loader()
        loader2 = get_profile_loader()
        # Should be the same instance
        assert loader1 is loader2


class TestMergedProfile:
    """Tests for MergedProfile class."""

    def test_empty_merged_profile(self) -> None:
        """Test creating an empty merged profile."""
        merged = MergedProfile()
        assert merged.profiles == []
        assert merged.packages == []
        assert merged.services == []
        assert merged.requires_privileged is False

    def test_merge_single_profile(self) -> None:
        """Test merging a single profile."""
        profile = Profile(
            name="test",
            packages=["pkg1", "pkg2"],
            services=["svc1"],
            environment={"KEY": "value"},
        )
        merged = MergedProfile()
        merged.merge_profile(profile)

        assert merged.profiles == ["test"]
        assert merged.packages == ["pkg1", "pkg2"]
        assert merged.services == ["svc1"]
        assert merged.environment == {"KEY": "value"}

    def test_merge_multiple_profiles(self) -> None:
        """Test merging multiple profiles."""
        profile1 = Profile(
            name="profile1",
            packages=["pkg1", "common"],
            environment={"KEY1": "val1", "SHARED": "from1"},
        )
        profile2 = Profile(
            name="profile2",
            packages=["pkg2", "common"],
            environment={"KEY2": "val2", "SHARED": "from2"},
        )
        merged = MergedProfile()
        merged.merge_profile(profile1)
        merged.merge_profile(profile2)

        assert merged.profiles == ["profile1", "profile2"]
        # Packages should be deduplicated
        assert merged.packages == ["pkg1", "common", "pkg2"]
        # Environment from later profile overrides
        assert merged.environment["SHARED"] == "from2"
        assert merged.environment["KEY1"] == "val1"
        assert merged.environment["KEY2"] == "val2"

    def test_merge_capabilities(self) -> None:
        """Test merging capabilities from profiles."""
        profile = Profile(
            name="test",
            capabilities_add=["NET_ADMIN", "SYS_PTRACE"],
            capabilities_drop=["NET_RAW"],
        )
        merged = MergedProfile()
        merged.merge_profile(profile)

        assert "NET_ADMIN" in merged.capabilities_add
        assert "SYS_PTRACE" in merged.capabilities_add
        assert "NET_RAW" in merged.capabilities_drop

    def test_merge_privileged_flag(self) -> None:
        """Test that privileged flag is set if any profile requires it."""
        profile1 = Profile(name="normal", requires_privileged=False)
        profile2 = Profile(name="privileged", requires_privileged=True)
        profile3 = Profile(name="normal2", requires_privileged=False)

        merged = MergedProfile()
        merged.merge_profile(profile1)
        assert merged.requires_privileged is False

        merged.merge_profile(profile2)
        assert merged.requires_privileged is True

        merged.merge_profile(profile3)
        # Should still be True
        assert merged.requires_privileged is True

    def test_generate_setup_script_debian(self) -> None:
        """Test generating setup script for Debian."""
        merged = MergedProfile(
            packages=["vim", "curl"],
            services=["nginx"],
            setup_commands=["echo 'Hello World'"],
        )
        script = merged.generate_setup_script("trixie")

        assert "#!/bin/bash" in script
        assert "apt-get update" in script
        assert "apt-get install -y vim curl" in script
        assert "echo 'Hello World'" in script
        assert "systemctl enable nginx" in script

    def test_generate_setup_script_alpine(self) -> None:
        """Test generating setup script for Alpine."""
        merged = MergedProfile(packages=["vim"])
        script = merged.generate_setup_script("alpine")

        assert "apk update" in script
        assert "apk add --no-cache vim" in script

    def test_generate_setup_script_fedora(self) -> None:
        """Test generating setup script for Fedora."""
        merged = MergedProfile(packages=["vim"])
        script = merged.generate_setup_script("fedora")

        assert "dnf" in script
        # vim is translated to vim-enhanced on Fedora
        assert "vim-enhanced" in script

    def test_generate_setup_script_with_files(self) -> None:
        """Test generating setup script with file creation."""
        merged = MergedProfile(
            files={"/etc/test.conf": "key=value\n"}
        )
        script = merged.generate_setup_script("trixie")

        assert "cat > '/etc/test.conf'" in script
        assert "key=value" in script
        assert "GAOL_EOF" in script


class TestProfileApplicator:
    """Tests for ProfileApplicator class."""

    def test_resolve_and_merge_empty(self) -> None:
        """Test resolving empty profile list."""
        applicator = ProfileApplicator()
        merged = applicator.resolve_and_merge([])
        assert merged.profiles == []

    def test_resolve_and_merge_single(self) -> None:
        """Test resolving single profile."""
        applicator = ProfileApplicator()
        merged = applicator.resolve_and_merge(["base"])
        assert "base" in merged.profiles

    def test_resolve_and_merge_with_dependencies(self) -> None:
        """Test resolving profiles with dependencies."""
        applicator = ProfileApplicator()
        merged = applicator.resolve_and_merge(["tailscale"])
        # Should include base (dependency) and tailscale
        assert "base" in merged.profiles
        assert "tailscale" in merged.profiles
        # base should come before tailscale
        assert merged.profiles.index("base") < merged.profiles.index("tailscale")

    def test_apply_to_config_environment(self) -> None:
        """Test applying profile environment to config."""
        config = ContainerConfig(
            name="test",
            distro="trixie",
            environment={"USER_VAR": "user_value"},
        )
        merged = MergedProfile(
            profiles=["test"],
            environment={"PROFILE_VAR": "profile_value", "USER_VAR": "profile_default"},
        )
        applicator = ProfileApplicator()
        updated = applicator.apply_to_config(config, merged)

        # User value should override profile default
        assert updated.environment["USER_VAR"] == "user_value"
        # Profile var should be present
        assert updated.environment["PROFILE_VAR"] == "profile_value"

    def test_apply_to_config_volumes(self) -> None:
        """Test applying profile volumes to config."""
        config = ContainerConfig(
            name="test",
            distro="trixie",
            volumes={"/host/user": "/container/user"},
        )
        merged = MergedProfile(
            profiles=["test"],
            volumes={"/host/profile": "/container/profile", "/host/user": "/container/default"},
        )
        applicator = ProfileApplicator()
        updated = applicator.apply_to_config(config, merged)

        # User volume should override
        assert updated.volumes["/host/user"] == "/container/user"
        # Profile volume should be present
        assert updated.volumes["/host/profile"] == "/container/profile"

    def test_apply_to_config_ports(self) -> None:
        """Test applying profile exposed ports to config."""
        config = ContainerConfig(
            name="test",
            distro="trixie",
            network=NetworkConfig(ports={8080: 80}),
        )
        merged = MergedProfile(
            profiles=["test"],
            exposed_ports=[443, 80],  # 80 already mapped
        )
        applicator = ProfileApplicator()
        updated = applicator.apply_to_config(config, merged)

        # Existing mapping preserved
        assert updated.network.ports[8080] == 80
        # New port added (443 -> 443)
        assert 443 in updated.network.ports

    def test_apply_to_config_privileged(self) -> None:
        """Test applying privileged flag from profile."""
        config = ContainerConfig(
            name="test",
            distro="trixie",
            privileged=False,
        )
        merged = MergedProfile(
            profiles=["test"],
            requires_privileged=True,
        )
        applicator = ProfileApplicator()
        updated = applicator.apply_to_config(config, merged)

        assert updated.privileged is True

    def test_apply_to_config_preserves_profiles(self) -> None:
        """Test that applied profiles are stored in config."""
        config = ContainerConfig(name="test", distro="trixie")
        merged = MergedProfile(profiles=["base", "tailscale"])
        applicator = ProfileApplicator()
        updated = applicator.apply_to_config(config, merged)

        assert updated.profiles == ["base", "tailscale"]


class TestGetProfileApplicator:
    """Tests for get_profile_applicator function."""

    def test_get_profile_applicator(self) -> None:
        """Test getting profile applicator."""
        applicator = get_profile_applicator()
        assert isinstance(applicator, ProfileApplicator)
