import json
from pathlib import Path

import pytest

from agentmux.adapters.cursor import CursorAdapter
from agentmux.core.component import Agent, Command, Instructions, MCPConfig, Rule, Skill


@pytest.fixture
def adapter() -> CursorAdapter:
    return CursorAdapter()


# ── Agent ─────────────────────────────────────────────────────────────────────


def test_convert_agent_output_has_name_and_description(adapter: CursorAdapter) -> None:
    agent = Agent(name="Helper", description="Helps with code", body="Be helpful.")
    results = adapter.convert_agent(agent)
    assert len(results) == 1
    result = results[0]
    assert result.path == Path("agents/Helper.md")
    assert result.content is not None
    assert "name: Helper" in result.content
    assert "description: Helps with code" in result.content
    assert "Be helpful." in result.content


def test_convert_agent_no_mode_field(adapter: CursorAdapter) -> None:
    agent = Agent(name="Test", description="desc", body="body", mode="cursor-only")
    result = adapter.convert_agent(agent)[0]
    assert "mode:" not in result.content  # type: ignore[operator]


def test_convert_agent_scope_is_global(adapter: CursorAdapter) -> None:
    agent = Agent(name="A", description="d", body="b")
    assert adapter.convert_agent(agent)[0].scope == "global"


def test_convert_agent_has_no_source(adapter: CursorAdapter, tmp_path: Path) -> None:
    """source must be None so symlink strategy does not bypass content transformation."""
    f = tmp_path / "agent.md"
    f.write_text("---\nname: Helper\ndescription: d\n---\nbody")
    agent = Agent.from_file(f)
    result = adapter.convert_agent(agent)[0]
    assert result.source is None

    f = tmp_path / "agent.md"
    f.write_text("---\nname: X\ndescription: d\nmodel: gpt-4o\n---\nbody")
    agent = Agent.from_file(f)
    result = adapter.convert_agent(agent)[0]
    assert result.content is not None
    assert "model: gpt-4o" in result.content


# ── Command ───────────────────────────────────────────────────────────────────


def test_convert_command_strips_frontmatter(adapter: CursorAdapter, tmp_path: Path) -> None:
    cmd_file = tmp_path / "fix-tests.md"
    cmd_file.write_text("---\nname: Fix Tests\ndescription: Fix\n---\nFix all tests.")
    cmd = Command.from_file(cmd_file)
    results = adapter.convert_command(cmd)
    assert len(results) == 1
    result = results[0]
    assert result.path == Path("commands/fix-tests.md")
    assert result.content is not None
    assert "---" not in result.content
    assert "Fix all tests." in result.content


def test_convert_command_plain_markdown(adapter: CursorAdapter, tmp_path: Path) -> None:
    cmd_file = tmp_path / "my-cmd.md"
    cmd_file.write_text("Do the thing.")
    cmd = Command.from_file(cmd_file)
    result = adapter.convert_command(cmd)[0]
    assert "Do the thing." in result.content  # type: ignore[operator]


def test_convert_command_preserves_filename(adapter: CursorAdapter, tmp_path: Path) -> None:
    cmd_file = tmp_path / "review-pr.md"
    cmd_file.write_text("Review it.")
    cmd = Command.from_file(cmd_file)
    result = adapter.convert_command(cmd)[0]
    assert result.path.name == "review-pr.md"


def test_convert_command_has_no_source(adapter: CursorAdapter, tmp_path: Path) -> None:
    """source must be None so symlink strategy writes content rather than linking raw file."""
    cmd_file = tmp_path / "fix.md"
    cmd_file.write_text("---\nname: Fix\n---\nFix it.")
    cmd = Command.from_file(cmd_file)
    result = adapter.convert_command(cmd)[0]
    assert result.source is None


# ── Skill ─────────────────────────────────────────────────────────────────────


def test_convert_skill_symlinks_directory(adapter: CursorAdapter, tmp_path: Path) -> None:
    skill_dir = tmp_path / "my-skill"
    skill_dir.mkdir()
    (skill_dir / "SKILL.md").write_text("---\nname: My Skill\ndescription: d\n---\ncontent")
    skill = Skill.from_dir(skill_dir)
    results = adapter.convert_skill(skill)
    assert len(results) == 1
    result = results[0]
    assert result.path == Path("skills/my-skill")
    assert result.content is None
    assert result.source == skill_dir
    assert result.is_directory is True


