"""Fixtures for integration tests.

This module provides fixtures for integration testing that use real filesystem
operations while mocking dangerous or slow operations.

SAFETY MEASURES - HOST SYSTEM PROTECTION:
==========================================

These fixtures ensure tests NEVER modify files on the host system:

1. `protect_host_system` (autouse):
   - Overrides Config default paths (/Applications/*.app) → temp directory
   - Overrides Config STANDARD_SCAN_DIRS (~/Library/*) → temp directory
   - Clears Config SPECIFIC_APPS (real app paths)
   - All tests automatically protected

2. `mock_pkill_safe`:
   - Prevents killing real processes via pkill
   - Returns "no processes found" for all pkill calls

3. `mock_subprocess_all_safe`:
   - Mocks pkill, codesign, xattr in one fixture
   - Prevents real subprocess execution

4. `track_temp_files`:
   - Validates writes stay within temp directories
   - Raises AssertionError if host files would be modified

All integration tests use pytest's tmp_path fixture which provides:
- Isolated temporary directory per test
- Automatic cleanup after test
- Typically under /tmp/ or /var/folders/

EXAMPLES OF PROTECTION:
-----------------------
- /Applications/Brave Browser.app → /tmp/pytest-x/Applications/Brave Browser.app
- ~/Library/Application Support → /tmp/pytest-x/Library/Application Support
- pkill Brave → mocked (no real process killed)
- codesign --force ... → mocked (no real signing)
"""

from __future__ import annotations

import os
import shutil
import subprocess
from pathlib import Path
from types import ModuleType
from unittest.mock import MagicMock
from typing import Set

import pytest

from electron_angle_patcher.config import Config

# Save reference to original subprocess.run before any mocking
_ORIGINAL_SUBPROCESS_RUN = subprocess.run

# Track files created during tests for validation
_TEMP_FILES_CREATED: Set[Path] = set()


