"""
RBSP File System Integration.

Wraps standard file operations so that every create, modify, delete, and
move automatically updates the RBSP index.  Search is delegated to the
RBSP runtime engine.

Works on Windows, Linux, and macOS via the platform abstraction layer.
"""

from __future__ import annotations

import shutil
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional

from rbsp.runtime.engine import RBSPEngine, Result
from rbsp.runtime.platform import FileSystem


@dataclass
class FileOperation:
    """Record of a file operation performed through the RBSP file system."""

    operation: str       # "read", "write", "delete", "move"
    path: str
    timestamp: float = field(default_factory=time.time)
    success: bool = True
    detail: str = ""


class RBSPFileSystem:
    """File system wrapper with automatic RBSP indexing.

    Every write, delete, or move operation triggers an incremental index
    update so that search results are always current.

    Parameters
    ----------
    root:
        Project root directory.  An RBSP index is initialised here if
        one does not already exist.
    auto_index:
        If True (default), file mutations automatically update the index.

    Example
    -------
    >>> fs = RBSPFileSystem("/my/project")
    >>> fs.write("src/auth.py", "def login(): ...")
    >>> results = fs.search("login")
    """

    def __init__(self, root: str = ".", auto_index: bool = True) -> None:
        self._root = Path(root).resolve()
        self._auto_index = auto_index
        self._fs = FileSystem()
        self._history: List[FileOperation] = []

        # Ensure engine is initialised for this root
        self._engine = RBSPEngine.get_instance()
        if not self._engine.is_initialized:
            self._engine.init(str(self._root))

    @property
    def root(self) -> Path:
        """Project root directory."""
        return self._root

    @property
    def history(self) -> List[FileOperation]:
        """Return the operation history log."""
        return list(self._history)

    # ------------------------------------------------------------------
    # Core file operations
    # ------------------------------------------------------------------

    def read(self, path: str, encoding: str = "utf-8") -> str:
        """Read a text file relative to the project root.

        Parameters
        ----------
        path:
            Relative or absolute path.  Relative paths are resolved
            against the project root.
        encoding:
            Text encoding (default ``utf-8``).

        Returns
        -------
        str
            File contents, or empty string on error.
        """
        full = self._resolve(path)
        op = FileOperation(operation="read", path=str(full))
        try:
            content = self._fs.read_text(full, encoding=encoding)
            op.success = True
        except Exception as exc:
            content = ""
            op.success = False
            op.detail = str(exc)
        self._history.append(op)
        return content

    def write(self, path: str, content: str, encoding: str = "utf-8") -> bool:
        """Write text to a file and update the RBSP index.

        Creates parent directories as needed.  If *auto_index* is
        enabled, the file is re-indexed immediately after writing.

        Returns True on success.
        """
        full = self._resolve(path)
        op = FileOperation(operation="write", path=str(full))
        try:
            self._fs.write_text(full, content, encoding=encoding)
            op.success = True
            if self._auto_index:
                self._reindex_file(full)
        except Exception as exc:
            op.success = False
            op.detail = str(exc)
        self._history.append(op)
        return op.success

    def write_bytes(self, path: str, data: bytes) -> bool:
        """Write raw bytes to a file and update the index."""
        full = self._resolve(path)
        op = FileOperation(operation="write", path=str(full))
        try:
            self._fs.write_bytes(full, data)
            op.success = True
            if self._auto_index:
                self._reindex_file(full)
        except Exception as exc:
            op.success = False
            op.detail = str(exc)
        self._history.append(op)
        return op.success

    def delete(self, path: str) -> bool:
        """Delete a file and remove it from the RBSP index.

        Returns True if the file was removed.
        """
        full = self._resolve(path)
        op = FileOperation(operation="delete", path=str(full))
        try:
            removed = self._fs.remove(full)
            op.success = removed
            if removed and self._auto_index:
                self._remove_from_index(full)
        except Exception as exc:
            op.success = False
            op.detail = str(exc)
        self._history.append(op)
        return op.success

    def move(self, src: str, dst: str) -> bool:
        """Move / rename a file and update the RBSP index.

        The old path is removed from the index and the new path is
        indexed.

        Returns True on success.
        """
        full_src = self._resolve(src)
        full_dst = self._resolve(dst)
        op = FileOperation(operation="move", path=f"{full_src} -> {full_dst}")
        try:
            full_dst.parent.mkdir(parents=True, exist_ok=True)
            shutil.move(str(full_src), str(full_dst))
            op.success = True
            if self._auto_index:
                self._remove_from_index(full_src)
                self._reindex_file(full_dst)
        except Exception as exc:
            op.success = False
            op.detail = str(exc)
        self._history.append(op)
        return op.success

    def exists(self, path: str) -> bool:
        """Check whether a file exists."""
        return self._resolve(path).exists()

    def list_files(self, path: str = ".", extensions: Optional[set] = None) -> List[str]:
        """List files under *path*, returning paths relative to root."""
        target = self._resolve(path)
        result: List[str] = []
        for fp in self._fs.walk_files(target, extensions=extensions):
            result.append(self._fs.relative_to(fp, self._root))
        return result

    # ------------------------------------------------------------------
    # Search
    # ------------------------------------------------------------------

    def search(self, query: str, **kwargs: Any) -> List[Result]:
        """Search the RBSP index.

        Parameters
        ----------
        query:
            Natural-language or keyword search string.
        **kwargs:
            Forwarded to :meth:`RBSPEngine.resolve`.

        Returns
        -------
        list[Result]
            Ranked search results.
        """
        return self._engine.resolve(query, scope=str(self._root), **kwargs)

    # ------------------------------------------------------------------
    # Bulk operations
    # ------------------------------------------------------------------

    def index_all(self, force: bool = False) -> Dict[str, Any]:
        """Trigger a full index of the project root.

        Returns a dict with indexing statistics.
        """
        stats = self._engine.index(str(self._root), force=force)
        return {
            "files_indexed": stats.files_indexed,
            "files_skipped": stats.files_skipped,
            "total_size": stats.total_size,
            "duration": stats.duration,
        }

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _resolve(self, path: str) -> Path:
        """Resolve a user path relative to root."""
        p = Path(path)
        if p.is_absolute():
            return p.resolve()
        return (self._root / p).resolve()

    def _reindex_file(self, full_path: Path) -> None:
        """Re-index a single file via the engine (incremental)."""
        # The engine's index method handles incremental updates.
        # We index the parent directory with a narrow scope to pick up
        # just this file efficiently.
        try:
            self._engine.index(str(full_path.parent))
        except Exception:
            pass

    def _remove_from_index(self, full_path: Path) -> None:
        """Remove a file from the engine's in-memory index."""
        key = str(full_path)
        # Access engine internals to remove the entry directly
        self._engine._index.pop(key, None)
