"""
Integration tests for editable mode workflow and mode transitions.

Tests the full lifecycle of editable mode:
- Fresh installation with editable mode
- Mode transitions (normal -> editable, editable -> normal)
- File type preservation for non-symlinkable files

These tests validate that compute_changes() correctly detects file type
mismatches when transitioning between modes.

Note: Unit tests for _is_file_type_mismatch() are located in
tests/unit/test_persuade/test_file_ops.py (TestFileTypeMismatchDetection class).
"""

from pathlib import Path

from persuade.constants import FileCategory
from persuade.file_ops import (
    FileInfo,
    apply_file_changes,
    compute_changes,
)


class TestComputeChangesWithModeTransition:
    """Test compute_changes() with mode transition detection."""

    def test_detects_copy_to_symlink_transition(self, tmp_path: Path) -> None:
        """compute_changes should detect files needing conversion from copy to symlink."""
        source_path = tmp_path / "source"
        target_path = tmp_path / "target"

        # Setup source with a file
        source_claude = source_path / ".claude" / "scripts"
        source_claude.mkdir(parents=True)
        source_file = source_claude / "test.sh"
        source_file.write_text("#!/bin/bash\necho test")

        # Setup target with a COPY of the same file (not a symlink)
        target_claude = target_path / ".claude" / "scripts"
        target_claude.mkdir(parents=True)
        target_file = target_claude / "test.sh"
        target_file.write_text("#!/bin/bash\necho test")  # Same content

        source_files = {
            ".claude/scripts/test.sh": FileInfo(
                category=FileCategory.SYNC_ALWAYS,
                destination=None,
            )
        }

        # With use_symlinks=False, no mismatch (both are copies)
        changes_normal = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=False
        )
        assert ".claude/scripts/test.sh" not in changes_normal.modified

        # With use_symlinks=True, should detect mismatch (copy when symlink expected)
        changes_editable = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=True
        )
        assert ".claude/scripts/test.sh" in changes_editable.modified

    def test_detects_symlink_to_copy_transition(self, tmp_path: Path) -> None:
        """compute_changes should detect files needing conversion from symlink to copy."""
        source_path = tmp_path / "source"
        target_path = tmp_path / "target"

        # Setup source with a file
        source_claude = source_path / ".claude" / "scripts"
        source_claude.mkdir(parents=True)
        source_file = source_claude / "test.sh"
        source_file.write_text("#!/bin/bash\necho test")

        # Setup target with a SYMLINK to the source file
        target_claude = target_path / ".claude" / "scripts"
        target_claude.mkdir(parents=True)
        target_file = target_claude / "test.sh"
        target_file.symlink_to(source_file)

        source_files = {
            ".claude/scripts/test.sh": FileInfo(
                category=FileCategory.SYNC_ALWAYS,
                destination=None,
            )
        }

        # With use_symlinks=True, no mismatch (both expect symlink)
        changes_editable = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=True
        )
        assert ".claude/scripts/test.sh" not in changes_editable.modified

        # With use_symlinks=False, should detect mismatch (symlink when copy expected)
        changes_normal = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=False
        )
        assert ".claude/scripts/test.sh" in changes_normal.modified


