from armorer.apps.openclaw.skills import (
    _resolve_runtime_base_image,
    _select_runtime_install_option,
    check_openclaw_skill,
    discover_openclaw_skills,
    ensure_openclaw_skill_runtime_dependencies,
)


def test_discover_openclaw_skills_reads_frontmatter(tmp_path):
    skill_dir = tmp_path / "skills" / "demo"
    skill_dir.mkdir(parents=True)
    (skill_dir / "SKILL.md").write_text(
        """---
name: demo
description: Demo skill
metadata:
  openclaw:
    primaryEnv: DEMO_API_KEY
    requires:
      env: [DEMO_API_KEY, DEMO_REGION]
      bins: [demo-cli]
    install:
      - id: brew
        label: Install Demo CLI
---
"""
    )

    skills = discover_openclaw_skills(tmp_path)

    assert len(skills) == 1
    assert skills[0].key == "demo"
    assert skills[0].primary_env == "DEMO_API_KEY"
    assert skills[0].required_env == ["DEMO_API_KEY", "DEMO_REGION"]
    assert skills[0].required_bins == ["demo-cli"]
    assert skills[0].install_options[0].label == "Install Demo CLI"
    assert skills[0].install_options[0].kind == ""


def test_check_openclaw_skill_reports_missing_requirements(tmp_path, monkeypatch):
    skill_dir = tmp_path / "skills" / "demo"
    skill_dir.mkdir(parents=True)
    (skill_dir / "SKILL.md").write_text(
        """---
name: demo
description: Demo skill
metadata:
  openclaw:
    primaryEnv: DEMO_API_KEY
    requires:
      env: [DEMO_API_KEY]
      bins: [demo-cli]
---
"""
    )
    skill = discover_openclaw_skills(tmp_path)[0]
    monkeypatch.setattr("armorer.apps.openclaw.skills.shutil.which", lambda name: None)

    result = check_openclaw_skill(skill, env_lookup=lambda key: None)

    assert result.ready is False
    assert result.missing_env == ["DEMO_API_KEY"]
    assert result.missing_bins == ["demo-cli"]


def test_check_openclaw_skill_uses_custom_bin_lookup(tmp_path):
    skill_dir = tmp_path / "skills" / "demo"
    skill_dir.mkdir(parents=True)
    (skill_dir / "SKILL.md").write_text(
        """---
name: demo
description: Demo skill
metadata:
  openclaw:
    requires:
      bins: [demo-cli]
---
"""
    )
    skill = discover_openclaw_skills(tmp_path)[0]

    result = check_openclaw_skill(skill, env_lookup=lambda key: None, bin_lookup=lambda name: name == "demo-cli")

    assert result.ready is True
    assert result.missing_bins == []


def test_discover_openclaw_skills_ignores_missing_primary_env(tmp_path):
    skill_dir = tmp_path / "skills" / "demo"
    skill_dir.mkdir(parents=True)
    (skill_dir / "SKILL.md").write_text(
        """---
name: demo
description: Demo skill
metadata:
  openclaw:
    requires:
      bins: [demo-cli]
---
"""
    )

    skill = discover_openclaw_skills(tmp_path)[0]

    assert skill.primary_env is None


def test_discover_openclaw_skills_ignores_none_and_null_in_requirement_lists(tmp_path):
    skill_dir = tmp_path / "skills" / "demo"
    skill_dir.mkdir(parents=True)
    (skill_dir / "SKILL.md").write_text(
        """---
name: demo
description: Demo skill
metadata:
  openclaw:
    primaryEnv:
    requires:
      env: [null, None, DEMO_API_KEY]
      bins: [null, None, demo-cli]
      anyBins: [null, helper-cli]
---
"""
    )

    skill = discover_openclaw_skills(tmp_path)[0]

    assert skill.primary_env is None
    assert skill.required_env == ["DEMO_API_KEY"]
    assert skill.required_bins == ["demo-cli"]
    assert skill.any_bins == ["helper-cli"]


def test_select_runtime_install_option_prefers_npm_then_go(tmp_path):
    skill_dir = tmp_path / "skills" / "demo"
    skill_dir.mkdir(parents=True)
    (skill_dir / "SKILL.md").write_text(
        """---
name: demo
description: Demo skill
metadata:
  openclaw:
    requires:
      bins: [demo-cli]
    install:
      - id: brew
        kind: brew
        formula: demo/tap/demo
        bins: [demo-cli]
      - id: npm
        kind: npm
        package: demo-cli
        bins: [demo-cli]
      - id: go
        kind: go
        module: example.com/demo@latest
        bins: [demo-cli]
---
"""
    )
    skill = discover_openclaw_skills(tmp_path)[0]

    option = _select_runtime_install_option(skill)

    assert option is not None
    assert option.kind == "npm"
    assert option.package == "demo-cli"


