"""Tests for workflow-to-skill auto-generation (synapse/workflow_skill_sync.py)."""

from __future__ import annotations

from pathlib import Path

import pytest


@pytest.fixture()
def project_dir(tmp_path: Path) -> Path:
    """Return a temporary project directory."""
    return tmp_path / "project"


def _make_workflow(
    name: str = "post-impl",
    description: str = "Post-implementation workflow",
    trigger: str = "",
    auto_spawn: bool = False,
):
    from synapse.workflow import Workflow, WorkflowStep

    return Workflow(
        name=name,
        steps=[WorkflowStep(target="claude", message="Review code")],
        description=description,
        trigger=trigger,
        auto_spawn=auto_spawn,
        scope="project",
    )


# ── generate_skill_md ────────────────────────────────────────


class TestGenerateSkillMd:
    def test_basic_frontmatter(self) -> None:
        """Should contain name and description in frontmatter."""
        from synapse.workflow_skill_sync import generate_skill_md

        wf = _make_workflow()
        md = generate_skill_md(wf)
        assert "name: post-impl" in md
        assert "Workflow: Post-implementation workflow." in md
        assert "Triggered by /post-impl command." in md

    def test_contains_autogen_marker(self) -> None:
        """Should contain the autogen marker."""
        from synapse.workflow_skill_sync import _AUTOGEN_MARKER, generate_skill_md

        wf = _make_workflow()
        md = generate_skill_md(wf)
        assert _AUTOGEN_MARKER in md

    def test_trigger_keywords_in_description(self) -> None:
        """Trigger keywords should appear in description."""
        from synapse.workflow_skill_sync import generate_skill_md

        wf = _make_workflow(trigger="実装が終わった|post-impl")
        md = generate_skill_md(wf)
        assert "Use this skill when: 実装が終わった|post-impl." in md

    def test_no_trigger_keywords(self) -> None:
        """Without trigger, no 'Use this skill when' text."""
        from synapse.workflow_skill_sync import generate_skill_md

        wf = _make_workflow(trigger="")
        md = generate_skill_md(wf)
        assert "Use this skill when:" not in md

    def test_auto_spawn_flag_in_instructions(self) -> None:
        """auto_spawn=True should add --auto-spawn to run command."""
        from synapse.workflow_skill_sync import generate_skill_md

        wf = _make_workflow(auto_spawn=True)
        md = generate_skill_md(wf)
        assert "--auto-spawn" in md

    def test_no_auto_spawn_flag(self) -> None:
        """auto_spawn=False should not add --auto-spawn."""
        from synapse.workflow_skill_sync import generate_skill_md

        wf = _make_workflow(auto_spawn=False)
        md = generate_skill_md(wf)
        assert "--auto-spawn" not in md

    def test_fallback_description(self) -> None:
        """When description is empty, use workflow name."""
        from synapse.workflow_skill_sync import generate_skill_md

        wf = _make_workflow(name="my-wf", description="")
        md = generate_skill_md(wf)
        assert "Workflow: my-wf." in md


# ── is_autogenerated_skill ───────────────────────────────────


class TestIsAutogeneratedSkill:
    def test_autogenerated(self, project_dir: Path) -> None:
        """Directory with autogen marker should return True."""
        from synapse.workflow_skill_sync import _AUTOGEN_MARKER, is_autogenerated_skill

        skill_dir = project_dir / ".claude" / "skills" / "test"
        skill_dir.mkdir(parents=True)
        (skill_dir / "SKILL.md").write_text(
            f"---\nname: test\n---\n{_AUTOGEN_MARKER}\n# test\n"
        )
        assert is_autogenerated_skill(skill_dir) is True

    def test_hand_written(self, project_dir: Path) -> None:
        """Directory without autogen marker should return False."""
        from synapse.workflow_skill_sync import is_autogenerated_skill

        skill_dir = project_dir / ".claude" / "skills" / "test"
        skill_dir.mkdir(parents=True)
        (skill_dir / "SKILL.md").write_text("---\nname: test\n---\n# test\n")
        assert is_autogenerated_skill(skill_dir) is False

    def test_missing_directory(self, project_dir: Path) -> None:
        """Non-existent directory should return False."""
        from synapse.workflow_skill_sync import is_autogenerated_skill

        assert is_autogenerated_skill(project_dir / "nonexistent") is False

    def test_missing_skill_md(self, project_dir: Path) -> None:
        """Directory without SKILL.md should return False."""
        from synapse.workflow_skill_sync import is_autogenerated_skill

        skill_dir = project_dir / ".claude" / "skills" / "test"
        skill_dir.mkdir(parents=True)
        assert is_autogenerated_skill(skill_dir) is False


# ── sync_workflow_skill ──────────────────────────────────────


