"""Tests for monorepo / platform detection helpers added to DynamicInstaller."""

from unittest.mock import MagicMock, patch
import pytest
from pathlib import Path

from armorer.installers.dynamic import DynamicInstaller
from armorer.profiles import RiskProfile, NetworkPolicy, FilesystemPolicy


# ── Helper to build a DynamicInstaller with a tmp install_dir ─────────────────

def _make_installer(tmp_path: Path) -> DynamicInstaller:
    """Build a DynamicInstaller with mocked dependencies."""
    with patch("armorer.knowledge.KnowledgeBase"), \
         patch("armorer.installers.dynamic.SearchTool"), \
         patch("armorer.installers.dynamic.ArmorerAgent"), \
         patch("armorer.registry.AppRegistry.REGISTRY_FILE", tmp_path / "registry.json"):
        profile = RiskProfile(
            "TEST", "Test",
            NetworkPolicy.OPEN, FilesystemPolicy.NONE, False,
        )
        inst = DynamicInstaller("test-app", profile, [])
        inst.install_dir = tmp_path
        return inst


# ── _find_best_compose ────────────────────────────────────────────────────────


def test_find_best_compose_prefers_subdir_with_more_services(tmp_path):
    """A subdir compose with 2x+ more services should win over root."""
    # Root compose: 2 services
    root_compose = tmp_path / "docker-compose.yml"
    root_compose.write_text(
        "services:\n"
        "  web:\n    image: nginx\n"
        "  db:\n    image: postgres\n"
    )

    # Subdir compose: 6 services (platform-style)
    platform_dir = tmp_path / "platform"
    platform_dir.mkdir()
    (platform_dir / "docker-compose.yml").write_text(
        "services:\n"
        "  backend:\n    image: app-backend\n"
        "  frontend:\n    image: app-frontend\n"
        "  redis:\n    image: redis\n"
        "  postgres:\n    image: postgres\n"
        "  rabbitmq:\n    image: rabbitmq\n"
        "  worker:\n    image: app-worker\n"
    )

    inst = _make_installer(tmp_path)
    best = inst._find_best_compose()
    assert best == platform_dir / "docker-compose.yml"


def test_find_best_compose_uses_root_when_adequate(tmp_path):
    """When root compose has ≥3 services and subdir doesn't have 2x, use root."""
    root_compose = tmp_path / "docker-compose.yml"
    root_compose.write_text(
        "services:\n"
        "  web:\n    image: nginx\n"
        "  api:\n    image: flask\n"
        "  db:\n    image: postgres\n"
    )

    # Subdir compose: 4 services (not 2x more than root's 3)
    sub = tmp_path / "deploy"
    sub.mkdir()
    (sub / "docker-compose.yml").write_text(
        "services:\n"
        "  web:\n    image: nginx\n"
        "  api:\n    image: flask\n"
        "  db:\n    image: postgres\n"
        "  cache:\n    image: redis\n"
    )

    inst = _make_installer(tmp_path)
    best = inst._find_best_compose()
    assert best == root_compose


def test_find_best_compose_no_compose_returns_none(tmp_path):
    """When no compose file exists, return None."""
    inst = _make_installer(tmp_path)
    assert inst._find_best_compose() is None


def test_find_best_compose_only_subdir(tmp_path):
    """When only a subdir has a compose file, return it."""
    sub = tmp_path / "autogpt_platform"
    sub.mkdir()
    compose = sub / "docker-compose.yml"
    compose.write_text(
        "services:\n  backend:\n    image: backend\n"
    )

    inst = _make_installer(tmp_path)
    assert inst._find_best_compose() == compose


def test_find_best_compose_skips_test_dirs(tmp_path):
    """Compose files inside test/ directories should be ignored."""
    test_dir = tmp_path / "tests"
    test_dir.mkdir()
    (test_dir / "docker-compose.yml").write_text(
        "services:\n  test:\n    image: test\n"
    )

    # Real compose in root
    root = tmp_path / "docker-compose.yml"
    root.write_text(
        "services:\n  app:\n    image: app\n"
    )

    inst = _make_installer(tmp_path)
    assert inst._find_best_compose() == root


# ── _detect_setup_sequence ────────────────────────────────────────────────────


def test_detect_setup_sequence_finds_env_defaults(tmp_path):
    """Should detect .env.default files that need copying."""
    inst = _make_installer(tmp_path)

    (tmp_path / ".env.default").write_text("FOO=bar")

    steps = inst._detect_setup_sequence()
    env_steps = [s for s in steps if s["type"] == "env_copy"]
    assert len(env_steps) == 1
    assert ".env.default" in env_steps[0]["label"]


