"""Tests for CLI migration helper utilities."""

import json
import os
import re
import subprocess
import sys
from pathlib import Path
from subprocess import CalledProcessError, CompletedProcess

import pytest

from agency_swarm.cli import migrate_agent


def _sanitize_name(name: str) -> str:
    """Mirror the generator's sanitizeName helper for assertions."""
    sanitized = re.sub(r"\s+", "_", name)
    sanitized = re.sub(r"[^a-zA-Z0-9_]", "", sanitized)
    sanitized = re.sub(r"_+", "_", sanitized)
    sanitized = sanitized.strip("_")
    if sanitized and sanitized[0].isdigit():
        sanitized = f"agent_{sanitized}"
    return sanitized or "agent"


def test_check_node_dependencies_requires_node(monkeypatch: pytest.MonkeyPatch) -> None:
    """Ensure dependency check fails when Node.js is unavailable."""

    def fake_run(command, capture_output, check, shell):  # type: ignore[no-untyped-def]
        raise FileNotFoundError()

    monkeypatch.setattr("agency_swarm.cli.migrate_agent.subprocess.run", fake_run)

    available, runner = migrate_agent.check_node_dependencies()
    assert available is False
    assert runner == ""


def test_check_node_dependencies_requires_ts_node(monkeypatch: pytest.MonkeyPatch) -> None:
    """Ensure dependency check fails when tsx/ts-node is unavailable even if Node is present."""

    def fake_run(command, capture_output, check, shell):  # type: ignore[no-untyped-def]
        # Node is available
        if command[0] == "node":
            return CompletedProcess(command, 0)
        # tsx and ts-node are not available
        raise CalledProcessError(1, command)

    monkeypatch.setattr("agency_swarm.cli.migrate_agent.subprocess.run", fake_run)

    available, runner = migrate_agent.check_node_dependencies()
    assert available is False
    assert runner == ""


def test_check_node_dependencies_succeeds_with_tsx(monkeypatch: pytest.MonkeyPatch) -> None:
    """Dependency check should succeed when tsx is available as preferred runner."""

    def fake_run(command, capture_output, check, shell):  # type: ignore[no-untyped-def]
        if command[0] == "node":
            return CompletedProcess(command, 0)
        if command[:2] == ["npx", "tsx"]:
            return CompletedProcess(command, 0)
        raise CalledProcessError(1, command)

    monkeypatch.setattr("agency_swarm.cli.migrate_agent.subprocess.run", fake_run)

    available, runner = migrate_agent.check_node_dependencies()
    assert available is True
    assert runner == "tsx"


def test_check_node_dependencies_succeeds_with_npx_ts_node(monkeypatch: pytest.MonkeyPatch) -> None:
    """Dependency check should succeed when npx ts-node is available."""

    def fake_run(command, capture_output, check, shell):  # type: ignore[no-untyped-def]
        if command[0] == "node":
            return CompletedProcess(command, 0)
        if command[:2] == ["npx", "tsx"]:
            raise CalledProcessError(1, command)  # tsx not available
        if command[:2] == ["npx", "ts-node"]:
            return CompletedProcess(command, 0)
        raise CalledProcessError(1, command)

    monkeypatch.setattr("agency_swarm.cli.migrate_agent.subprocess.run", fake_run)

    available, runner = migrate_agent.check_node_dependencies()
    assert available is True
    assert runner == "ts-node"


def test_check_node_dependencies_succeeds_with_global_ts_node(monkeypatch: pytest.MonkeyPatch) -> None:
    """Dependency check should succeed with globally installed ts-node when npx fails."""

    def fake_run(command, capture_output, check, shell):  # type: ignore[no-untyped-def]
        if command[0] == "node":
            return CompletedProcess(command, 0)
        if command[:2] == ["npx", "tsx"]:
            raise CalledProcessError(1, command)  # tsx not available
        if command[:2] == ["npx", "ts-node"]:
            raise CalledProcessError(1, command)  # npx ts-node not available
        if command[0] == "ts-node":
            return CompletedProcess(command, 0)  # global install available
        raise CalledProcessError(1, command)

    monkeypatch.setattr("agency_swarm.cli.migrate_agent.subprocess.run", fake_run)

    available, runner = migrate_agent.check_node_dependencies()
    assert available is True
    assert runner == "ts-node"


def test_find_typescript_script_exists() -> None:
    """The generator script should be discoverable in the package."""
    ts_path = migrate_agent.find_typescript_script()
    assert ts_path is not None
    assert ts_path.exists()
    assert ts_path.name == "generate-agent-from-settings.ts"


