"""Unit tests for the manifest compiler."""

import pytest

from src.services.manifest_compiler import (
    compile_compose,
    compile_dockerfile,
    compile_entrypoint,
    compute_image_tag,
    deep_merge,
    get_manifest_home_dir,
    merge_with_config,
    resolve_base,
)


class TestResolveBase:
    """Tests for resolve_base."""

    def test_returns_manifest_unchanged_when_no_base(self) -> None:
        manifest = {"name": "test", "base_image": "ubuntu:24.04"}
        result = resolve_base(manifest)
        assert result["name"] == "test"
        assert "base_definition_id" not in result


class TestDeepMerge:
    """Tests for deep_merge."""

    def test_packages_are_unioned(self) -> None:
        base = {"packages": {"apt": ["curl", "git"]}}
        override = {"packages": {"apt": ["neovim"]}}
        result = deep_merge(base, override)
        assert result["packages"]["apt"] == ["curl", "git", "neovim"]

    def test_node_version_overrides(self) -> None:
        base = {"packages": {"node": {"version": "18"}}}
        override = {"packages": {"node": {"version": "20"}}}
        result = deep_merge(base, override)
        assert result["packages"]["node"]["version"] == "20"

    def test_env_is_merged_with_override_winning(self) -> None:
        base = {"env": {"FOO": "base", "BAR": "base"}}
        override = {"env": {"FOO": "override"}}
        result = deep_merge(base, override)
        assert result["env"]["FOO"] == "override"
        assert result["env"]["BAR"] == "base"

    def test_build_scripts_are_concatenated(self) -> None:
        base = {"scripts": {"build": ["echo base"]}}
        override = {"scripts": {"build": ["echo override"]}}
        result = deep_merge(base, override)
        assert result["scripts"]["build"] == ["echo base", "echo override"]

    def test_mounts_are_concatenated(self) -> None:
        base = {"mounts": [{"name": "base-mount", "target": "/base"}]}
        override = {"mounts": [{"name": "tool-mount", "target": "/tool"}]}
        result = deep_merge(base, override)
        assert len(result["mounts"]) == 2

    def test_user_is_overridden_entirely(self) -> None:
        base = {"user": {"name": "base", "uid": 1000}}
        override = {"user": {"name": "tool", "uid": 1001}}
        result = deep_merge(base, override)
        assert result["user"]["name"] == "tool"
        assert result["user"]["uid"] == 1001


class TestCompileDockerfile:
    """Tests for compile_dockerfile."""

    def test_includes_from(self) -> None:
        manifest = {"base_image": "ubuntu:24.04", "name": "test"}
        df = compile_dockerfile(manifest)
        assert "FROM ubuntu:24.04" in df

    def test_installs_apt_packages(self) -> None:
        manifest = {
            "base_image": "ubuntu:24.04",
            "name": "test",
            "packages": {"apt": ["curl", "git"]},
        }
        df = compile_dockerfile(manifest)
        assert "apt-get install -y" in df
        assert "curl" in df
        assert "git" in df
        assert "rm -rf /var/lib/apt/lists/*" in df

    def test_installs_node(self) -> None:
        manifest = {
            "base_image": "ubuntu:24.04",
            "name": "test",
            "packages": {"node": {"version": "20"}},
        }
        df = compile_dockerfile(manifest)
        assert "nodesource.com/setup_20.x" in df

    def test_installs_npm_global(self) -> None:
        manifest = {
            "base_image": "ubuntu:24.04",
            "name": "test",
            "packages": {"npm_global": ["@scope/pkg"]},
        }
        df = compile_dockerfile(manifest)
        assert "npm install -g @scope/pkg" in df

    def test_creates_user(self) -> None:
        manifest = {
            "base_image": "ubuntu:24.04",
            "name": "test",
            "user": {"name": "dev", "uid": 1001, "gid": 1001},
        }
        df = compile_dockerfile(manifest)
        assert "groupadd -g 1001 dev" in df
        assert "useradd -u 1001 -g 1001" in df
        assert "USER dev" in df

    def test_build_scripts_as_run_commands(self) -> None:
        manifest = {
            "base_image": "ubuntu:24.04",
            "name": "test",
            "scripts": {"build": ["echo hello", "echo world"]},
        }
        df = compile_dockerfile(manifest)
        assert "RUN echo hello" in df
        assert "RUN echo world" in df

    def test_creates_mount_directories(self) -> None:
        manifest = {
            "base_image": "ubuntu:24.04",
            "name": "test",
            "user": {"name": "dev", "uid": 1001, "gid": 1001},
            "mounts": [
                {"name": "ws", "target": "/workspace"},
                {"name": "cfg", "target": "/config"},
            ],
        }
        df = compile_dockerfile(manifest)
        assert "mkdir -p /workspace /config" in df
        assert "chown -R dev:dev /workspace /config" in df

    def test_entrypoint_for_startup_scripts(self) -> None:
        manifest = {
            "base_image": "ubuntu:24.04",
            "name": "test",
            "scripts": {"startup": ["echo start"]},
        }
        df = compile_dockerfile(manifest)
        assert 'ENTRYPOINT ["/usr/local/bin/headquarter-entrypoint"]' in df

    def test_cmd_from_runtime(self) -> None:
        manifest = {
            "base_image": "ubuntu:24.04",
            "name": "test",
            "runtime": {"command": ["/bin/bash", "-il"]},
        }
        df = compile_dockerfile(manifest)
        assert 'CMD ["/bin/bash", "-il"]' in df

    def test_default_cmd_when_no_runtime(self) -> None:
        manifest = {"base_image": "ubuntu:24.04", "name": "test"}
        df = compile_dockerfile(manifest)
        assert 'CMD ["/bin/bash"]' in df

    def test_sets_home_env_for_user(self) -> None:
        manifest = {
            "base_image": "ubuntu:24.04",
            "name": "test",
            "user": {"name": "dev", "uid": 1001, "gid": 1001},
        }
        df = compile_dockerfile(manifest)
        assert "ENV HOME=/home/dev" in df
        assert "ENV USER=dev" in df

    def test_no_home_env_without_user(self) -> None:
        manifest = {"base_image": "ubuntu:24.04", "name": "test"}
        df = compile_dockerfile(manifest)
        assert "ENV HOME=" not in df
        assert "ENV USER=" not in df


