"""Integration tests for executable patching.

These tests use real filesystem operations to verify that ExecutableWrapper
and LghubPatcher correctly create backup files and wrapper scripts.
"""

from __future__ import annotations

import stat
from pathlib import Path
from unittest.mock import MagicMock

import pytest

from electron_angle_patcher.executable_patcher import (
    BraveExecutablePatcher,
    ExecutableWrapper,
    LghubPatcher,
)
from electron_angle_patcher.models import PatchResult
from tests.fixtures.app_builders import (
    create_brave_app_bundle,
    create_lghub_app_bundle,
)


class TestExecutableWrapperIntegration:
    """Integration tests for ExecutableWrapper with real file I/O."""

    def test_fresh_app_gets_wrapper(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
    ) -> None:
        """Test that fresh app gets wrapper script and backup."""
        # Create a fresh app bundle
        app_path = create_brave_app_bundle(integration_temp_dir, "fresh")

        # Patch the app
        wrapper = ExecutableWrapper(app_path, "Brave Browser")
        result = wrapper.patch()

        # Verify success
        assert result == PatchResult.SUCCESS

        # Verify backup exists
        backup_path = app_path / "Contents" / "MacOS" / "Brave Browser.original"
        assert backup_path.exists()

        # Verify wrapper exists and has correct content
        executable = app_path / "Contents" / "MacOS" / "Brave Browser"
        assert executable.exists()
        content = executable.read_text()
        assert "#!/bin/bash" in content
        assert "--use-angle=gl" in content
        assert "Brave Browser.original" in content

        # Verify executable permissions
        st_mode = executable.stat().st_mode
        assert st_mode & stat.S_IXUSR != 0

    def test_already_patched_detected(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that already patched app is detected and skipped."""
        # Create an already patched app
        app_path = create_brave_app_bundle(integration_temp_dir, "already_patched")

        # Attempt to patch
        wrapper = ExecutableWrapper(app_path, "Brave Browser")
        result = wrapper.patch()

        # Verify already patched status
        assert result == PatchResult.ALREADY_PATCHED

        # Verify backup still exists (not modified)
        backup_path = app_path / "Contents" / "MacOS" / "Brave Browser.original"
        assert backup_path.exists()

    def test_app_not_found(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that non-existent app returns NOT_FOUND."""
        # Attempt to patch non-existent app
        app_path = integration_temp_dir / "MissingApp.app"
        wrapper = ExecutableWrapper(app_path, "MissingApp")
        result = wrapper.patch()

        # Verify not found status
        assert result == PatchResult.NOT_FOUND

    def test_executable_not_found(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that app without executable returns NOT_FOUND."""
        # Create app bundle with corrupt structure
        app_path = integration_temp_dir / "CorruptApp.app"
        macos_path = app_path / "Contents" / "MacOS"
        macos_path.mkdir(parents=True)
        # Don't create the executable

        # Attempt to patch
        wrapper = ExecutableWrapper(app_path, "CorruptApp")
        result = wrapper.patch()

        # Verify not found status
        assert result == PatchResult.NOT_FOUND

    def test_permission_denied(
        self,
        integration_temp_dir: Path,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test that unwritable app returns PERMISSION_DENIED."""
        # Create a fresh app
        app_path = create_brave_app_bundle(integration_temp_dir, "fresh")

        # Mock can_write_to_path to return False
        monkeypatch.setattr(
            "electron_angle_patcher.executable_patcher.can_write_to_path",
            lambda path: False,
        )

        # Attempt to patch
        wrapper = ExecutableWrapper(app_path, "Brave Browser")
        result = wrapper.patch()

        # Verify permission denied status
        assert result == PatchResult.PERMISSION_DENIED

        # Verify no backup was created
        backup_path = app_path / "Contents" / "MacOS" / "Brave Browser.original"
        assert not backup_path.exists()

    def test_custom_extra_args(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
    ) -> None:
        """Test that custom extra arguments are used."""
        # Create a fresh app
        app_path = create_brave_app_bundle(integration_temp_dir, "fresh")

        # Patch with custom args
        wrapper = ExecutableWrapper(app_path, "Brave Browser")
        result = wrapper.patch(extra_args=["--custom-arg=value", "--another-flag"])

        # Verify success
        assert result == PatchResult.SUCCESS

        # Verify wrapper contains custom args
        executable = app_path / "Contents" / "MacOS" / "Brave Browser"
        content = executable.read_text()
        assert "--custom-arg=value" in content
        assert "--another-flag" in content

    def test_codesign_called_real(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
        real_codesign_available: bool,
    ) -> None:
        """Test that codesign is actually called when available."""
        if not real_codesign_available:
            pytest.skip("codesign not available on this system")

        # Create a fresh app
        app_path = create_brave_app_bundle(integration_temp_dir, "fresh")

        # Patch the app (will call real codesign)
        wrapper = ExecutableWrapper(app_path, "Brave Browser")
        result = wrapper.patch()

        # Verify success
        assert result == PatchResult.SUCCESS

        # Verify backup was created (indicates successful patch)
        backup_path = app_path / "Contents" / "MacOS" / "Brave Browser.original"
        assert backup_path.exists()

    def test_xattr_called_on_app_bundle(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
    ) -> None:
        """Test that xattr is called to strip extended attributes."""
        # Create a fresh app
        app_path = create_brave_app_bundle(integration_temp_dir, "fresh")

        # Patch the app
        wrapper = ExecutableWrapper(app_path, "Brave Browser")
        result = wrapper.patch()

        # Verify success
        assert result == PatchResult.SUCCESS

        # Verify backup was created
        backup_path = app_path / "Contents" / "MacOS" / "Brave Browser.original"
        assert backup_path.exists()


class TestBraveExecutablePatcherIntegration:
    """Integration tests for BraveExecutablePatcher."""

    def test_patches_brave_successfully(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
    ) -> None:
        """Test that Brave browser is patched successfully."""
        # Create Brave app
        app_path = create_brave_app_bundle(integration_temp_dir, "fresh")

        # Patch using BraveExecutablePatcher
        patcher = BraveExecutablePatcher(app_path)
        result = patcher.patch()

        # Verify success
        assert result == PatchResult.SUCCESS

        # Verify wrapper and backup exist
        executable = app_path / "Contents" / "MacOS" / "Brave Browser"
        backup = app_path / "Contents" / "MacOS" / "Brave Browser.original"
        assert executable.exists()
        assert backup.exists()


class TestLghubPatcherIntegration:
    """Integration tests for LghubPatcher."""

    def test_patches_inner_electron_app(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
    ) -> None:
        """Test that lghub patches the nested Electron app."""
        # Create lghub app with nested structure
        app_path = create_lghub_app_bundle(integration_temp_dir, "fresh")

        # Patch using LghubPatcher
        patcher = LghubPatcher(app_path)
        result = patcher.patch()

        # Verify success
        assert result == PatchResult.SUCCESS

        # Verify the nested Electron app was patched
        inner_app = app_path / "Contents" / "MacOS" / "lghub_ui.app"
        inner_exe = inner_app / "Contents" / "MacOS" / "lghub_ui"
        inner_backup = inner_app / "Contents" / "MacOS" / "lghub_ui.original"

        assert inner_exe.exists()
        assert inner_backup.exists()

        # Verify wrapper content
        content = inner_exe.read_text()
        assert "#!/bin/bash" in content
        assert "--use-angle=gl" in content

    def test_lghub_not_found(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that non-existent lghub returns NOT_FOUND."""
        # Attempt to patch non-existent lghub
        app_path = integration_temp_dir / "MissingLghub.app"
        patcher = LghubPatcher(app_path)
        result = patcher.patch()

        # Verify not found status
        assert result == PatchResult.NOT_FOUND

    def test_lghub_already_patched(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that already patched lghub is detected."""
        # Create already patched lghub
        app_path = create_lghub_app_bundle(integration_temp_dir, "already_patched")

        # Attempt to patch
        patcher = LghubPatcher(app_path)
        result = patcher.patch()

        # Verify already patched status
        assert result == PatchResult.ALREADY_PATCHED

    def test_lghub_inner_app_missing(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that lghub with missing inner app handles gracefully."""
        # Create corrupt lghub without inner executable
        app_path = create_lghub_app_bundle(integration_temp_dir, "corrupt")

        # Attempt to patch
        patcher = LghubPatcher(app_path)
        result = patcher.patch()

        # Verify not found status (inner app executable missing)
        assert result == PatchResult.NOT_FOUND
