"""Filesystem read interception for AutoDebug capture."""

from __future__ import annotations

import builtins
import hashlib
import os
import threading
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Set

from autodebug_probe.recorder import get_recorder

# Original functions to restore
_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

# Track which files have been recorded to avoid duplicates
_recorded_files: Set[str] = set()
_recorded_files_lock = threading.Lock()

# Files to exclude from capture
EXCLUDE_PATTERNS = [
    # System files
    "/etc/",
    "/proc/",
    "/sys/",
    "/dev/",
    "/var/",
    "/usr/",
    "/lib/",
    "/bin/",
    # Python internals
    "__pycache__",
    ".pyc",
    "site-packages",
    "dist-packages",
    # Version control
    ".git/",
    ".hg/",
    ".svn/",
    # IDE/Editor
    ".vscode/",
    ".idea/",
    # AutoDebug internal
    ".autodebug/",
    # Secrets (never capture)
    ".pem",
    ".key",
    "/secrets/",
    ".env",  # Capture env vars separately
]

# Files to always include (config files)
INCLUDE_PATTERNS = [
    ".yaml",
    ".yml",
    ".json",
    ".toml",
    ".ini",
    ".cfg",
    ".conf",
    "config/",
    "settings/",
]

# Maximum file size to capture content (100KB)
MAX_FILE_SIZE = 100 * 1024


def _should_capture(path: str) -> bool:
    """Determine if a file read should be captured."""
    path_str = str(path)

    # Never capture excluded patterns
    for pattern in EXCLUDE_PATTERNS:
        if pattern in path_str:
            return False

    # Always capture included patterns
    for pattern in INCLUDE_PATTERNS:
        if pattern in path_str:
            return True

    # Default: capture files in project directory (relative paths or cwd)
    if not os.path.isabs(path_str):
        return True

    cwd = os.getcwd()
    if path_str.startswith(cwd):
        return True

    return False


def _hash_file(path: Path) -> Optional[str]:
    """Calculate SHA256 hash of file contents."""
    try:
        if not path.exists() or not path.is_file():
            return None
        if path.stat().st_size > MAX_FILE_SIZE:
            return None  # Too large to hash
        content = path.read_bytes()
        return hashlib.sha256(content).hexdigest()
    except (OSError, PermissionError):
        return None


def _record_file_read(path: str, category: str = "file") -> None:
    """Record a file read event."""
    # Convert to absolute path for deduplication
    abs_path = os.path.abspath(path)

    # Thread-safe check-and-add for _recorded_files
    with _recorded_files_lock:
        if abs_path in _recorded_files:
            return
        if not _should_capture(path):
            return
        _recorded_files.add(abs_path)

    path_obj = Path(abs_path)

    # Resolve symlinks
    try:
        real_path = path_obj.resolve()
        is_symlink = path_obj.is_symlink()
    except OSError:
        real_path = path_obj
        is_symlink = False

    # Get file info
    exists = path_obj.exists()
    file_hash = _hash_file(real_path) if exists else None

    try:
        size = path_obj.stat().st_size if exists else None
    except OSError:
        size = None

    # Record the file read
    get_recorder().record_event({
        "type": "FILE_READ",
        "path": path,
        "abs_path": abs_path,
        "real_path": str(real_path) if is_symlink else None,
        "is_symlink": is_symlink,
        "exists": exists,
        "hash": file_hash,
        "size": size,
        "category": category,
    })


def _patched_open(file, mode='r', *args, **kwargs):
    """Patched builtins.open that records file reads."""
    # Only record read operations
    if 'r' in mode or mode == '':
        _record_file_read(str(file), category="open")
    return _original_open(file, mode, *args, **kwargs)


def _patched_path_read_text(self, *args, **kwargs):
    """Patched Path.read_text that records file reads."""
    _record_file_read(str(self), category="pathlib")
    return _original_path_read_text(self, *args, **kwargs)


def _patched_path_read_bytes(self, *args, **kwargs):
    """Patched Path.read_bytes that records file reads."""
    _record_file_read(str(self), category="pathlib")
    return _original_path_read_bytes(self, *args, **kwargs)


def _patched_path_open(self, mode='r', *args, **kwargs):
    """Patched Path.open that records file reads."""
    if 'r' in mode or mode == '':
        _record_file_read(str(self), category="pathlib")
    return _original_path_open(self, mode, *args, **kwargs)


def install() -> None:
    """Install file read patches."""
    global _original_open, _original_path_read_text, _original_path_read_bytes, _original_path_open

    # 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


def get_recorded_files() -> List[str]:
    """Get list of recorded file paths."""
    with _recorded_files_lock:
        return list(_recorded_files)
