"""Integration tests for LocalStateScanner.

These tests use real filesystem operations to verify that the scanner
correctly discovers Local State files in complex directory structures.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from electron_angle_patcher.models import AppInfo
from electron_angle_patcher.scanner import LocalStateScanner
from tests.fixtures.app_builders import create_local_state_file


class TestLocalStateScannerIntegration:
    """Integration tests for LocalStateScanner with real filesystem."""

    def test_scans_application_support(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that scanner finds apps in Application Support directory."""
        # Create test structure
        support_dir = integration_temp_dir / "Application Support"
        create_local_state_file(support_dir, "Brave", "with_angle")
        create_local_state_file(support_dir, "Discord", "empty")

        # Scan the directory
        scanner = LocalStateScanner()
        results = scanner.scan(support_dir)

        # Verify apps were found
        assert len(results) == 2
        app_names = {app.name for app in results}
        assert "Brave" in app_names
        assert "Discord" in app_names

        # Verify state file paths are correct
        for app in results:
            assert app.state_file.name == "Local State"
            assert app.state_file.exists()

    def test_skips_hidden_directories(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that scanner skips hidden directories (starting with .)."""
        # Create visible and hidden directories
        support_dir = integration_temp_dir / "Application Support"
        create_local_state_file(support_dir, "VisibleApp", "empty")

        hidden_dir = support_dir / ".hidden_app"
        hidden_dir.mkdir(parents=True, exist_ok=True)
        (hidden_dir / "Local State").write_text('{"browser": {}}')

        # Scan the directory
        scanner = LocalStateScanner()
        results = scanner.scan(support_dir)

        # Verify only visible app was found
        assert len(results) == 1
        assert results[0].name == "VisibleApp"

    def test_skip_patterns_work(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that skip patterns filter out matching paths."""
        # Create apps that should be skipped
        support_dir = integration_temp_dir / "Application Support"
        create_local_state_file(support_dir, "GoodApp", "empty")

        # Create lghub (should be skipped with pattern)
        lghub_dir = support_dir / "lghub"
        lghub_dir.mkdir(parents=True, exist_ok=True)
        (lghub_dir / "Local State").write_text('{"browser": {}}')

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

        # Verify lghub was skipped
        assert len(results) == 1
        assert results[0].name == "GoodApp"

    def test_extract_app_name_from_complex_paths(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that app names are extracted correctly from nested paths."""
        # Create nested structure like Microsoft Teams
        teams_dir = (
            integration_temp_dir
            / "Library"
            / "Application Support"
            / "Microsoft"
            / "Teams"
            / "EBWebView"
        )
        teams_dir.mkdir(parents=True, exist_ok=True)
        (teams_dir / "Local State").write_text('{"browser": {}}')

        # Scan
        scanner = LocalStateScanner()
        results = scanner.scan(integration_temp_dir)

        # Verify app name extraction (should skip generic folder names)
        assert len(results) == 1
        assert "Teams" in results[0].name

    def test_empty_directory(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that scanning empty directory returns empty list."""
        # Create empty directory structure
        empty_dir = integration_temp_dir / "Empty"
        empty_dir.mkdir()

        # Scan
        scanner = LocalStateScanner()
        results = scanner.scan(empty_dir)

        # Verify no results
        assert len(results) == 0

    def test_deeply_nested_structure(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test scanner with deeply nested directories."""
        # Create deep nesting
        deep_dir = (
            integration_temp_dir
            / "Level1"
            / "Level2"
            / "Level3"
            / "Application Support"
            / "MyApp"
        )
        create_local_state_file(deep_dir.parent, "MyApp", "empty")

        # Scan from top level
        scanner = LocalStateScanner()
        results = scanner.scan(integration_temp_dir)

        # Verify app was found deep in structure
        assert len(results) == 1
        assert results[0].name == "MyApp"

    def test_multiple_local_state_files_same_directory(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test scanner when multiple Local State files exist."""
        # This is unusual but possible - create two files in same dir
        test_dir = integration_temp_dir / "TestApp"
        test_dir.mkdir(parents=True)
        (test_dir / "Local State").write_text('{"browser": {}}')

        # Scan
        scanner = LocalStateScanner()
        results = scanner.scan(integration_temp_dir)

        # Should find exactly one (first one encountered)
        assert len(results) == 1

    def test_skips_node_modules(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that scanner skips node_modules directories."""
        # Create app in node_modules (should be skipped)
        support_dir = integration_temp_dir / "Application Support"
        create_local_state_file(support_dir, "GoodApp", "empty")

        node_modules = support_dir / "node_modules" / "some_package"
        node_modules.mkdir(parents=True)
        (node_modules / "Local State").write_text('{"browser": {}}')

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

        # Verify node_modules was skipped
        assert len(results) == 1
        assert results[0].name == "GoodApp"

    def test_skips_library_developer(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that scanner skips Library/Developer directory."""
        # Create app in Library/Developer (should be skipped)
        dev_dir = integration_temp_dir / "Library" / "Developer" / "SomeApp"
        dev_dir.mkdir(parents=True)
        (dev_dir / "Local State").write_text('{"browser": {}}')

        # Create app outside Developer
        support_dir = integration_temp_dir / "Library" / "Application Support"
        create_local_state_file(support_dir, "GoodApp", "empty")

        # Scan from Library
        scanner = LocalStateScanner()
        results = scanner.scan(integration_temp_dir / "Library")

        # Verify Developer directory was skipped
        assert len(results) == 1
        assert results[0].name == "GoodApp"

    def test_container_structure(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test scanner with macOS app container structure."""
        # Create typical container structure
        container_dir = (
            integration_temp_dir
            / "Library"
            / "Containers"
            / "com.example.app"
            / "Data"
            / "Library"
            / "Application Support"
            / "MyApp"
        )
        create_local_state_file(container_dir.parent, "MyApp", "empty")

        # Scan
        scanner = LocalStateScanner()
        results = scanner.scan(integration_temp_dir)

        # Verify app was found in container
        assert len(results) == 1
        # App name extraction should handle container paths
        assert "MyApp" in results[0].name

    def test_network_mounts_respected(
        self,
        integration_temp_dir: Path,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test that scanner respects network mount detection."""
        # Create app in a directory
        support_dir = integration_temp_dir / "Application Support"
        app_file = create_local_state_file(support_dir, "TestApp", "empty")

        # Mock get_network_mounts to include our temp dir
        mock_mounts = {integration_temp_dir}
        monkeypatch.setattr(
            "electron_angle_patcher.utils.get_network_mounts",
            lambda: mock_mounts,
        )

        # Scan
        scanner = LocalStateScanner()
        results = scanner.scan(integration_temp_dir)

        # Verify network mount was skipped
        assert len(results) == 0

    def test_scanner_caches_network_mounts(
        self,
        integration_temp_dir: Path,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """Test that network mount check is cached for performance."""
        # Track how many times get_network_mounts is called
        call_count = 0

        def mock_get_mounts():
            nonlocal call_count
            call_count += 1
            return set()

        monkeypatch.setattr(
            "electron_angle_patcher.utils.get_network_mounts",
            mock_get_mounts,
        )

        # Create multiple directories to scan
        support_dir = integration_temp_dir / "Application Support"
        create_local_state_file(support_dir, "App1", "empty")
        create_local_state_file(support_dir, "App2", "empty")

        # Scan
        scanner = LocalStateScanner()
        scanner.scan(integration_temp_dir)

        # Verify get_network_mounts was called only once (cached)
        assert call_count == 1

    def test_relative_path_calculation(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that relative path calculation works correctly."""
        # Create app structure
        app_dir = integration_temp_dir / "Application Support" / "MyApp"
        create_local_state_file(app_dir.parent, "MyApp", "empty")

        # Scan from Application Support level
        scanner = LocalStateScanner()
        results = scanner.scan(integration_temp_dir / "Application Support")

        # Verify app name is extracted correctly
        assert len(results) == 1
        assert results[0].name == "MyApp"

    def test_generic_folder_names_filtered(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that generic folder names are filtered from app names."""
        # Create structure with generic folder names
        generic_dir = (
            integration_temp_dir
            / "Library"
            / "Application Support"
            / "EBWebView"  # Generic name
        )
        generic_dir.mkdir(parents=True)
        (generic_dir / "Local State").write_text('{"browser": {}}')

        # Scan
        scanner = LocalStateScanner()
        results = scanner.scan(integration_temp_dir)

        # Verify generic name was filtered out
        assert len(results) == 1
        # Should fall back to parent or a non-generic name
        assert results[0].name != "EBWebView"

    def test_multiple_apps_in_same_scan(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test finding multiple apps in a single scan."""
        # Create multiple apps
        support_dir = integration_temp_dir / "Application Support"
        create_local_state_file(support_dir, "App1", "empty")
        create_local_state_file(support_dir, "App2", "with_angle")
        create_local_state_file(support_dir, "App3", "empty")

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

        # Verify all apps found
        assert len(results) == 3
        app_names = {app.name for app in results}
        assert "App1" in app_names
        assert "App2" in app_names
        assert "App3" in app_names

    def test_scan_with_nonexistent_start_dir(
        self,
        tmp_path: Path,
    ) -> None:
        """Test scanning a directory that doesn't exist."""
        # Try to scan non-existent directory
        scanner = LocalStateScanner()
        results = scanner.scan(tmp_path / "does_not_exist")

        # Should return empty list (not crash)
        assert isinstance(results, list)
        assert len(results) == 0

    def test_appinfo_dataclass_properties(
        self,
        integration_temp_dir: Path,
    ) -> None:
        """Test that AppInfo objects have correct properties."""
        # Create an app
        support_dir = integration_temp_dir / "Application Support"
        state_file = create_local_state_file(support_dir, "TestApp", "empty")

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

        # Verify AppInfo properties
        assert len(results) == 1
        app = results[0]
        assert isinstance(app, AppInfo)
        assert app.name == "TestApp"
        assert app.state_file == state_file
        assert app.already_patched is False  # Default value