class TestSyncWorkflowSkill:
    def test_creates_skill_in_both_dirs(self, project_dir: Path) -> None:
        """Should create SKILL.md in both .claude/skills/ and .agents/skills/."""
        from synapse.workflow_skill_sync import sync_workflow_skill

        wf = _make_workflow(name="my-wf")
        written = sync_workflow_skill(wf, project_dir)

        assert len(written) == 2
        assert (project_dir / ".claude" / "skills" / "my-wf" / "SKILL.md").is_file()
        assert (project_dir / ".agents" / "skills" / "my-wf" / "SKILL.md").is_file()

    def test_updates_existing_autogen(self, project_dir: Path) -> None:
        """Should overwrite existing autogenerated skill."""
        from synapse.workflow_skill_sync import (
            _AUTOGEN_MARKER,
            sync_workflow_skill,
        )

        skill_dir = project_dir / ".claude" / "skills" / "my-wf"
        skill_dir.mkdir(parents=True)
        (skill_dir / "SKILL.md").write_text(f"old\n{_AUTOGEN_MARKER}\n")

        wf = _make_workflow(name="my-wf", description="Updated")
        sync_workflow_skill(wf, project_dir)

        content = (skill_dir / "SKILL.md").read_text()
        assert "Updated" in content

    def test_protects_hand_written_skill(self, project_dir: Path) -> None:
        """Should NOT overwrite hand-written skill (no marker)."""
        from synapse.workflow_skill_sync import sync_workflow_skill

        skill_dir = project_dir / ".claude" / "skills" / "my-wf"
        skill_dir.mkdir(parents=True)
        original = "---\nname: my-wf\n---\n# Custom skill\n"
        (skill_dir / "SKILL.md").write_text(original)

        wf = _make_workflow(name="my-wf")
        written = sync_workflow_skill(wf, project_dir)

        # .claude/skills should be skipped, .agents/skills should be created
        claude_paths = [p for p in written if ".claude" in str(p)]
        assert len(claude_paths) == 0
        assert (skill_dir / "SKILL.md").read_text() == original


# ── remove_workflow_skill ────────────────────────────────────


class TestRemoveWorkflowSkill:
    def test_removes_autogenerated(self, project_dir: Path) -> None:
        """Should remove autogenerated skill directories."""
        from synapse.workflow_skill_sync import (
            _AUTOGEN_MARKER,
            remove_workflow_skill,
        )

        for base in (".claude/skills", ".agents/skills"):
            skill_dir = project_dir / base / "my-wf"
            skill_dir.mkdir(parents=True)
            (skill_dir / "SKILL.md").write_text(f"x\n{_AUTOGEN_MARKER}\n")

        removed = remove_workflow_skill("my-wf", project_dir)
        assert len(removed) == 2
        assert not (project_dir / ".claude" / "skills" / "my-wf").exists()
        assert not (project_dir / ".agents" / "skills" / "my-wf").exists()

    def test_protects_hand_written(self, project_dir: Path) -> None:
        """Should NOT remove hand-written skill."""
        from synapse.workflow_skill_sync import remove_workflow_skill

        skill_dir = project_dir / ".claude" / "skills" / "my-wf"
        skill_dir.mkdir(parents=True)
        (skill_dir / "SKILL.md").write_text("---\nname: my-wf\n---\n# Custom\n")

        removed = remove_workflow_skill("my-wf", project_dir)
        assert len(removed) == 0
        assert skill_dir.exists()

    def test_nonexistent_is_noop(self, project_dir: Path) -> None:
        """Removing non-existent skill should be a no-op."""
        from synapse.workflow_skill_sync import remove_workflow_skill

        removed = remove_workflow_skill("ghost", project_dir)
        assert removed == []


# ── sync_all_workflows ───────────────────────────────────────


class TestSyncAllWorkflows:
    def test_syncs_all_and_removes_orphans(self, tmp_path: Path) -> None:
        """Should sync all workflows and remove orphans."""
        from synapse.workflow import Workflow, WorkflowStep, WorkflowStore
        from synapse.workflow_skill_sync import (
            _AUTOGEN_MARKER,
            sync_all_workflows,
        )

        project_dir = tmp_path / "project"
        wf_dir = project_dir / ".synapse" / "workflows"
        wf_dir.mkdir(parents=True)

        store = WorkflowStore(
            project_dir=wf_dir,
            user_dir=tmp_path / "user" / ".synapse" / "workflows",
        )

        # Save two workflows
        store.save(
            Workflow(
                name="wf-a",
                steps=[WorkflowStep(target="claude", message="a")],
                scope="project",
            )
        )
        store.save(
            Workflow(
                name="wf-b",
                steps=[WorkflowStep(target="gemini", message="b")],
                scope="project",
            )
        )

        # Create an orphaned autogenerated skill
        orphan_dir = project_dir / ".claude" / "skills" / "old-wf"
        orphan_dir.mkdir(parents=True)
        (orphan_dir / "SKILL.md").write_text(f"old\n{_AUTOGEN_MARKER}\n")

        written, removed = sync_all_workflows(
            project_dir,
            user_dir=tmp_path / "user" / ".synapse" / "workflows",
        )

        # Each workflow creates 2 files (.claude + .agents)
        assert len(written) == 4
        assert len(removed) == 1
        assert orphan_dir in removed
        assert not orphan_dir.exists()

    def test_preserves_hand_written_orphan(self, tmp_path: Path) -> None:
        """Should NOT remove hand-written skills even if no matching workflow."""
        from synapse.workflow_skill_sync import sync_all_workflows

        project_dir = tmp_path / "project"
        wf_dir = project_dir / ".synapse" / "workflows"
        wf_dir.mkdir(parents=True)

        # Create a hand-written skill (no matching workflow)
        skill_dir = project_dir / ".claude" / "skills" / "custom"
        skill_dir.mkdir(parents=True)
        (skill_dir / "SKILL.md").write_text("---\nname: custom\n---\n# Custom\n")

        _written, removed = sync_all_workflows(
            project_dir,
            user_dir=tmp_path / "user" / ".synapse" / "workflows",
        )

        assert removed == []
        assert skill_dir.exists()