def test_migrate_agent_command_missing_settings_returns_error(tmp_path: Path) -> None:
    """Missing settings files should return exit code 1."""
    exit_code = migrate_agent.migrate_agent_command(str(tmp_path / "missing.json"), str(tmp_path))
    assert exit_code == 1


def test_migrate_agent_command_missing_script_returns_error(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Missing TypeScript script should return exit code 1."""
    settings_path = tmp_path / "settings.json"
    settings_path.write_text("{}")

    monkeypatch.setattr(migrate_agent, "find_typescript_script", lambda: None)

    exit_code = migrate_agent.migrate_agent_command(str(settings_path), str(tmp_path))
    assert exit_code == 1


def test_migrate_agent_command_dependency_failure_returns_error(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Dependency check failure should return exit code 1."""
    settings_path = tmp_path / "settings.json"
    settings_path.write_text("{}")

    ts_script = tmp_path / "generate-agent-from-settings.ts"
    ts_script.write_text("// stub")

    monkeypatch.setattr(migrate_agent, "find_typescript_script", lambda: ts_script)
    monkeypatch.setattr(migrate_agent, "check_node_dependencies", lambda: (False, ""))

    exit_code = migrate_agent.migrate_agent_command(str(settings_path), str(tmp_path))
    assert exit_code == 1


def test_migrate_agent_command_successful_execution(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Successful execution should return exit code 0."""
    settings_path = tmp_path / "settings.json"
    settings_path.write_text("{}")

    ts_script = tmp_path / "generate-agent-from-settings.ts"
    ts_script.write_text("// stub")

    monkeypatch.setattr(migrate_agent, "find_typescript_script", lambda: ts_script)
    monkeypatch.setattr(migrate_agent, "check_node_dependencies", lambda: (True, "tsx"))

    completed = CompletedProcess(["npx", "tsx"], 0, stdout="Agent generated successfully\n", stderr="")

    def fake_run(command, capture_output, text, shell):  # type: ignore[no-untyped-def]
        return completed

    monkeypatch.setattr("agency_swarm.cli.migrate_agent.subprocess.run", fake_run)

    exit_code = migrate_agent.migrate_agent_command(str(settings_path), str(tmp_path))
    assert exit_code == 0


def test_migrate_agent_command_failed_execution(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Failed execution should return non-zero exit code."""
    settings_path = tmp_path / "settings.json"
    settings_path.write_text("{}")

    ts_script = tmp_path / "generate-agent-from-settings.ts"
    ts_script.write_text("// stub")

    monkeypatch.setattr(migrate_agent, "find_typescript_script", lambda: ts_script)
    monkeypatch.setattr(migrate_agent, "check_node_dependencies", lambda: (True, "tsx"))

    completed = CompletedProcess(["npx", "tsx"], 2, stdout="", stderr="TypeScript error\n")

    def fake_run(command, capture_output, text, shell):  # type: ignore[no-untyped-def]
        return completed

    monkeypatch.setattr("agency_swarm.cli.migrate_agent.subprocess.run", fake_run)

    exit_code = migrate_agent.migrate_agent_command(str(settings_path), str(tmp_path))
    assert exit_code == 2


def test_generate_agent_script_escapes_strings(tmp_path: Path) -> None:
    """TypeScript generator should escape quotes in user-provided strings."""
    project_root = Path(__file__).parents[2]
    script_path = project_root / "src" / "agency_swarm" / "cli" / "utils" / "generate-agent-from-settings.ts"
    ts_node_binary = project_root / "node_modules" / ".bin" / ("ts-node.cmd" if sys.platform == "win32" else "ts-node")

    if not ts_node_binary.exists():
        pytest.skip("ts-node binary not available for generator test")

    settings = {
        "name": 'Quote "Tester"',
        "description": 'Says "hello" often',
        "instructions": "Be helpful",
        "model": "gpt-5.4-mini",
    }

    settings_path = tmp_path / "settings.json"
    settings_path.write_text(json.dumps(settings))

    env = os.environ.copy()
    env.setdefault("TS_NODE_TRANSPILE_ONLY", "true")

    subprocess.run(
        [str(ts_node_binary), str(script_path), str(settings_path)],
        cwd=tmp_path,
        check=True,
        capture_output=True,
        text=True,
        env=env,
    )

    agent_name = _sanitize_name(settings["name"])
    agent_file = tmp_path / agent_name / f"{agent_name}.py"
    content = agent_file.read_text()

    assert 'name="Quote "Tester""' not in content
    assert 'description="Says "hello" often"' not in content
    assert 'name="Quote \\"Tester\\""' in content
    assert 'description="Says \\"hello\\" often"' in content