class TestModeTransitionWorkflow:
    """Integration tests for full mode transition workflows."""

    def test_full_editable_workflow_lifecycle(self, tmp_path: Path) -> None:
        """Test complete lifecycle: fresh install -> editable -> modify -> normal -> editable."""
        source_path = tmp_path / "source"
        target_path = tmp_path / "target"

        # Setup source repository
        source_claude = source_path / ".claude" / "scripts"
        source_claude.mkdir(parents=True)
        source_file = source_claude / "sync.sh"
        source_file.write_text("#!/bin/bash\necho original")

        source_files = {
            ".claude/scripts/sync.sh": FileInfo(
                category=FileCategory.SYNC_ALWAYS,
                destination=None,
            )
        }

        # Step 1: Fresh install in editable mode
        target_path.mkdir()
        changes = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=True
        )
        assert ".claude/scripts/sync.sh" in changes.added

        apply_file_changes(source_path, target_path, changes, source_files, use_symlinks=True)

        target_file = target_path / ".claude" / "scripts" / "sync.sh"
        assert target_file.is_symlink(), "Should be symlink after editable install"
        assert target_file.read_text() == "#!/bin/bash\necho original"

        # Step 2: Modify source - symlink should reflect change immediately
        source_file.write_text("#!/bin/bash\necho modified")
        assert target_file.read_text() == "#!/bin/bash\necho modified", (
            "Symlinked file should reflect source changes"
        )

        # Step 3: Transition to normal mode
        changes = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=False
        )
        assert ".claude/scripts/sync.sh" in changes.modified, (
            "Should detect symlink needs replacement with copy"
        )

        apply_file_changes(source_path, target_path, changes, source_files, use_symlinks=False)

        assert not target_file.is_symlink(), "Should be regular file after normal mode sync"
        assert target_file.read_text() == "#!/bin/bash\necho modified"

        # Step 4: Verify files are now independent
        source_file.write_text("#!/bin/bash\necho changed again")
        assert target_file.read_text() == "#!/bin/bash\necho modified", (
            "Copied file should NOT reflect source changes"
        )

        # Step 5: Transition back to editable mode
        changes = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=True
        )
        # Content differs now, so it should be in modified
        assert ".claude/scripts/sync.sh" in changes.modified

        apply_file_changes(source_path, target_path, changes, source_files, use_symlinks=True)

        assert target_file.is_symlink(), "Should be symlink after returning to editable mode"
        assert target_file.read_text() == "#!/bin/bash\necho changed again"

    def test_mode_transition_normal_to_editable(self, tmp_path: Path) -> None:
        """Test transition from normal mode (copies) to editable mode (symlinks)."""
        source_path = tmp_path / "source"
        target_path = tmp_path / "target"

        # Setup source
        source_claude = source_path / ".claude" / "commands"
        source_claude.mkdir(parents=True)
        (source_claude / "review.md").write_text("# Review command")

        # Setup target with copied files (simulating normal mode state)
        target_claude = target_path / ".claude" / "commands"
        target_claude.mkdir(parents=True)
        target_file = target_claude / "review.md"
        target_file.write_text("# Review command")

        assert not target_file.is_symlink(), "Precondition: file should be a copy"

        source_files = {
            ".claude/commands/review.md": FileInfo(
                category=FileCategory.SYNC_ALWAYS,
                destination=None,
            )
        }

        # Transition to editable mode
        changes = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=True
        )

        assert ".claude/commands/review.md" in changes.modified, (
            "Should detect copy needs replacement with symlink"
        )

        apply_file_changes(source_path, target_path, changes, source_files, use_symlinks=True)

        assert target_file.is_symlink(), "File should be symlink after transition"

    def test_mode_transition_editable_to_normal(self, tmp_path: Path) -> None:
        """Test transition from editable mode (symlinks) to normal mode (copies)."""
        source_path = tmp_path / "source"
        target_path = tmp_path / "target"

        # Setup source
        source_claude = source_path / ".claude" / "hooks"
        source_claude.mkdir(parents=True)
        source_file = source_claude / "pre-commit.sh"
        source_file.write_text("#!/bin/bash\necho hook")

        # Setup target with symlinked file (simulating editable mode state)
        target_claude = target_path / ".claude" / "hooks"
        target_claude.mkdir(parents=True)
        target_file = target_claude / "pre-commit.sh"
        target_file.symlink_to(source_file)

        assert target_file.is_symlink(), "Precondition: file should be a symlink"

        source_files = {
            ".claude/hooks/pre-commit.sh": FileInfo(
                category=FileCategory.SYNC_ALWAYS,
                destination=None,
            )
        }

        # Transition to normal mode
        changes = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=False
        )

        assert ".claude/hooks/pre-commit.sh" in changes.modified, (
            "Should detect symlink needs replacement with copy"
        )

        apply_file_changes(source_path, target_path, changes, source_files, use_symlinks=False)

        assert not target_file.is_symlink(), "File should be regular file after transition"
        assert target_file.read_text() == "#!/bin/bash\necho hook"

    def test_mode_transition_preserves_non_symlinkable_files(self, tmp_path: Path) -> None:
        """COPY_IF_MISSING and MERGE_SMART files should not be affected by mode transitions."""
        source_path = tmp_path / "source"
        target_path = tmp_path / "target"

        # Setup source
        source_claude = source_path / ".claude"
        source_claude.mkdir(parents=True)
        (source_claude / "README.md").write_text("# Project README")

        # Setup target with existing file
        target_claude = target_path / ".claude"
        target_claude.mkdir(parents=True)
        target_readme = target_claude / "README.md"
        target_readme.write_text("# Customized README")  # Different content

        source_files = {
            ".claude/README.md": FileInfo(
                category=FileCategory.COPY_IF_MISSING,
                destination=None,
            )
        }

        # In editable mode, COPY_IF_MISSING files should not be modified
        changes = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=True
        )

        assert ".claude/README.md" not in changes.modified, (
            "COPY_IF_MISSING files should not be modified"
        )
        assert ".claude/README.md" not in changes.added, (
            "COPY_IF_MISSING files should not be re-added if they exist"
        )

        # File should remain unchanged
        assert target_readme.read_text() == "# Customized README"
        assert not target_readme.is_symlink()

    def test_mode_transition_preserves_settings_json(self, tmp_path: Path) -> None:
        """settings.json (MERGE_SMART) should never become a symlink during mode transitions.

        This test validates that settings.json is never symlinked regardless of mode,
        ensuring user customizations are preserved and merge logic can work correctly.
        """
        source_path = tmp_path / "source"
        target_path = tmp_path / "target"

        # Setup source with settings.json
        source_claude = source_path / ".claude"
        source_claude.mkdir(parents=True)
        source_settings = source_claude / "settings.json"
        source_settings.write_text('{"sourceKey": "sourceValue"}')

        # Setup target with customized settings.json
        target_claude = target_path / ".claude"
        target_claude.mkdir(parents=True)
        target_settings = target_claude / "settings.json"
        target_settings.write_text('{"targetKey": "customValue"}')

        source_files = {
            ".claude/settings.json": FileInfo(
                category=FileCategory.MERGE_SMART,
                destination=None,
            )
        }

        # In editable mode, settings.json should still be treated as MERGE_SMART
        _changes = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=True
        )

        # settings.json is always in modified (for merge handling) but should not be
        # symlinked - verify the file type after any operations
        assert target_settings.exists(), "settings.json should exist"
        assert not target_settings.is_symlink(), (
            "settings.json should NEVER be a symlink, even in editable mode"
        )
        assert target_settings.read_text() == '{"targetKey": "customValue"}', (
            "settings.json content should be preserved (merge handled separately)"
        )

        # Transition back to normal mode - should still not be a symlink
        _changes_normal = compute_changes(
            source_path, target_path, source_files, strict=False, use_symlinks=False
        )

        assert not target_settings.is_symlink(), (
            "settings.json should remain a regular file in normal mode"
        )