# ── CLI integration ──────────────────────────────────────────


class TestCLIIntegration:
    def test_create_generates_skill(self, tmp_path: Path) -> None:
        """cmd_workflow_create should auto-generate skill."""
        import argparse
        from unittest.mock import patch

        from synapse.commands.workflow import cmd_workflow_create
        from synapse.workflow import WorkflowStore
        from synapse.workflow_skill_sync import is_autogenerated_skill

        project_dir = tmp_path / "project"
        wf_dir = project_dir / ".synapse" / "workflows"
        store = WorkflowStore(
            project_dir=wf_dir,
            user_dir=tmp_path / "user" / ".synapse" / "workflows",
        )

        args = argparse.Namespace(
            workflow_name="test-wf",
            user=False,
            project=False,
            force=False,
        )

        with (
            patch("synapse.commands.workflow._get_workflow_store", return_value=store),
            patch("synapse.workflow_skill_sync.Path.cwd", return_value=project_dir),
            patch("synapse.commands.workflow.Path.cwd", return_value=project_dir),
        ):
            cmd_workflow_create(args)

        skill_dir = project_dir / ".claude" / "skills" / "test-wf"
        assert skill_dir.exists()
        assert is_autogenerated_skill(skill_dir)

    def test_delete_removes_skill(self, tmp_path: Path) -> None:
        """cmd_workflow_delete should remove auto-generated skill."""
        import argparse
        from unittest.mock import patch

        from synapse.commands.workflow import cmd_workflow_delete
        from synapse.workflow import Workflow, WorkflowStep, WorkflowStore
        from synapse.workflow_skill_sync import _AUTOGEN_MARKER

        project_dir = tmp_path / "project"
        wf_dir = project_dir / ".synapse" / "workflows"
        store = WorkflowStore(
            project_dir=wf_dir,
            user_dir=tmp_path / "user" / ".synapse" / "workflows",
        )

        # Create workflow + skill
        store.save(
            Workflow(
                name="victim",
                steps=[WorkflowStep(target="claude", message="x")],
                scope="project",
            )
        )
        skill_dir = project_dir / ".claude" / "skills" / "victim"
        skill_dir.mkdir(parents=True)
        (skill_dir / "SKILL.md").write_text(f"x\n{_AUTOGEN_MARKER}\n")

        args = argparse.Namespace(
            workflow_name="victim",
            user=False,
            project=False,
            force=True,
        )

        with (
            patch("synapse.commands.workflow._get_workflow_store", return_value=store),
            patch("synapse.commands.workflow.Path.cwd", return_value=project_dir),
        ):
            cmd_workflow_delete(args)

        assert not skill_dir.exists()

    def test_sync_command(self, tmp_path: Path, capsys: pytest.CaptureFixture) -> None:
        """cmd_workflow_sync should sync all workflows."""
        import argparse
        from unittest.mock import patch

        from synapse.commands.workflow import cmd_workflow_sync
        from synapse.workflow import Workflow, WorkflowStep, WorkflowStore

        project_dir = tmp_path / "project"
        wf_dir = project_dir / ".synapse" / "workflows"
        store = WorkflowStore(
            project_dir=wf_dir,
            user_dir=tmp_path / "user" / ".synapse" / "workflows",
        )

        store.save(
            Workflow(
                name="my-wf",
                steps=[WorkflowStep(target="claude", message="hello")],
                scope="project",
            )
        )

        args = argparse.Namespace()

        with patch("synapse.commands.workflow.Path.cwd", return_value=project_dir):
            cmd_workflow_sync(args)

        captured = capsys.readouterr()
        assert "Synced" in captured.out
        assert (project_dir / ".claude" / "skills" / "my-wf" / "SKILL.md").is_file()
