"""Tests for cleave install-skill command."""

from __future__ import annotations

import argparse
from pathlib import Path
from unittest.mock import patch

import pytest

from cleave.cli import cmd_install_skill
from cleave.core.install import (
    InstallResult,
    find_package_skill_dir,
    install_skill,
)


class TestFindPackageSkillDir:
    """Tests for locating the package's skill directory."""

    def test_finds_skill_dir_in_package(self) -> None:
        """Should find the skill directory within the installed package."""
        skill_dir = find_package_skill_dir()
        assert skill_dir is not None
        assert skill_dir.exists()
        assert skill_dir.is_dir()
        assert (skill_dir / "SKILL.md").exists()

    def test_skill_dir_is_absolute(self) -> None:
        """The returned path should be absolute."""
        skill_dir = find_package_skill_dir()
        assert skill_dir is not None
        assert skill_dir.is_absolute()


class TestInstallSkill:
    """Tests for the install_skill function."""

    @pytest.fixture
    def temp_home(self, tmp_path: Path) -> Path:
        """Create a temporary home directory for testing."""
        return tmp_path / "home"

    @pytest.fixture
    def skills_dir(self, temp_home: Path) -> Path:
        """Path to the skills directory within temp home."""
        return temp_home / ".claude" / "skills"

    @pytest.fixture
    def target_link(self, skills_dir: Path) -> Path:
        """Path to the cleave symlink target."""
        return skills_dir / "cleave"

    def test_creates_symlink_when_none_exists(
        self, temp_home: Path, skills_dir: Path, target_link: Path
    ) -> None:
        """Should create symlink when skills dir exists but link doesn't."""
        skills_dir.mkdir(parents=True)

        result = install_skill(home_dir=temp_home)

        assert result.success
        assert result.action == "created"
        assert target_link.is_symlink()
        # Verify symlink points to actual skill directory
        assert (target_link / "SKILL.md").exists()

    def test_creates_parent_directories(
        self, temp_home: Path, skills_dir: Path, target_link: Path
    ) -> None:
        """Should create ~/.claude/skills/ if it doesn't exist."""
        # temp_home exists but .claude/skills does not
        temp_home.mkdir(parents=True)
        assert not skills_dir.exists()

        result = install_skill(home_dir=temp_home)

        assert result.success
        assert result.action == "created"
        assert skills_dir.exists()
        assert target_link.is_symlink()

    def test_skips_when_symlink_already_correct(
        self, temp_home: Path, skills_dir: Path, target_link: Path
    ) -> None:
        """Should skip if symlink already points to correct location."""
        skills_dir.mkdir(parents=True)
        # Create correct symlink first
        source = find_package_skill_dir()
        assert source is not None
        target_link.symlink_to(source)

        result = install_skill(home_dir=temp_home)

        assert result.success
        assert result.action == "skipped"
        assert "already installed" in result.message.lower()

    def test_updates_symlink_pointing_elsewhere(
        self, temp_home: Path, skills_dir: Path, target_link: Path
    ) -> None:
        """Should update symlink if it points to wrong location."""
        skills_dir.mkdir(parents=True)
        # Create symlink pointing to wrong place
        wrong_target = temp_home / "wrong_skill"
        wrong_target.mkdir()
        target_link.symlink_to(wrong_target)

        result = install_skill(home_dir=temp_home)

        assert result.success
        assert result.action == "updated"
        assert target_link.is_symlink()
        # Now points to correct location
        assert (target_link / "SKILL.md").exists()

    def test_errors_when_target_is_directory(
        self, temp_home: Path, skills_dir: Path, target_link: Path
    ) -> None:
        """Should error with helpful message if target is a real directory."""
        skills_dir.mkdir(parents=True)
        # Create actual directory (not symlink)
        target_link.mkdir()
        (target_link / "some_file.txt").touch()

        result = install_skill(home_dir=temp_home)

        assert not result.success
        assert result.action == "error"
        assert "directory exists" in result.message.lower()
        # Directory should be untouched
        assert target_link.is_dir()
        assert not target_link.is_symlink()

    def test_errors_when_target_is_file(
        self, temp_home: Path, skills_dir: Path, target_link: Path
    ) -> None:
        """Should error if target path is a regular file."""
        skills_dir.mkdir(parents=True)
        target_link.touch()  # Create regular file

        result = install_skill(home_dir=temp_home)

        assert not result.success
        assert result.action == "error"
        assert "file exists" in result.message.lower()

    def test_handles_broken_symlink(
        self, temp_home: Path, skills_dir: Path, target_link: Path
    ) -> None:
        """Should update a broken symlink (pointing to nonexistent path)."""
        skills_dir.mkdir(parents=True)
        # Create symlink to nonexistent path
        target_link.symlink_to("/nonexistent/path/that/does/not/exist")
        assert target_link.is_symlink()
        assert not target_link.exists()  # Broken symlink

        result = install_skill(home_dir=temp_home)

        assert result.success
        assert result.action == "updated"
        assert target_link.is_symlink()
        assert target_link.exists()  # Now valid

    def test_result_contains_paths(
        self, temp_home: Path, skills_dir: Path
    ) -> None:
        """Result should include source and target paths."""
        skills_dir.mkdir(parents=True)

        result = install_skill(home_dir=temp_home)

        assert result.source_path is not None
        assert result.target_path is not None
        assert result.source_path.exists()
        assert "cleave" in str(result.target_path)

    def test_uses_real_home_by_default(self) -> None:
        """When no home_dir provided, should use actual home directory."""
        # This test doesn't actually install, just verifies the path logic
        expected_target = Path.home() / ".claude" / "skills" / "cleave"
        source = find_package_skill_dir()

        # We can't easily test the full install without affecting real filesystem
        # but we can verify the paths are computed correctly
        assert source is not None
        assert expected_target.parent.parent == Path.home() / ".claude"


