"""Tests for the skills bundler and MCP config generator."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from exokit.core.skills import (
    ESSENTIAL_SKILLS,
    bundle_skills,
    generate_mcp_config,
    update_skills,
)

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture()
def skills_source(tmp_path: Path) -> Path:
    """Create a mock skills source directory with all essential skills."""
    source = tmp_path / "skills_source"
    for skill_name in ESSENTIAL_SKILLS:
        skill_dir = source / skill_name
        skill_dir.mkdir(parents=True)
        (skill_dir / "README.md").write_text(f"# {skill_name}\n")
        (skill_dir / "skill.md").write_text(f"Skill: {skill_name}\n")
    return source


@pytest.fixture()
def partial_skills_source(tmp_path: Path) -> Path:
    """Create a mock skills source with only a subset of skills."""
    source = tmp_path / "partial_source"
    for skill_name in ESSENTIAL_SKILLS[:3]:
        skill_dir = source / skill_name
        skill_dir.mkdir(parents=True)
        (skill_dir / "skill.md").write_text(f"Skill: {skill_name}\n")
    return source


@pytest.fixture()
def target_dir(tmp_path: Path) -> Path:
    """Create a target project directory."""
    target = tmp_path / "project"
    target.mkdir()
    return target


@pytest.fixture()
def ai_dev_kit_path(tmp_path: Path) -> Path:
    """Create a mock ai-dev-kit directory path for config generation."""
    return tmp_path / "ai-dev-kit"


# ---------------------------------------------------------------------------
# bundle_skills
# ---------------------------------------------------------------------------


class TestBundleSkills:
    """Tests for bundle_skills."""

    def test_copies_all_essential_skills(self, target_dir: Path, skills_source: Path) -> None:
        """All essential skills should be copied to .claude/skills/."""
        result = bundle_skills(target_dir, skills_source=skills_source)

        assert len(result) == len(ESSENTIAL_SKILLS)
        for skill_name in ESSENTIAL_SKILLS:
            skill_dir = target_dir / ".claude" / "skills" / skill_name
            assert skill_dir.is_dir(), f"Missing skill: {skill_name}"
            assert (skill_dir / "skill.md").exists()

    def test_returns_skill_names(self, target_dir: Path, skills_source: Path) -> None:
        """Returned list should contain all copied skill names."""
        result = bundle_skills(target_dir, skills_source=skills_source)
        assert set(result) == set(ESSENTIAL_SKILLS)

    def test_creates_skills_directory(self, target_dir: Path, skills_source: Path) -> None:
        """The .claude/skills/ directory should be created if it doesn't exist."""
        bundle_skills(target_dir, skills_source=skills_source)
        assert (target_dir / ".claude" / "skills").is_dir()

    def test_skips_existing_skills(self, target_dir: Path, skills_source: Path) -> None:
        """Already-bundled skills should be skipped (not overwritten)."""
        # First bundle
        bundle_skills(target_dir, skills_source=skills_source)

        # Modify a skill in the target to verify it's not overwritten
        marker = target_dir / ".claude" / "skills" / ESSENTIAL_SKILLS[0] / "marker.txt"
        marker.write_text("original")

        # Second bundle — should skip existing
        result = bundle_skills(target_dir, skills_source=skills_source)
        assert len(result) == 0  # All already exist
        assert marker.read_text() == "original"

    def test_missing_source_returns_empty(self, target_dir: Path, tmp_path: Path) -> None:
        """When source directory doesn't exist, return empty list."""
        nonexistent = tmp_path / "nonexistent"
        result = bundle_skills(target_dir, skills_source=nonexistent)
        assert result == []

    def test_partial_source(self, target_dir: Path, partial_skills_source: Path) -> None:
        """Only skills present in source should be copied."""
        result = bundle_skills(target_dir, skills_source=partial_skills_source)
        assert len(result) == 3
        for skill_name in ESSENTIAL_SKILLS[:3]:
            assert (target_dir / ".claude" / "skills" / skill_name).is_dir()

    def test_no_duplicate_skills(self, target_dir: Path, skills_source: Path) -> None:
        """Returned list should have no duplicates."""
        result = bundle_skills(target_dir, skills_source=skills_source)
        assert len(result) == len(set(result))

    def test_preserves_skill_content(self, target_dir: Path, skills_source: Path) -> None:
        """Skill files should be copied with their content intact."""
        bundle_skills(target_dir, skills_source=skills_source)

        first_skill = ESSENTIAL_SKILLS[0]
        original = (skills_source / first_skill / "skill.md").read_text()
        copied = (target_dir / ".claude" / "skills" / first_skill / "skill.md").read_text()
        assert original == copied


# ---------------------------------------------------------------------------
# update_skills
# ---------------------------------------------------------------------------


