"""Tests for Windows 11 cross-platform compatibility."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import patch

import pytest

from cleave.core.install import create_symlink_with_fallback, install_skill


class TestCrossPlatformPathHandling:
    """Tests for cross-platform path comparison and validation."""

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

    def test_path_validation_works_cross_platform(self, temp_home: Path) -> None:
        """Path validation should work on both Unix (/) and Windows (\\) separators."""
        temp_home.mkdir(parents=True)
        skills_dir = temp_home / ".claude" / "skills"
        skills_dir.mkdir(parents=True)

        # Test that paths within home are accepted
        result = install_skill(home_dir=temp_home)

        # Should not error with "Path escapes home directory"
        if not result.success:
            assert "Path escapes home directory" not in result.message

    def test_path_escape_detection(self, temp_home: Path, tmp_path: Path) -> None:
        """Should detect when resolved path escapes home directory."""
        temp_home.mkdir(parents=True)

        # Create a symlink that points outside home
        skills_dir = temp_home / ".claude" / "skills"
        skills_dir.mkdir(parents=True)

        # Try to trick the validation with a symlink to outside home
        outside_dir = tmp_path / "outside_home"
        outside_dir.mkdir()

        # Create symlink pointing outside
        fake_skills = temp_home / ".claude" / "fake_skills"
        fake_skills.symlink_to(outside_dir)

        # The install should use the actual .claude/skills path, not escape
        result = install_skill(home_dir=temp_home)

        # Path should be within home (validation passes)
        assert result.success or "Path escapes home directory" not in result.message


class TestWindowsSymlinkFallback:
    """Tests for Windows symlink privilege fallback to directory copy."""

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

    def test_symlink_fallback_on_windows_permission_error(
        self, temp_home: Path, tmp_path: Path
    ) -> None:
        """Should fall back to directory copy when symlink fails on Windows."""
        source_dir = tmp_path / "source"
        source_dir.mkdir()
        (source_dir / "test.txt").write_text("test content")

        target_path = tmp_path / "target"

        # Mock Windows platform and symlink failure
        with patch("cleave.core.install.platform.system", return_value="Windows"):
            with patch.object(Path, "symlink_to", side_effect=OSError("Symlink privilege error")):
                used_copy, error = create_symlink_with_fallback(target_path, source_dir)

        # Should have used copy fallback
        assert used_copy is True
        assert error == ""
        assert target_path.exists()
        assert (target_path / "test.txt").read_text() == "test content"

    def test_symlink_used_on_unix(self, tmp_path: Path) -> None:
        """Should use actual symlink on Unix platforms."""
        source_dir = tmp_path / "source"
        source_dir.mkdir()
        (source_dir / "test.txt").write_text("test content")

        target_path = tmp_path / "target"

        # Mock Unix platform
        with patch("cleave.core.install.platform.system", return_value="Linux"):
            used_copy, error = create_symlink_with_fallback(target_path, source_dir)

        # Should have used symlink (not copy)
        assert used_copy is False
        assert error == ""
        assert target_path.is_symlink()

    def test_install_result_indicates_copy_method(self, temp_home: Path) -> None:
        """InstallResult should indicate when directory copy was used."""
        temp_home.mkdir(parents=True)
        skills_dir = temp_home / ".claude" / "skills"
        skills_dir.mkdir(parents=True)

        # Mock Windows and symlink failure
        with patch("cleave.core.install.platform.system", return_value="Windows"):
            with patch.object(Path, "symlink_to", side_effect=OSError("Privilege error")):
                result = install_skill(home_dir=temp_home)

        # Should succeed with copy method
        assert result.success
        assert result.used_copy is True
        assert "copy" in result.message.lower() or "fallback" in result.message.lower()

    def test_existing_directory_copy_recognized(self, temp_home: Path) -> None:
        """Should recognize and skip existing directory copy installations."""
        temp_home.mkdir(parents=True)
        skills_dir = temp_home / ".claude" / "skills"
        skills_dir.mkdir(parents=True)

        # Create a directory copy (not symlink) with SKILL.md
        target = skills_dir / "cleave"
        target.mkdir()
        (target / "SKILL.md").write_text("# Test Skill")

        result = install_skill(home_dir=temp_home)

        # Should recognize and skip
        assert result.success
        assert result.action == "skipped"
        assert "directory copy" in result.message.lower()
        assert result.used_copy is True


class TestMCPDependencyCheck:
    """Tests for MCP dependency validation."""

    def test_check_mcp_dependency_returns_instructions(self) -> None:
        """check_mcp_dependency should return installation instructions."""
        from cleave.skill import check_mcp_dependency

        result = check_mcp_dependency()

        assert "message" in result
        assert "install_instructions" in result
        assert "sequentialthinking" in result["install_instructions"]
        assert "mcp__MCP_DOCKER__mcp-add" in result["install_instructions"]


class TestWindowsLongPaths:
    """Tests for Windows long path validation."""

    def test_long_path_validation_on_windows(self) -> None:
        """Should detect paths exceeding Windows MAX_PATH."""
        from cleave.core.install import check_windows_long_path

        # Create an artificially long path
        long_path = Path("C:" + "\\very_long_directory_name" * 20 + "\\file.txt")

        with patch("cleave.core.install.platform.system", return_value="Windows"):
            is_valid, error_msg = check_windows_long_path(long_path)

        # Should detect the long path
        assert not is_valid
        assert "MAX_PATH" in error_msg or "260" in error_msg

    def test_long_path_validation_allows_short_paths(self) -> None:
        """Short paths should pass validation on Windows."""
        from cleave.core.install import check_windows_long_path

        short_path = Path("C:\\Users\\Alice\\.claude\\skills\\cleave")

        with patch("cleave.core.install.platform.system", return_value="Windows"):
            is_valid, error_msg = check_windows_long_path(short_path)

        assert is_valid
        assert error_msg == ""

    def test_long_path_validation_skipped_on_unix(self) -> None:
        """Long path validation should be skipped on Unix systems."""
        from cleave.core.install import check_windows_long_path

        long_path = Path("/very/long/path" * 50)

        with patch("cleave.core.install.platform.system", return_value="Linux"):
            is_valid, error_msg = check_windows_long_path(long_path)

        # Unix has no 260 char limit
        assert is_valid
        assert error_msg == ""


class TestVersionTracking:
    """Tests for directory copy version tracking."""

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

    def test_version_file_created_for_directory_copy(
        self, temp_home: Path, tmp_path: Path
    ) -> None:
        """Should create .version file when using directory copy fallback."""
        temp_home.mkdir(parents=True)
        skills_dir = temp_home / ".claude" / "skills"
        skills_dir.mkdir(parents=True)

        # Mock Windows and symlink failure to trigger copy
        with patch("cleave.core.install.platform.system", return_value="Windows"):
            with patch.object(Path, "symlink_to", side_effect=OSError("Privilege error")):
                result = install_skill(home_dir=temp_home)

        assert result.success
        assert result.used_copy

        # Check for version file
        version_file = skills_dir / "cleave" / ".version"
        if version_file.exists():
            version = version_file.read_text().strip()
            assert version  # Should have some version string

    def test_stale_copy_detected_and_updated(
        self, temp_home: Path, tmp_path: Path
    ) -> None:
        """Should detect and update stale directory copies."""
        temp_home.mkdir(parents=True)
        skills_dir = temp_home / ".claude" / "skills"
        skills_dir.mkdir(parents=True)

        target = skills_dir / "cleave"
        target.mkdir()
        (target / "SKILL.md").write_text("# Old Skill")
        (target / ".version").write_text("0.1.0")  # Old version

        # Mock current version as different
        with patch("cleave.core.install.importlib.metadata.version", return_value="0.2.0"):
            with patch("cleave.core.install.platform.system", return_value="Windows"):
                with patch.object(Path, "symlink_to", side_effect=OSError("Privilege error")):
                    result = install_skill(home_dir=temp_home)

        # Should have updated, not skipped
        assert result.success
        assert result.action == "updated"
        assert "0.1.0" in result.message and "0.2.0" in result.message
