"""Filesystem replay guard for AutoDebug.

This module provides a guard that fails replay if uncaptured files are read.
For now, it warns but doesn't block - full file replay is a future enhancement.
"""

from __future__ import annotations

import builtins
import os
import warnings
from pathlib import Path
from typing import Callable, Dict, List, Optional, Set

from autodebug_replay.transcripts import TranscriptIndex, ReplayDivergence

# Original functions
_original_open: Optional[Callable] = None
_original_path_read_text: Optional[Callable] = None
_original_path_read_bytes: Optional[Callable] = None
_original_path_open: Optional[Callable] = None

# Set of file paths that were captured
_captured_files: Set[str] = set()

# Whether to enforce strict file checking (fail on uncaptured reads)
_strict_mode: bool = False

# Files that are always allowed (system files, etc.)
ALWAYS_ALLOWED = [
    "/etc/",
    "/proc/",
    "/sys/",
    "/dev/",
    "/usr/",
    "/lib/",
    "/bin/",
    "site-packages",
    "dist-packages",
    "__pycache__",
    ".pyc",
    "/repro/",  # Allow reading from harness directory
]


def _is_always_allowed(path: str) -> bool:
    """Check if a path is always allowed to be read."""
    for pattern in ALWAYS_ALLOWED:
        if pattern in path:
            return True
    return False


def _check_file_read(path: str) -> None:
    """Check if a file read is allowed during replay."""
    if _is_always_allowed(path):
        return

    abs_path = os.path.abspath(path)

    if abs_path not in _captured_files and _strict_mode:
        raise ReplayDivergence("UNCAPTURED_FILE_READ", {
            "path": path,
            "abs_path": abs_path,
            "message": f"File was not captured during recording: {path}",
        })

    # In non-strict mode, just warn
    if abs_path not in _captured_files:
        warnings.warn(
            f"Reading uncaptured file during replay: {path}",
            RuntimeWarning,
            stacklevel=3,
        )


def _patched_open(file, mode='r', *args, **kwargs):
    """Patched builtins.open that checks captured files."""
    if 'r' in mode or mode == '':
        _check_file_read(str(file))
    return _original_open(file, mode, *args, **kwargs)


def _patched_path_read_text(self, *args, **kwargs):
    """Patched Path.read_text that checks captured files."""
    _check_file_read(str(self))
    return _original_path_read_text(self, *args, **kwargs)


def _patched_path_read_bytes(self, *args, **kwargs):
    """Patched Path.read_bytes that checks captured files."""
    _check_file_read(str(self))
    return _original_path_read_bytes(self, *args, **kwargs)


def _patched_path_open(self, mode='r', *args, **kwargs):
    """Patched Path.open that checks captured files."""
    if 'r' in mode or mode == '':
        _check_file_read(str(self))
    return _original_path_open(self, mode, *args, **kwargs)


def install(captured_files: List[str], strict: bool = False) -> None:
    """Install file replay guard.

    Args:
        captured_files: List of file paths that were captured during recording
        strict: If True, fail on uncaptured file reads. If False, just warn.
    """
    global _original_open, _original_path_read_text, _original_path_read_bytes
    global _original_path_open, _captured_files, _strict_mode

    _captured_files = set(captured_files)
    _strict_mode = strict

    # Patch builtins.open
    _original_open = builtins.open
    builtins.open = _patched_open

    # Patch pathlib.Path methods
    _original_path_read_text = Path.read_text
    _original_path_read_bytes = Path.read_bytes
    _original_path_open = Path.open

    Path.read_text = _patched_path_read_text
    Path.read_bytes = _patched_path_read_bytes
    Path.open = _patched_path_open


def uninstall() -> None:
    """Restore original file functions."""
    global _original_open, _original_path_read_text, _original_path_read_bytes, _original_path_open

    if _original_open:
        builtins.open = _original_open
    if _original_path_read_text:
        Path.read_text = _original_path_read_text
    if _original_path_read_bytes:
        Path.read_bytes = _original_path_read_bytes
    if _original_path_open:
        Path.open = _original_path_open