class TestGetManifestHomeDir:
    """Tests for get_manifest_home_dir."""

    def test_with_user_name(self) -> None:
        manifest = {"user": {"name": "dev", "uid": 1001, "gid": 1001}}
        assert get_manifest_home_dir(manifest) == "/home/dev"

    def test_without_user(self) -> None:
        manifest = {"base_image": "ubuntu:24.04"}
        assert get_manifest_home_dir(manifest) == "/root"

    def test_with_empty_user_name(self) -> None:
        manifest = {"user": {"name": "", "uid": 1001, "gid": 1001}}
        assert get_manifest_home_dir(manifest) == "/root"


class TestCompileEntrypoint:
    """Tests for compile_entrypoint."""

    def test_includes_shebang_and_set_e(self) -> None:
        manifest = {"scripts": {"startup": ["echo hello"]}}
        ep = compile_entrypoint(manifest)
        assert "#!/bin/bash" in ep
        assert "set -e" in ep

    def test_includes_startup_scripts(self) -> None:
        manifest = {"scripts": {"startup": ["echo hello", "echo world"]}}
        ep = compile_entrypoint(manifest)
        assert "echo hello" in ep
        assert "echo world" in ep

    def test_ends_with_exec(self) -> None:
        manifest: dict = {"scripts": {"startup": []}}
        ep = compile_entrypoint(manifest)
        assert 'exec "$@"' in ep