class TestInstallResultDataclass:
    """Tests for the InstallResult dataclass."""

    def test_success_result(self) -> None:
        """Verify successful result structure."""
        result = InstallResult(
            success=True,
            action="created",
            message="Skill installed successfully",
            source_path=Path("/src"),
            target_path=Path("/target"),
        )
        assert result.success
        assert result.action == "created"

    def test_error_result(self) -> None:
        """Verify error result structure."""
        result = InstallResult(
            success=False,
            action="error",
            message="Something went wrong",
            source_path=None,
            target_path=Path("/target"),
        )
        assert not result.success
        assert result.action == "error"


class TestInstallSkillCLI:
    """Tests for the CLI subcommand integration."""

    @pytest.fixture
    def temp_home(self, tmp_path: Path) -> Path:
        """Create a temporary home directory for testing."""
        return tmp_path / "home"

    def test_cli_install_skill_success(
        self, temp_home: Path, capsys: pytest.CaptureFixture[str]
    ) -> None:
        """CLI should print success message on install."""
        skills_dir = temp_home / ".claude" / "skills"
        skills_dir.mkdir(parents=True)

        with patch("cleave.cli.install_skill") as mock_install:
            mock_install.return_value = InstallResult(
                success=True,
                action="created",
                message="Test success",
                source_path=Path("/src/skill"),
                target_path=skills_dir / "cleave",
            )
            args = argparse.Namespace()
            result = cmd_install_skill(args)

        assert result == 0
        captured = capsys.readouterr()
        assert "Skill installed" in captured.out

    def test_cli_install_skill_error(
        self, temp_home: Path, capsys: pytest.CaptureFixture[str]
    ) -> None:
        """CLI should print error and return non-zero on failure."""
        with patch("cleave.cli.install_skill") as mock_install:
            mock_install.return_value = InstallResult(
                success=False,
                action="error",
                message="Directory exists at /path. Remove it manually.",
                source_path=Path("/src/skill"),
                target_path=Path("/target"),
            )
            args = argparse.Namespace()
            result = cmd_install_skill(args)

        assert result == 1
        captured = capsys.readouterr()
        assert "Error:" in captured.err
        assert "Directory exists" in captured.err

    def test_cli_install_skill_skipped(
        self, capsys: pytest.CaptureFixture[str]
    ) -> None:
        """CLI should print appropriate message when already installed."""
        with patch("cleave.cli.install_skill") as mock_install:
            mock_install.return_value = InstallResult(
                success=True,
                action="skipped",
                message="Already installed",
                source_path=Path("/src/skill"),
                target_path=Path("/target/cleave"),
            )
            args = argparse.Namespace()
            result = cmd_install_skill(args)

        assert result == 0
        captured = capsys.readouterr()
        assert "already installed" in captured.out.lower()