@pytest.fixture(autouse=True)
def protect_host_system(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Automatically protect host system from integration tests.

    This fixture:
    1. Overrides default Config paths to use temp directory
    2. Prevents scanning of real user directories
    3. Validates no files created outside temp directories

    Args:
        tmp_path: Pytest's built-in tmp_path fixture.
        monkeypatch: Pytest's monkeypatch fixture.

    Returns:
        None
    """
    # Override default paths to point to temp directory
    fake_brave = tmp_path / "Applications" / "Brave Browser.app"
    fake_chrome = tmp_path / "Applications" / "Google Chrome.app"
    fake_lghub = tmp_path / "Applications" / "lghub.app"

    # Create parent directories
    fake_brave.parent.mkdir(parents=True, exist_ok=True)
    fake_chrome.parent.mkdir(parents=True, exist_ok=True)
    fake_lghub.parent.mkdir(parents=True, exist_ok=True)

    monkeypatch.setattr(Config, "DEFAULT_BRAVE_PATH", fake_brave)
    monkeypatch.setattr(Config, "DEFAULT_CHROME_PATH", fake_chrome)
    monkeypatch.setattr(Config, "DEFAULT_LGHUB_PATH", fake_lghub)

    # Override standard scan dirs to use temp directory structure
    temp_scan_dirs = [
        tmp_path / "Library" / "Application Support",
        tmp_path / "Library" / "Caches",
        tmp_path / "Library" / "Preferences",
        tmp_path / "Library" / "Containers",
    ]

    # Create the directories
    for scan_dir in temp_scan_dirs:
        scan_dir.mkdir(parents=True, exist_ok=True)

    monkeypatch.setattr(Config, "STANDARD_SCAN_DIRS", [str(d) for d in temp_scan_dirs])

    # Clear SPECIFIC_APPS to prevent scanning real paths
    monkeypatch.setattr(Config, "SPECIFIC_APPS", {})


@pytest.fixture(autouse=True)
def track_temp_files(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Track file creation to ensure no host files are modified.

    Args:
        tmp_path: Pytest's built-in tmp_path fixture.
        monkeypatch: Pytest's monkeypatch fixture.

    Returns:
        None
    """
    created_files = set()

    original_path_write = Path.write_text
    original_path_write_bytes = Path.write_bytes
    original_path_mkdir = Path.mkdir
    original_path_touch = Path.touch

    def tracked_write(self, *args, **kwargs):
        result = original_path_write(self, *args, **kwargs)
        try:
            if not self.is_relative_to(tmp_path):
                raise AssertionError(
                    f"TEST SAFETY VIOLATION: Attempting to write to host file:\n  {self}\n"
                    f"  All file operations must stay within temp directory: {tmp_path}"
                )
        except ValueError:
            # Not relative to tmp_path
            if not str(self).startswith("/var/folders/") and not str(self).startswith("/tmp/"):
                raise AssertionError(
                    f"TEST SAFETY VIOLATION: Attempting to write to host file:\n  {self}\n"
                    f"  All file operations must stay within temp directories"
                )
        return result

    monkeypatch.setattr(Path, "write_text", tracked_write)
    # Note: We don't track all operations to avoid performance overhead
    # The protect_host_system fixture handles the main risks


@pytest.fixture
def integration_temp_dir(tmp_path: Path) -> Path:
    """Create a temporary directory for integration testing.

    This is a thin wrapper around pytest's tmp_path that provides
    a clean directory for each test function with automatic cleanup.

    Args:
        tmp_path: Pytest's built-in tmp_path fixture.

    Returns:
        Path to temporary directory.
    """
    return tmp_path


@pytest.fixture
def real_codesign_available() -> bool:
    """Check if codesign command is available on the system.

    Returns:
        True if codesign is available, False otherwise.
    """
    return shutil.which("codesign") is not None


@pytest.fixture
def mock_pkill_safe(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
    """Mock pkill to prevent killing real processes.

    This fixture intercepts subprocess.run calls and prevents
    pkill from actually executing, which could kill user processes
    during testing.

    Args:
        monkeypatch: Pytest's monkeypatch fixture.

    Returns:
        MagicMock instance for the mocked pkill result.
    """
    mock_result = MagicMock()
    mock_result.returncode = 1  # No processes found
    mock_result.stdout = ""
    mock_result.stderr = ""

    import electron_angle_patcher.utils as utils_module

    def safe_run(*args, **kwargs):
        cmd_args = args[0] if args else []
        cmd_str = str(cmd_args) if cmd_args else ""
        if "pkill" in cmd_str:
            return mock_result
        return _ORIGINAL_SUBPROCESS_RUN(*args, **kwargs)

    monkeypatch.setattr(utils_module.subprocess, "run", safe_run)
    return mock_result


@pytest.fixture
def mock_codesign_success(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
    """Mock codesign to always succeed.

    This fixture allows tests to run on systems without codesign
    or to avoid the overhead of actual signing operations.

    Args:
        monkeypatch: Pytest's monkeypatch fixture.

    Returns:
        MagicMock instance for the mocked codesign result.
    """
    mock_result = MagicMock()
    mock_result.returncode = 0
    mock_result.stdout = ""
    mock_result.stderr = ""

    import electron_angle_patcher.utils as utils_module

    def mock_run(*args, **kwargs):
        cmd_args = args[0] if args else []
        cmd_str = str(cmd_args) if cmd_args else ""
        if "codesign" in cmd_str:
            return mock_result
        return _ORIGINAL_SUBPROCESS_RUN(*args, **kwargs)

    monkeypatch.setattr(utils_module.subprocess, "run", mock_run)
    return mock_result


@pytest.fixture
def mock_xattr_success(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
    """Mock xattr to always succeed.

    This fixture allows tests to run on systems without xattr
    or to avoid the overhead of actual attribute operations.

    Args:
        monkeypatch: Pytest's monkeypatch fixture.

    Returns:
        MagicMock instance for the mocked xattr result.
    """
    mock_result = MagicMock()
    mock_result.returncode = 0

    import electron_angle_patcher.utils as utils_module

    def mock_run(*args, **kwargs):
        cmd_args = args[0] if args else []
        cmd_str = str(cmd_args) if cmd_args else ""
        if "xattr" in cmd_str:
            return mock_result
        return _ORIGINAL_SUBPROCESS_RUN(*args, **kwargs)

    monkeypatch.setattr(utils_module.subprocess, "run", mock_run)
    return mock_result


@pytest.fixture
def mock_subprocess_all_safe(monkeypatch: pytest.MonkeyPatch) -> None:
    """Mock all dangerous subprocess calls (pkill, codesign, xattr).

    This is a convenience fixture that combines all subprocess mocking
    into one fixture to avoid conflicts.

    Args:
        monkeypatch: Pytest's monkeypatch fixture.

    Returns:
        None
    """
    import electron_angle_patcher.utils as utils_module

    pkill_mock = MagicMock()
    pkill_mock.returncode = 1
    pkill_mock.stdout = ""
    pkill_mock.stderr = ""

    codesign_mock = MagicMock()
    codesign_mock.returncode = 0
    codesign_mock.stdout = ""
    codesign_mock.stderr = ""

    xattr_mock = MagicMock()
    xattr_mock.returncode = 0

    def safe_run(*args, **kwargs):
        cmd_args = args[0] if args else []
        cmd_str = str(cmd_args) if cmd_args else ""
        if "pkill" in cmd_str:
            return pkill_mock
        elif "codesign" in cmd_str:
            return codesign_mock
        elif "xattr" in cmd_str:
            return xattr_mock
        return _ORIGINAL_SUBPROCESS_RUN(*args, **kwargs)

    monkeypatch.setattr(utils_module.subprocess, "run", safe_run)


@pytest.fixture
def mock_network_mounts_empty(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
    """Mock network mount detection to return empty set.

    This fixture speeds up tests by avoiding the slow mount command
    that would otherwise be called during scanning.

    Args:
        monkeypatch: Pytest's monkeypatch fixture.

    Returns:
        MagicMock instance for the mocked function.
    """
    mock_func = MagicMock(return_value=set())
    monkeypatch.setattr(
        "electron_angle_patcher.utils.get_network_mounts",
        mock_func,
    )
    return mock_func


@pytest.fixture
def safe_integration_environment(
    mock_pkill_safe: MagicMock,
    mock_network_mounts_empty: MagicMock,
) -> None:
    """Set up a safe integration test environment.

    This composite fixture sets up all the safety mocks for integration
    testing in one place.

    Args:
        mock_pkill_safe: Mock pkill fixture.
        mock_network_mounts_empty: Mock network mounts fixture.

    Returns:
        None
    """
    # All the work is done by the dependent fixtures
    return None


@pytest.fixture
def mock_subprocess_run_all_success(monkeypatch: pytest.MonkeyPatch) -> MagicMock:
    """Mock all subprocess.run calls to succeed.

    This is useful for testing on systems that may not have all
    the required commands (codesign, xattr, etc.).

    Args:
        monkeypatch: Pytest's monkeypatch fixture.

    Returns:
        MagicMock instance.
    """
    mock = MagicMock()
    mock.returncode = 0
    mock.stdout = ""
    mock.stderr = ""

    def run_mock(*args, **kwargs):
        return mock

    monkeypatch.setattr("subprocess.run", run_mock)
    return mock


@pytest.fixture(autouse=True)
def patch_cli_path_conversion(monkeypatch: pytest.MonkeyPatch) -> None:
    """Patch CLI to handle string to Path conversion.

    This works around a bug where _expand_path_if_exists returns strings
    but ElectronAnglePatcher expects Path objects.

    Args:
        monkeypatch: Pytest's monkeypatch fixture.
    """
    from pathlib import Path
    from electron_angle_patcher import cli, Config

    original_init = cli.ElectronAnglePatcher.__init__

    def patched_init(self, brave_path=None, chrome_path=None, lghub_path=None):
        # Use Config defaults for None values, then convert to Path if needed
        if brave_path is None:
            brave_path = Config.DEFAULT_BRAVE_PATH
        elif not isinstance(brave_path, Path):
            brave_path = Path(brave_path)

        if chrome_path is None:
            chrome_path = Config.DEFAULT_CHROME_PATH
        elif not isinstance(chrome_path, Path):
            chrome_path = Path(chrome_path)

        if lghub_path is None:
            lghub_path = Config.DEFAULT_LGHUB_PATH
        elif not isinstance(lghub_path, Path):
            lghub_path = Path(lghub_path)

        original_init(self, brave_path, chrome_path, lghub_path)

    monkeypatch.setattr(cli.ElectronAnglePatcher, "__init__", patched_init)