def test_select_runtime_install_option_supports_apt(tmp_path):
    skill_dir = tmp_path / "skills" / "demo"
    skill_dir.mkdir(parents=True)
    (skill_dir / "SKILL.md").write_text(
        """---
name: demo
description: Demo skill
metadata:
  openclaw:
    requires:
      bins: [demo-cli]
    install:
      - id: apt
        kind: apt
        package: demo-cli
        bins: [demo-cli]
---
"""
    )
    skill = discover_openclaw_skills(tmp_path)[0]

    option = _select_runtime_install_option(skill)

    assert option is not None
    assert option.kind == "apt"
    assert option.package == "demo-cli"


def test_ensure_openclaw_skill_runtime_dependencies_renders_overlay(tmp_path, monkeypatch):
    skill_dir = tmp_path / "skills" / "demo"
    skill_dir.mkdir(parents=True)
    (skill_dir / "SKILL.md").write_text(
        """---
name: demo
description: Demo skill
metadata:
  openclaw:
    requires:
      bins: [demo-cli]
    install:
      - id: npm
        kind: npm
        package: demo-cli
        bins: [demo-cli]
---
"""
    )
    (tmp_path / "docker-compose.yml").write_text(
        """
services:
  openclaw-gateway:
    image: openclaw:local
  openclaw-cli:
    image: openclaw:local
"""
    )
    skill = discover_openclaw_skills(tmp_path)[0]
    calls = []

    def fake_bin_exists(install_dir, bin_name, service_name=None):
        return len(calls) >= 2

    def fake_run(cmd, cwd=None, check=False, **kwargs):
        calls.append((cmd, cwd, check))
        class Result:
            returncode = 0
        return Result()

    monkeypatch.setattr("armorer.apps.openclaw.skills.runtime_openclaw_bin_exists", fake_bin_exists)
    monkeypatch.setattr("armorer.apps.openclaw.skills.subprocess.run", fake_run)
    # New behavior: docker/compose calls are routed through docker_runner.
    from armorer import docker_runner

    def fake_build_file(*, context, tag, dockerfile, **kwargs):
        calls.append((["docker", "build", "-t", tag, "-f", str(dockerfile), "."], context, True))
        return docker_runner.DockerResult(args=["docker", "build"], returncode=0, stdout="", stderr="")

    def fake_compose_up(*, cwd, **kwargs):
        calls.append((["docker", "compose", "up", "-d", "--force-recreate"], cwd, True))
        return docker_runner.DockerResult(args=["docker", "compose", "up"], returncode=0, stdout="", stderr="")

    monkeypatch.setattr(docker_runner, "docker_build_file", fake_build_file)
    monkeypatch.setattr(docker_runner, "compose_up", fake_compose_up)

    result = ensure_openclaw_skill_runtime_dependencies(tmp_path, [skill])

    assert result["blocked"] == []
    assert "demo" in result["installed"]
    dockerfile = (tmp_path / ".armorer" / "openclaw-runtime.Dockerfile").read_text()
    override = (tmp_path / "docker-compose.override.yml").read_text()
    assert "USER root" in dockerfile
    assert "npm install -g demo-cli" in dockerfile
    assert "openclaw:armorer-" in override
    assert any(call[0][:3] == ["docker", "build", "-t"] for call in calls)
    assert any(call[0] == ["docker", "compose", "up", "-d", "--force-recreate"] for call in calls)


def test_resolve_runtime_base_image_prefers_running_container_configured_image(tmp_path, monkeypatch):
    calls = []

    def fake_run(cmd, cwd=None, capture_output=False, text=False, **kwargs):
        calls.append(cmd)
        class Result:
            def __init__(self, stdout):
                self.stdout = stdout
        if cmd[:5] == ["docker", "compose", "ps", "--status", "running"]:
            return Result("abc123\n")
        if cmd[:3] == ["docker", "inspect", "abc123"]:
            return Result("openclaw:local\n")
        return Result("")

    monkeypatch.setattr("armorer.apps.openclaw.skills.subprocess.run", fake_run)

    result = _resolve_runtime_base_image(tmp_path, "openclaw-gateway", "openclaw:local")

    assert result == "openclaw:local"


def test_resolve_runtime_base_image_falls_back_when_container_returns_digest(tmp_path, monkeypatch):
    def fake_run(cmd, cwd=None, capture_output=False, text=False, **kwargs):
        class Result:
            def __init__(self, stdout):
                self.stdout = stdout
        if cmd[:5] == ["docker", "compose", "ps", "--status", "running"]:
            return Result("abc123\n")
        if cmd[:3] == ["docker", "inspect", "abc123"]:
            return Result("sha256:deadbeef\n")
        return Result("")

    monkeypatch.setattr("armorer.apps.openclaw.skills.subprocess.run", fake_run)

    result = _resolve_runtime_base_image(tmp_path, "openclaw-gateway", "openclaw:local")

    assert result == "openclaw:local"