class TestUpdateSkills:
    """Tests for update_skills."""

    def test_overwrites_existing_skills(self, target_dir: Path, skills_source: Path) -> None:
        """update_skills should overwrite existing skill directories."""
        # First bundle
        bundle_skills(target_dir, skills_source=skills_source)

        # Modify a file in the target
        modified_skill = ESSENTIAL_SKILLS[0]
        modified_file = target_dir / ".claude" / "skills" / modified_skill / "skill.md"
        modified_file.write_text("MODIFIED")

        # Update — should overwrite
        result = update_skills(target_dir, skills_source=skills_source)
        assert len(result) == len(ESSENTIAL_SKILLS)

        # Verify the file was restored to original content
        assert modified_file.read_text() == f"Skill: {modified_skill}\n"

    def test_returns_updated_names(self, target_dir: Path, skills_source: Path) -> None:
        """Returned list should contain all updated skill names."""
        result = update_skills(target_dir, skills_source=skills_source)
        assert set(result) == set(ESSENTIAL_SKILLS)

    def test_missing_source_returns_empty(self, target_dir: Path, tmp_path: Path) -> None:
        """When source directory doesn't exist, return empty list."""
        nonexistent = tmp_path / "nonexistent"
        result = update_skills(target_dir, skills_source=nonexistent)
        assert result == []

    def test_creates_new_skills_on_update(self, target_dir: Path, skills_source: Path) -> None:
        """update_skills should also create skills that don't exist yet."""
        result = update_skills(target_dir, skills_source=skills_source)
        for skill_name in ESSENTIAL_SKILLS:
            assert (target_dir / ".claude" / "skills" / skill_name).is_dir()
        assert len(result) == len(ESSENTIAL_SKILLS)


# ---------------------------------------------------------------------------
# generate_mcp_config
# ---------------------------------------------------------------------------


class TestGenerateMcpConfig:
    """Tests for generate_mcp_config."""

    def test_generates_mcp_json(self, target_dir: Path, ai_dev_kit_path: Path) -> None:
        """Should create a valid .mcp.json file."""
        result_path = generate_mcp_config(target_dir, ai_dev_kit_path=ai_dev_kit_path)

        assert result_path == target_dir / ".mcp.json"
        assert result_path.exists()

        config = json.loads(result_path.read_text())
        assert "mcpServers" in config
        assert "databricks" in config["mcpServers"]

    def test_mcp_config_contains_correct_path(
        self, target_dir: Path, ai_dev_kit_path: Path
    ) -> None:
        """The MCP config should reference the ai-dev-kit override path."""
        generate_mcp_config(target_dir, ai_dev_kit_path=ai_dev_kit_path)

        config = json.loads((target_dir / ".mcp.json").read_text())
        server = config["mcpServers"]["databricks"]
        assert str(ai_dev_kit_path) in server["command"]
        assert "run_server.py" in server["args"][0]

    def test_mcp_config_structure_with_profile(
        self, target_dir: Path, ai_dev_kit_path: Path
    ) -> None:
        """Verify config includes profile and defer_loading."""
        generate_mcp_config(
            target_dir,
            ai_dev_kit_path=ai_dev_kit_path,
            databricks_profile="me-aws",
        )

        config = json.loads((target_dir / ".mcp.json").read_text())
        server = config["mcpServers"]["databricks"]

        assert server["defer_loading"] is True
        assert server["env"]["DATABRICKS_CONFIG_PROFILE"] == "me-aws"

    def test_mcp_config_default_path(self, target_dir: Path) -> None:
        """Without override, config should use ~/.ai-dev-kit/ standard path."""
        generate_mcp_config(target_dir)

        config = json.loads((target_dir / ".mcp.json").read_text())
        server = config["mcpServers"]["databricks"]

        assert "~/.ai-dev-kit/" in server["command"]
        assert "~/.ai-dev-kit/" in server["args"][0]

    def test_returns_path_to_mcp_json(self, target_dir: Path, ai_dev_kit_path: Path) -> None:
        """Return value should be the path to the .mcp.json file."""
        result = generate_mcp_config(target_dir, ai_dev_kit_path=ai_dev_kit_path)
        assert result == target_dir / ".mcp.json"

    def test_overwrites_existing_mcp_json(self, target_dir: Path, ai_dev_kit_path: Path) -> None:
        """Calling generate_mcp_config twice should overwrite the existing file."""
        generate_mcp_config(target_dir, ai_dev_kit_path=ai_dev_kit_path)
        generate_mcp_config(target_dir, ai_dev_kit_path=ai_dev_kit_path)

        # Should not raise, and file should be valid JSON
        config = json.loads((target_dir / ".mcp.json").read_text())
        assert "mcpServers" in config

    def test_auto_detect_fallback(self, target_dir: Path) -> None:
        """When no ai-dev-kit is found, a fallback path should be used."""
        # Don't provide ai_dev_kit_path — detection will fail and use fallback
        result_path = generate_mcp_config(target_dir)

        assert result_path.exists()
        config = json.loads(result_path.read_text())
        assert "mcpServers" in config


# ---------------------------------------------------------------------------
# ESSENTIAL_SKILLS constant
# ---------------------------------------------------------------------------


class TestEssentialSkills:
    """Tests for the ESSENTIAL_SKILLS constant."""

    def test_contains_expected_skills(self) -> None:
        """All required skills should be in the constant."""
        expected = {
            "databricks-spark-declarative-pipelines",
            "databricks-jobs",
            "databricks-aibi-dashboards",
            "databricks-asset-bundles",
            "databricks-model-serving",
            "databricks-agent-bricks",
            "databricks-synthetic-data-generation",
            "databricks-vector-search",
            "demo-governance",
            "demo-memory",
        }
        assert set(ESSENTIAL_SKILLS) == expected

    def test_no_duplicates(self) -> None:
        """ESSENTIAL_SKILLS should not contain duplicates."""
        assert len(ESSENTIAL_SKILLS) == len(set(ESSENTIAL_SKILLS))

    def test_count(self) -> None:
        """Should have exactly 10 essential skills."""
        assert len(ESSENTIAL_SKILLS) == 10