class TestCompileCompose:
    """Tests for compile_compose."""

    def test_includes_image_and_container_name(self) -> None:
        manifest = {"name": "test", "interface_type": "terminal"}
        vars_dict = {"IMAGE_TAG": "test:v1", "INSTANCE_NAME": "test-1"}
        compose = compile_compose(manifest, vars_dict)
        assert "image: test:v1" in compose
        assert "container_name: test-1" in compose

    def test_terminal_fields(self) -> None:
        manifest = {
            "name": "test",
            "interface_type": "terminal",
            "runtime": {"stdin_open": True, "tty": True, "working_dir": "/workspace"},
        }
        compose = compile_compose(manifest, {"IMAGE_TAG": "t", "INSTANCE_NAME": "n"})
        assert "stdin_open: true" in compose
        assert "tty: true" in compose
        assert "working_dir: /workspace" in compose

    def test_web_ports(self) -> None:
        manifest = {
            "name": "test",
            "interface_type": "web",
            "default_port": 8080,
        }
        compose = compile_compose(
            manifest, {"IMAGE_TAG": "t", "INSTANCE_NAME": "n", "TOOL_PORT": "3000"}
        )
        assert "3000:8080" in compose

    def test_user_override(self) -> None:
        manifest = {
            "name": "test",
            "interface_type": "terminal",
            "user": {"uid": 1001, "gid": 1001},
        }
        compose = compile_compose(manifest, {"IMAGE_TAG": "t", "INSTANCE_NAME": "n"})
        assert "user: 1001:1001" in compose

    def test_mounts_resolved(self) -> None:
        manifest = {
            "name": "test",
            "interface_type": "terminal",
            "mounts": [
                {"name": "ws", "target": "/workspace", "source_type": "repo"},
                {
                    "name": "ssh",
                    "target": "/home/user/.ssh",
                    "source_type": "ssh_key",
                    "readonly": True,
                },
            ],
        }
        compose = compile_compose(
            manifest,
            {
                "IMAGE_TAG": "t",
                "INSTANCE_NAME": "n",
                "REPO_PATH": "/repos/myrepo",
                "SSH_PATH": "/keys/ssh",
            },
        )
        assert "/repos/myrepo:/workspace" in compose
        assert "/keys/ssh:/home/user/.ssh:ro" in compose

    def test_extra_volumes_appended(self) -> None:
        manifest = {"name": "test", "interface_type": "terminal"}
        compose = compile_compose(
            manifest,
            {
                "IMAGE_TAG": "t",
                "INSTANCE_NAME": "n",
                "EXTRA_VOLUMES": [{"source": "/host/x", "target": "/container/x"}],
            },
        )
        assert "/host/x:/container/x" in compose


class TestComputeImageTag:
    """Tests for compute_image_tag."""

    def test_is_deterministic(self) -> None:
        manifest = {"name": "test", "packages": {"apt": ["curl"]}}
        tag1 = compute_image_tag("My Tool", manifest)
        tag2 = compute_image_tag("My Tool", manifest)
        assert tag1 == tag2

    def test_changes_with_content(self) -> None:
        manifest1 = {"name": "test", "packages": {"apt": ["curl"]}}
        manifest2 = {"name": "test", "packages": {"apt": ["wget"]}}
        tag1 = compute_image_tag("test", manifest1)
        tag2 = compute_image_tag("test", manifest2)
        assert tag1 != tag2

    def test_lowercases_name(self) -> None:
        manifest = {"name": "test"}
        tag = compute_image_tag("My Tool", manifest)
        assert "my-tool" in tag

    def test_valid_docker_reference(self) -> None:
        manifest = {"name": "test"}
        tag = compute_image_tag("test", manifest)
        assert tag.startswith("headquarter/test-")
        assert tag.endswith(":latest")


class TestMergeWithConfig:
    """Tests for merge_with_config (ConfigProfile only)."""

    def test_no_profile_returns_manifest_unchanged(self) -> None:
        manifest = {"name": "test"}
        result = merge_with_config(manifest)
        assert result["name"] == "test"
        assert result["_extra_env"] == {}
        assert result["_extra_volumes"] == []

    def test_profile_env_vars(self) -> None:
        manifest = {"name": "test"}
        profile = {"environment_variables": {"FOO": "bar"}}
        result = merge_with_config(manifest, profile)
        assert result["_extra_env"]["FOO"] == "bar"

    def test_profile_mounts(self) -> None:
        manifest = {"name": "test"}
        profile = {"mounts": [{"source": "/host", "target": "/container"}]}
        result = merge_with_config(manifest, profile)
        assert len(result["_extra_volumes"]) == 1

    def test_profile_port_override(self) -> None:
        manifest = {"name": "test", "default_port": 8080}
        profile = {"hints": {"port_override": 3000}}
        result = merge_with_config(manifest, profile)
        assert result["default_port"] == 3000

    def test_profile_start_command(self) -> None:
        manifest = {"name": "test", "runtime": {"command": ["/bin/bash"]}}
        profile = {"hints": {"start_command": "/bin/sh"}}
        result = merge_with_config(manifest, profile)
        assert result["runtime"]["command"] == ["/bin/sh"]

    def test_profile_working_directory(self) -> None:
        manifest = {"name": "test"}
        profile = {"hints": {"working_directory": "/workspace"}}
        result = merge_with_config(manifest, profile)
        assert result["runtime"]["working_dir"] == "/workspace"
