import json
import sys
from pathlib import Path

import pytest

from agentmux.adapters.gemini import GeminiAdapter
from agentmux.core.component import (
    Agent,
    Command,
    ComponentType,
    Instructions,
    MCPConfig,
    Rule,
    Skill,
)


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


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


def test_convert_agent_injects_kind_local(adapter: GeminiAdapter) -> None:
    agent = Agent(name="Helper", description="Helps", body="Be helpful.")
    result = adapter.convert_agent(agent)[0]
    assert result.path == Path("agents/Helper.md")
    assert result.content is not None
    assert "kind: local" in result.content


def test_convert_agent_no_duplicate_kind(adapter: GeminiAdapter, tmp_path: Path) -> None:
    f = tmp_path / "agent.md"
    f.write_text("---\nname: X\ndescription: d\nkind: local\n---\nbody")
    agent = Agent.from_file(f)
    result = adapter.convert_agent(agent)[0]
    # Only one "kind: local" in the frontmatter section
    assert result.content is not None
    assert result.content.count("kind: local") == 1


def test_convert_agent_has_name_and_description(adapter: GeminiAdapter) -> None:
    agent = Agent(name="A", description="B", body="C")
    result = adapter.convert_agent(agent)[0]
    assert "name: A" in result.content  # type: ignore[operator]
    assert "description: B" in result.content  # type: ignore[operator]


def test_convert_agent_has_no_source(adapter: GeminiAdapter, tmp_path: Path) -> None:
    """source must be None so symlink strategy does not bypass kind:local injection."""
    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\nmax_turns: 10\n---\nbody")
    agent = Agent.from_file(f)
    result = adapter.convert_agent(agent)[0]
    assert result.content is not None
    assert "max_turns: 10" in result.content


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


def test_convert_command_produces_toml(adapter: GeminiAdapter, tmp_path: Path) -> None:
    f = tmp_path / "fix-tests.md"
    f.write_text("---\nname: Fix Tests\ndescription: Fix them\n---\nFix failing tests.")
    cmd = Command.from_file(f)
    result = adapter.convert_command(cmd)[0]
    assert result.path == Path("commands/fix-tests.toml")
    assert result.content is not None
    assert "prompt = '''" in result.content
    assert "Fix failing tests." in result.content
    assert 'description = "Fix them"' in result.content


def test_convert_command_toml_no_description_when_empty(
    adapter: GeminiAdapter, tmp_path: Path
) -> None:
    f = tmp_path / "simple.md"
    f.write_text("Do something.")
    cmd = Command.from_file(f)
    result = adapter.convert_command(cmd)[0]
    assert "description" not in result.content  # type: ignore[operator]


def test_convert_command_toml_filename(adapter: GeminiAdapter, tmp_path: Path) -> None:
    f = tmp_path / "review-pr.md"
    f.write_text("Review carefully.")
    cmd = Command.from_file(f)
    result = adapter.convert_command(cmd)[0]
    assert result.path.name == "review-pr.toml"


def test_convert_command_uses_literal_string_no_escape_processing(adapter: GeminiAdapter) -> None:
    """Literal TOML strings (''') must preserve backslashes verbatim."""
    cmd = Command(name="regex", body=r"Match \n and \t literally.")
    result = adapter.convert_command(cmd)[0]
    assert result.content is not None
    # The raw backslash sequences must appear verbatim in the output
    assert r"\n" in result.content
    assert r"\t" in result.content
    # Literal string delimiters must be used
    assert "prompt = '''" in result.content


def test_convert_command_escapes_triple_single_quotes(adapter: GeminiAdapter) -> None:
    """Triple single-quotes inside the body must not break the TOML literal string."""
    cmd = Command(name="tricky", body="Use '''triple''' quotes.")
    result = adapter.convert_command(cmd)[0]
    assert result.content is not None
    # The resulting TOML must not contain an unescaped closing ''' that ends the literal string
    # early; verify the body text is present
    assert "triple" in result.content