def test_detect_setup_sequence_skips_existing_env(tmp_path):
    """Should NOT detect .env.default if .env already exists."""
    inst = _make_installer(tmp_path)

    (tmp_path / ".env.default").write_text("FOO=bar")
    (tmp_path / ".env").write_text("FOO=baz")

    steps = inst._detect_setup_sequence()
    env_steps = [s for s in steps if s["type"] == "env_copy"]
    assert len(env_steps) == 0


def test_detect_setup_sequence_finds_makefile_targets(tmp_path):
    """Should detect setup-related Makefile targets."""
    inst = _make_installer(tmp_path)

    (tmp_path / "Makefile").write_text(
        "# Initialize environment\n"
        "init-env:\n"
        "\tcp .env.default .env\n"
        "\n"
        "start-core:\n"
        "\tdocker compose up -d redis\n"
        "\n"
        "# Run database migrations\n"
        "migrate:\n"
        "\tprisma migrate deploy\n"
        "\n"
        "run-frontend:\n"
        "\tnpm start\n"
    )

    steps = inst._detect_setup_sequence()
    make_steps = [s for s in steps if s["type"] == "makefile"]
    # Should find init-env, start-core, and migrate (not run-frontend)
    labels = [s["label"] for s in make_steps]
    assert any("init-env" in l for l in labels)
    assert any("migrate" in l for l in labels)


def test_detect_setup_sequence_in_compose_dir(tmp_path):
    """When _compose_dir is set, should scan from there."""
    platform = tmp_path / "autogpt_platform"
    platform.mkdir()
    (platform / ".env.default").write_text("KEY=val")
    (platform / "Makefile").write_text("setup:\n\techo setup\n")

    inst = _make_installer(tmp_path)
    inst._compose_dir = platform

    steps = inst._detect_setup_sequence()
    assert len(steps) >= 2  # env_copy + makefile


def test_detect_setup_sequence_translates_playbook_command_cwd_to_host(tmp_path, monkeypatch):
    """Playbook command cwd should use the host-visible compose directory in sandbox mode."""
    host_compose = tmp_path / "installed" / "openclaw"
    host_compose.mkdir(parents=True)

    inst = _make_installer(tmp_path)
    inst.app_name = "openclaw"
    inst.install_dir = Path("/app/installed/openclaw")

    monkeypatch.setenv("HOST_PROJECT_ROOT", str(tmp_path))
    monkeypatch.setattr("os.path.exists", lambda p: True if p == "/.dockerenv" else Path(p).exists())

    steps = inst._detect_setup_sequence()
    command_steps = [s for s in steps if s["type"] == "command"]
    assert command_steps
    assert all(s["cwd"] == str(host_compose) for s in command_steps)


def test_compose_runtime_env_translates_relative_paths_to_host(tmp_path, monkeypatch):
    host_compose = tmp_path / "installed" / "openclaw"
    host_compose.mkdir(parents=True)
    (host_compose / ".env").write_text(
        "OPENCLAW_CONFIG_DIR=./.openclaw\n"
        "OPENCLAW_WORKSPACE_DIR=./workspace\n"
    )

    inst = _make_installer(tmp_path)
    inst.install_dir = Path("/app/installed/openclaw")

    monkeypatch.setenv("HOST_PROJECT_ROOT", str(tmp_path))
    monkeypatch.setattr("os.path.exists", lambda p: True if p == "/.dockerenv" else Path(p).exists())

    env = inst._compose_runtime_env()
    assert env["OPENCLAW_CONFIG_DIR"] == str((host_compose / ".openclaw").resolve())
    assert env["OPENCLAW_WORKSPACE_DIR"] == str((host_compose / "workspace").resolve())


def test_detect_setup_sequence_finds_setup_scripts(tmp_path):
    """Should detect setup.sh and similar scripts."""
    inst = _make_installer(tmp_path)

    (tmp_path / "setup.sh").write_text("#!/bin/bash\necho setup")

    steps = inst._detect_setup_sequence()
    script_steps = [s for s in steps if s["type"] == "script"]
    assert len(script_steps) == 1
    assert "setup.sh" in script_steps[0]["label"]


# ── _docker_compose_cwd property ─────────────────────────────────────────────


def test_docker_compose_cwd_defaults_to_install_dir(tmp_path):
    """Without _compose_dir set, should return install_dir."""
    inst = _make_installer(tmp_path)
    assert inst._docker_compose_cwd == tmp_path


def test_docker_compose_cwd_returns_compose_dir(tmp_path):
    """With _compose_dir set, should return that instead of install_dir."""
    subdir = tmp_path / "platform"
    subdir.mkdir()

    inst = _make_installer(tmp_path)
    inst._compose_dir = subdir
    assert inst._docker_compose_cwd == subdir