# ── Rule ──────────────────────────────────────────────────────────────────────


def test_convert_rule_produces_mdc(adapter: CursorAdapter) -> None:
    rule = Rule(
        name="no-comments",
        description="Avoid long comments",
        always_apply=True,
        body="Short comments only.",
    )
    results = adapter.convert_rule(rule)
    assert len(results) == 1
    result = results[0]
    assert result.path == Path("rules/no-comments.mdc")
    assert result.content is not None
    assert "alwaysApply: true" in result.content
    assert "description: Avoid long comments" in result.content
    assert "Short comments only." in result.content


def test_convert_rule_has_no_source(adapter: CursorAdapter, tmp_path: Path) -> None:
    """source must be None — target is .mdc, transformation is always applied."""
    rule_file = tmp_path / "my-rule.md"
    rule_file.write_text("---\nname: my-rule\nalwaysApply: false\n---\nContent.")
    rule = Rule.from_file(rule_file)
    result = adapter.convert_rule(rule)[0]
    assert result.source is None


def test_convert_instructions_has_no_source(adapter: CursorAdapter, tmp_path: Path) -> None:
    """source must be None — always transformed with alwaysApply frontmatter."""
    instr_file = tmp_path / "instructions.md"
    instr_file.write_text("Be concise.")
    from agentmux.core.component import Instructions

    instr = Instructions.from_file(instr_file)
    result = adapter.convert_instructions(instr)[0]
    assert result.source is None

    rule = Rule(name="r", description="", always_apply=False, body="b")
    assert adapter.convert_rule(rule)[0].scope == "project"


def test_convert_rule_with_globs(adapter: CursorAdapter) -> None:
    rule = Rule(name="ts", globs="**/*.ts", always_apply=False, body="TS rules.")
    result = adapter.convert_rule(rule)[0]
    assert "globs:" in result.content  # type: ignore[operator]


# ── Instructions ──────────────────────────────────────────────────────────────


def test_convert_instructions_wraps_with_always_apply(adapter: CursorAdapter) -> None:
    instr = Instructions(body="Be concise.")
    results = adapter.convert_instructions(instr)
    assert len(results) == 1
    result = results[0]
    assert result.path == Path("rules/instructions.mdc")
    assert result.content is not None
    assert "alwaysApply: true" in result.content
    assert "Be concise." in result.content


def test_convert_instructions_scope_is_project(adapter: CursorAdapter) -> None:
    instr = Instructions(body="b")
    assert adapter.convert_instructions(instr)[0].scope == "project"


# ── MCP ───────────────────────────────────────────────────────────────────────


def test_convert_mcp_symlinks_source_file(adapter: CursorAdapter, tmp_path: Path) -> None:
    mcp_file = tmp_path / "mcp.json"
    mcp_file.write_text(json.dumps({"mcpServers": {"srv": {}}}))
    mcp = MCPConfig.from_file(mcp_file)
    results = adapter.convert_mcp(mcp)
    assert len(results) == 1
    assert results[0].path == Path("mcp.json")
    assert results[0].content is None
    assert results[0].source == mcp_file


def test_convert_mcp_generates_when_no_source(adapter: CursorAdapter) -> None:
    mcp = MCPConfig(servers={"srv": {"command": "run"}})
    results = adapter.convert_mcp(mcp)
    assert results[0].content is not None
    parsed = json.loads(results[0].content)
    assert "mcpServers" in parsed


# ── Validate ──────────────────────────────────────────────────────────────────


def test_validate_returns_empty_for_agent(adapter: CursorAdapter) -> None:
    agent = Agent(name="A", description="d", body="b")
    assert adapter.validate(agent) == []


def test_validate_returns_empty_for_rule(adapter: CursorAdapter) -> None:
    rule = Rule(name="r", always_apply=False, body="b")
    assert adapter.validate(rule) == []


def test_validate_returns_empty_for_mcp(adapter: CursorAdapter) -> None:
    mcp = MCPConfig(servers={})
    assert adapter.validate(mcp) == []