def test_convert_command_escapes_triple_quotes_at_end_of_body(adapter: GeminiAdapter) -> None:
    """Triple single-quotes at the end of the body must produce valid TOML."""
    if sys.version_info >= (3, 11):
        import tomllib
    else:
        import tomli as tomllib  # type: ignore[import-not-found]

    cmd = Command(name="trailing-quotes", body="End with'''")
    result = adapter.convert_command(cmd)[0]
    assert result.content is not None
    # Parse the generated TOML to confirm it is valid and the body round-trips correctly
    parsed = tomllib.loads(result.content)
    assert parsed["prompt"].strip() == "End with'''"


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


def test_convert_skill_symlinks_directory(adapter: GeminiAdapter, 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)
    result = adapter.convert_skill(skill)[0]
    assert result.content is None
    assert result.source == skill_dir
    assert result.is_directory is True


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


def test_convert_rule_returns_empty_list(adapter: GeminiAdapter) -> None:
    rule = Rule(name="r", always_apply=False, body="b")
    result = adapter.convert_rule(rule)
    assert result == []


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


def test_convert_instructions_plain_markdown(adapter: GeminiAdapter) -> None:
    instr = Instructions(body="Be concise.")
    result = adapter.convert_instructions(instr)[0]
    assert result.path == Path("GEMINI.md")
    assert result.content is not None
    assert "---" not in result.content
    assert "Be concise." in result.content


def test_convert_instructions_strips_existing_frontmatter(
    adapter: GeminiAdapter, tmp_path: Path
) -> None:
    f = tmp_path / "instructions.md"
    f.write_text("---\nalwaysApply: true\n---\nBe helpful.")
    instr = Instructions.from_file(f)
    result = adapter.convert_instructions(instr)[0]
    assert "alwaysApply" not in result.content  # type: ignore[operator]
    assert "Be helpful." in result.content  # type: ignore[operator]


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


def test_convert_mcp_produces_settings_json_merge(adapter: GeminiAdapter, tmp_path: Path) -> None:
    mcp_file = tmp_path / "mcp.json"
    mcp_file.write_text(json.dumps({"mcpServers": {"srv": {"command": "run"}}}))
    mcp = MCPConfig.from_file(mcp_file)
    results = adapter.convert_mcp(mcp)
    assert len(results) == 1
    assert results[0].path == Path("settings.json")
    assert results[0].merge is True
    payload = json.loads(results[0].content)  # type: ignore[arg-type]
    assert "mcpServers" in payload
    assert "srv" in payload["mcpServers"]


# ── post_sync ─────────────────────────────────────────────────────────────────


def test_post_sync_enables_agents_when_agent_synced(adapter: GeminiAdapter, tmp_path: Path) -> None:
    extras = adapter.post_sync(tmp_path, {ComponentType.AGENT})
    assert len(extras) == 1
    assert extras[0].path == Path("settings.json")
    assert extras[0].merge is True
    payload = json.loads(extras[0].content)  # type: ignore[arg-type]
    assert payload["experimental"]["enableAgents"] is True


def test_post_sync_empty_when_no_agents(adapter: GeminiAdapter, tmp_path: Path) -> None:
    extras = adapter.post_sync(tmp_path, {ComponentType.COMMAND})
    assert extras == []


def test_post_sync_merges_with_existing_settings(adapter: GeminiAdapter, tmp_path: Path) -> None:
    """Verify that the merge flag is set so sync engine will deep-merge."""
    extras = adapter.post_sync(tmp_path, {ComponentType.AGENT})
    assert extras[0].merge is True


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


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


def test_validate_returns_error_for_rule(adapter: GeminiAdapter) -> None:
    rule = Rule(name="r", always_apply=False, body="b")
    errors = adapter.validate(rule)
    assert len(errors) == 1
    assert "rule" in errors[0].lower()


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