"""Integration tests for component interactions.

These tests verify that multiple components work together correctly,
such as scanner feeding apps to patcher, and the main orchestrator.
"""

from __future__ import annotations

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

import pytest

from electron_angle_patcher import ElectronAnglePatcher
from electron_angle_patcher.models import PatchResult
from electron_angle_patcher.scanner import LocalStateScanner
from electron_angle_patcher.settings_patcher import ChromiumSettingsPatcher
from tests.fixtures.app_builders import (
    create_brave_app_bundle,
    create_lghub_app_bundle,
    create_local_state_file,
)


class TestScanAndPatchWorkflow:
    """Tests for scanner → patcher workflow."""

    def test_scan_and_patch_workflow(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
    ) -> None:
        """Test that scanner feeds apps to patcher correctly."""
        # Create test apps
        support_dir = integration_temp_dir / "Application Support"
        app1_file = create_local_state_file(support_dir, "App1", "with_angle")
        app2_file = create_local_state_file(support_dir, "App2", "empty")

        # Scan for apps
        scanner = LocalStateScanner()
        apps = scanner.scan(support_dir)

        # Verify apps were found
        assert len(apps) == 2

        # Patch each discovered app
        results = []
        for app in apps:
            patcher = ChromiumSettingsPatcher(app.state_file)
            result = patcher.patch()
            results.append((app.name, result))

        # Verify all patches succeeded
        assert len(results) == 2
        for name, result in results:
            assert result == PatchResult.SUCCESS

        # Verify files were actually modified
        for state_file in [app1_file, app2_file]:
            data = json.loads(state_file.read_text())
            assert "use-angle@1" in data["browser"]["enabled_labs_experiments"]

    def test_scan_and_patch_selective(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test selective patching after scanning."""
        # Create multiple apps
        support_dir = integration_temp_dir / "Application Support"
        create_local_state_file(support_dir, "App1", "with_angle")
        create_local_state_file(support_dir, "App2", "with_angle")
        create_local_state_file(support_dir, "App3", "with_angle")

        # Scan
        scanner = LocalStateScanner()
        apps = scanner.scan(support_dir)

        # Only patch first app
        assert len(apps) >= 1
        first_app = apps[0]

        patcher = ChromiumSettingsPatcher(first_app.state_file)
        result = patcher.patch()

        assert result == PatchResult.SUCCESS

        # Verify only first app was patched
        data = json.loads(first_app.state_file.read_text())
        assert "use-angle@1" in data["browser"]["enabled_labs_experiments"]

    def test_scan_with_skip_patterns(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that skip patterns work in scan-and-patch workflow."""
        # Create apps, some should be skipped
        support_dir = integration_temp_dir / "Application Support"
        app1_file = create_local_state_file(support_dir, "GoodApp", "with_angle")
        create_local_state_file(support_dir, "lghub", "with_angle")  # Should be skipped

        # Scan with skip pattern
        scanner = LocalStateScanner(skip_patterns=frozenset(["lghub"]))
        apps = scanner.scan(support_dir)

        # Verify only GoodApp found
        assert len(apps) == 1
        assert apps[0].name == "GoodApp"

        # Patch it
        patcher = ChromiumSettingsPatcher(apps[0].state_file)
        result = patcher.patch()

        assert result == PatchResult.SUCCESS


class TestElectronAnglePatcher:
    """Tests for the main ElectronAnglePatcher orchestrator."""

    def test_run_list_only(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
        mock_network_mounts_empty: MagicMock,
    ) -> None:
        """Test ElectronAnglePatcher.run(list_only=True)."""
        # Create Brave app
        brave_path = create_brave_app_bundle(integration_temp_dir, "fresh")

        # Create lghub app
        lghub_path = create_lghub_app_bundle(integration_temp_dir, "fresh")

        # Use non-existent Chrome path (will be skipped)
        fake_chrome = integration_temp_dir / "NonExistentChrome.app"

        # Run in list-only mode
        patcher = ElectronAnglePatcher(
            brave_path=brave_path,
            chrome_path=fake_chrome,
            lghub_path=lghub_path,
        )
        summary = patcher.run(list_only=True)

        # Verify summary
        assert summary.total >= 3  # Brave, Chrome (not found), and lghub

        # Verify no modifications were made
        brave_backup = brave_path / "Contents" / "MacOS" / "Brave Browser.original"
        lghub_backup = (
            lghub_path
            / "Contents"
            / "MacOS"
            / "lghub_ui.app"
            / "Contents"
            / "MacOS"
            / "lghub_ui.original"
        )

        assert not brave_backup.exists()
        assert not lghub_backup.exists()

    def test_run_patches_apps(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
        mock_network_mounts_empty: MagicMock,
    ) -> None:
        """Test ElectronAnglePatcher.run(list_only=False)."""
        # Create Brave app
        brave_path = create_brave_app_bundle(integration_temp_dir, "fresh")

        # Create lghub app
        lghub_path = create_lghub_app_bundle(integration_temp_dir, "fresh")

        # Use non-existent Chrome path (will be skipped)
        fake_chrome = integration_temp_dir / "NonExistentChrome.app"

        # Run in patch mode
        patcher = ElectronAnglePatcher(
            brave_path=brave_path,
            chrome_path=fake_chrome,
            lghub_path=lghub_path,
        )
        summary = patcher.run(list_only=False)

        # Verify summary
        assert summary.total >= 2
        assert len(summary.successful) >= 2

        # Verify modifications were made
        brave_backup = brave_path / "Contents" / "MacOS" / "Brave Browser.original"
        lghub_backup = (
            lghub_path
            / "Contents"
            / "MacOS"
            / "lghub_ui.app"
            / "Contents"
            / "MacOS"
            / "lghub_ui.original"
        )

        assert brave_backup.exists()
        assert lghub_backup.exists()

    def test_run_with_nonexistent_apps(
        self,
        integration_temp_dir: Path,
        mock_network_mounts_empty: MagicMock,
    ) -> None:
        """Test run with non-existent app paths."""
        # Use non-existent paths
        fake_brave = integration_temp_dir / "NonExistentBrave.app"
        fake_chrome = integration_temp_dir / "NonExistentChrome.app"
        fake_lghub = integration_temp_dir / "NonExistentLghub.app"

        # Run
        patcher = ElectronAnglePatcher(
            brave_path=fake_brave,
            chrome_path=fake_chrome,
            lghub_path=fake_lghub,
        )
        summary = patcher.run(list_only=True)

        # Verify apps marked as not found
        assert len(summary.not_found) >= 3
        assert "Brave Browser" in summary.not_found
        assert "Google Chrome" in summary.not_found
        assert "lghub" in summary.not_found

    def test_run_mixed_success_failure(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
        mock_network_mounts_empty: MagicMock,
    ) -> None:
        """Test run with mix of successful and failed patches."""
        # Create fresh Brave (will succeed)
        brave_path = create_brave_app_bundle(integration_temp_dir, "fresh")

        # Create already patched lghub (will skip)
        lghub_path = create_lghub_app_bundle(integration_temp_dir, "already_patched")

        # Use non-existent Chrome path (will be skipped)
        fake_chrome = integration_temp_dir / "NonExistentChrome.app"

        # Run
        patcher = ElectronAnglePatcher(
            brave_path=brave_path,
            chrome_path=fake_chrome,
            lghub_path=lghub_path,
        )
        summary = patcher.run(list_only=False)

        # Verify mixed results
        assert summary.total >= 3
        assert len(summary.successful) >= 1  # Brave patched
        assert len(summary.already_patched) >= 1  # lghub skipped
        assert "Google Chrome" in summary.not_found  # Chrome doesn't exist

    def test_run_deep_scan(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
        mock_network_mounts_empty: MagicMock,
    ) -> None:
        """Test run with deep scan enabled."""
        # Create test structure in temp dir (simulating home)
        support_dir = integration_temp_dir / "Library" / "Application Support"
        create_local_state_file(support_dir, "TestApp", "empty")

        # Run with deep scan (will scan from integration_temp_dir)
        patcher = ElectronAnglePatcher()
        # Note: Deep scan from actual home, but we've mocked network mounts
        summary = patcher.run(list_only=True, deep_scan=False)

        # Should complete without error
        assert isinstance(summary, type(summary))  # Just verify it returns summary

    def test_summary_properties(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
        mock_network_mounts_empty: MagicMock,
    ) -> None:
        """Test that PatchSummary properties work correctly."""
        # Create apps
        brave_path = create_brave_app_bundle(integration_temp_dir, "fresh")
        lghub_path = create_lghub_app_bundle(integration_temp_dir, "already_patched")

        # Run
        patcher = ElectronAnglePatcher(
            brave_path=brave_path,
            lghub_path=lghub_path,
        )
        summary = patcher.run(list_only=True)

        # Verify summary properties
        assert hasattr(summary, "successful")
        assert hasattr(summary, "already_patched")
        assert hasattr(summary, "not_found")
        assert hasattr(summary, "failed")
        assert hasattr(summary, "total")

        # Verify total calculation
        expected_total = (
            len(summary.successful)
            + len(summary.already_patched)
            + len(summary.not_found)
            + len(summary.failed)
        )
        assert summary.total == expected_total


class TestPatcherIntegration:
    """Tests for patcher component integration."""

    def test_settings_patcher_with_scanner_results(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that ChromiumSettingsPatcher works with scanner output."""
        # Create test app
        support_dir = integration_temp_dir / "Application Support"
        state_file = create_local_state_file(support_dir, "TestApp", "with_angle")

        # Scan
        scanner = LocalStateScanner()
        apps = scanner.scan(support_dir)

        # Use scanner result to create patcher
        assert len(apps) == 1
        app = apps[0]

        patcher = ChromiumSettingsPatcher(app.state_file)
        result = patcher.patch()

        # Verify success
        assert result == PatchResult.SUCCESS

        # Verify file modified
        data = json.loads(state_file.read_text())
        assert "use-angle@1" in data["browser"]["enabled_labs_experiments"]

    def test_multiple_scans_and_patches(
        self,
        integration_temp_dir: Path,
        mock_subprocess_all_safe: None,
    ) -> None:
        """Test running multiple scan-and-patch cycles."""
        # Create apps
        support_dir = integration_temp_dir / "Application Support"
        create_local_state_file(support_dir, "App1", "with_angle")
        create_local_state_file(support_dir, "App2", "empty")

        # First scan and patch
        scanner = LocalStateScanner()
        apps = scanner.scan(support_dir)

        for app in apps:
            patcher = ChromiumSettingsPatcher(app.state_file)
            patcher.patch()

        # Second scan (should find all already patched)
        apps_after = scanner.scan(support_dir)

        # Should find same apps
        assert len(apps_after) == len(apps)

        # Patching again should be safe (already correct value)
        for app in apps_after:
            patcher = ChromiumSettingsPatcher(app.state_file)
            result = patcher.patch()
            # Should succeed (files already correct)
            assert result == PatchResult.SUCCESS
