"""Tests for agentkit migrate command (D2) — ≥12 tests."""
from __future__ import annotations

from pathlib import Path
from unittest.mock import patch

import pytest
from typer.testing import CliRunner

from agentkit_cli.main import app
from agentkit_cli.migrate import (
    FORMAT_AGENTS_MD,
    FORMAT_CLAUDE_MD,
    FORMAT_LLMSTXT,
    FORMAT_FILENAMES,
    MigrateEngine,
    _make_gen_stamp,
)

runner = CliRunner()
engine = MigrateEngine()

AGENTS_SAMPLE = """\
# My Project

## Session Startup
Always read AGENTS.md first.

## Safety
Keep data private.
"""

CLAUDE_SAMPLE = """\
# My Project

## Overview
A Python CLI.

## Commands
make test
"""


# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------

def make_project(tmp_path: Path, with_agents: bool = True, with_claude: bool = False) -> Path:
    if with_agents:
        (tmp_path / "AGENTS.md").write_text(AGENTS_SAMPLE)
    if with_claude:
        (tmp_path / "CLAUDE.md").write_text(CLAUDE_SAMPLE)
    return tmp_path


# ---------------------------------------------------------------------------
# D2: CLI tests
# ---------------------------------------------------------------------------

class TestMigrateCLI:
    def test_migrate_auto_all_creates_files(self, tmp_path):
        make_project(tmp_path)
        result = runner.invoke(app, ["migrate", str(tmp_path), "--force"])
        assert result.exit_code == 0
        assert (tmp_path / "CLAUDE.md").exists()
        assert (tmp_path / "llms.txt").exists()

    def test_migrate_dry_run_no_files_written(self, tmp_path):
        make_project(tmp_path)
        result = runner.invoke(app, ["migrate", str(tmp_path), "--dry-run"])
        assert result.exit_code == 0
        assert not (tmp_path / "CLAUDE.md").exists()
        assert not (tmp_path / "llms.txt").exists()

    def test_migrate_dry_run_output_mentions_dryrun(self, tmp_path):
        make_project(tmp_path)
        result = runner.invoke(app, ["migrate", str(tmp_path), "--dry-run"])
        assert "dry-run" in result.output.lower()

    def test_migrate_from_agents_to_claude(self, tmp_path):
        make_project(tmp_path)
        result = runner.invoke(app, [
            "migrate", str(tmp_path), "--from", "agents-md", "--to", "claude-md", "--force"
        ])
        assert result.exit_code == 0
        assert (tmp_path / "CLAUDE.md").exists()
        content = (tmp_path / "CLAUDE.md").read_text()
        assert "generated by agentkit migrate" in content

    def test_migrate_output_flag(self, tmp_path):
        make_project(tmp_path)
        out_file = tmp_path / "custom_output.md"
        result = runner.invoke(app, [
            "migrate", str(tmp_path),
            "--from", "agents-md", "--to", "claude-md",
            "--output", str(out_file), "--force"
        ])
        assert result.exit_code == 0
        assert out_file.exists()

    def test_migrate_all_flag_equivalent_to_to_all(self, tmp_path):
        make_project(tmp_path)
        result = runner.invoke(app, ["migrate", str(tmp_path), "--all", "--force"])
        assert result.exit_code == 0
        assert (tmp_path / "CLAUDE.md").exists()

    def test_migrate_missing_source_exits_1(self, tmp_path):
        # No context files at all
        result = runner.invoke(app, ["migrate", str(tmp_path)])
        assert result.exit_code == 1

    def test_migrate_explicit_source_not_found_exits_1(self, tmp_path):
        result = runner.invoke(app, [
            "migrate", str(tmp_path), "--from", "agents-md", "--to", "claude-md"
        ])
        assert result.exit_code == 1

    def test_migrate_diff_flag_shows_output(self, tmp_path):
        make_project(tmp_path)
        result = runner.invoke(app, ["migrate", str(tmp_path), "--diff", "--dry-run"])
        # Should show diff output (or "No changes" if file doesn't exist yet)
        assert result.exit_code == 0

    def test_migrate_force_overwrites_existing(self, tmp_path):
        make_project(tmp_path)
        (tmp_path / "CLAUDE.md").write_text("# old content\n")
        result = runner.invoke(app, [
            "migrate", str(tmp_path), "--from", "agents-md", "--to", "claude-md", "--force"
        ])
        assert result.exit_code == 0
        new_content = (tmp_path / "CLAUDE.md").read_text()
        assert "generated by agentkit migrate" in new_content

    def test_migrate_from_claude_to_llmstxt(self, tmp_path):
        make_project(tmp_path, with_agents=False, with_claude=True)
        result = runner.invoke(app, [
            "migrate", str(tmp_path), "--from", "claude-md", "--to", "llmstxt", "--force"
        ])
        assert result.exit_code == 0
        assert (tmp_path / "llms.txt").exists()

    def test_migrate_shows_summary_table(self, tmp_path):
        make_project(tmp_path)
        result = runner.invoke(app, ["migrate", str(tmp_path), "--dry-run"])
        # Rich table output should contain format names
        assert result.exit_code == 0
        assert "migrate" in result.output.lower()

    def test_migrate_invalid_path_exits_1(self, tmp_path):
        nonexistent = tmp_path / "does_not_exist"
        result = runner.invoke(app, ["migrate", str(nonexistent)])
        assert result.exit_code == 1


class TestSyncCLI:
    def test_sync_check_exits_1_when_stale(self, tmp_path):
        (tmp_path / "AGENTS.md").write_text(AGENTS_SAMPLE)
        # No derived files → stale/missing
        result = runner.invoke(app, ["sync", str(tmp_path), "--check"])
        assert result.exit_code == 1

    def test_sync_check_exits_0_when_fresh(self, tmp_path):
        (tmp_path / "AGENTS.md").write_text(AGENTS_SAMPLE)
        # Generate derived files
        r_claude = engine.convert(AGENTS_SAMPLE, FORMAT_AGENTS_MD, FORMAT_CLAUDE_MD)
        (tmp_path / "CLAUDE.md").write_text(r_claude.content)
        r_llms = engine.convert(AGENTS_SAMPLE, FORMAT_AGENTS_MD, FORMAT_LLMSTXT)
        (tmp_path / "llms.txt").write_text(r_llms.content)
        result = runner.invoke(app, ["sync", str(tmp_path), "--check"])
        assert result.exit_code == 0

    def test_sync_fix_creates_missing_files(self, tmp_path):
        (tmp_path / "AGENTS.md").write_text(AGENTS_SAMPLE)
        result = runner.invoke(app, ["sync", str(tmp_path), "--fix"])
        assert result.exit_code == 0
        assert (tmp_path / "CLAUDE.md").exists()
        assert (tmp_path / "llms.txt").exists()

    def test_sync_no_source_exits_1(self, tmp_path):
        result = runner.invoke(app, ["sync", str(tmp_path)])
        assert result.exit_code == 1

    def test_sync_shows_status_table(self, tmp_path):
        (tmp_path / "AGENTS.md").write_text(AGENTS_SAMPLE)
        result = runner.invoke(app, ["sync", str(tmp_path)])
        assert result.exit_code == 0
        # Should show the status table
        assert "agents-md" in result.output or "source" in result.output
