"""File operations scoped to a workspace directory."""

import logging
import os
from dataclasses import dataclass

from src.models import Workspace

logger = logging.getLogger(__name__)


@dataclass
class FileEntry:
    """A single file or directory entry."""

    name: str
    path: str
    type: str  # "file" or "directory"
    size: int | None = None


class FileService:
    """Read and write files within a workspace directory."""

    def list_directory(
        self,
        workspace: Workspace,
        relative_path: str = "",
    ) -> list[FileEntry]:
        """List entries in a workspace directory.

        Args:
            workspace: The workspace to list files in.
            relative_path: Path relative to workspace root.

        Returns:
            List of file entries sorted by name (directories first).
        """
        abs_path = os.path.join(workspace.path, relative_path)
        abs_path = os.path.normpath(abs_path)

        # Security: ensure we stay within workspace
        if not abs_path.startswith(os.path.normpath(workspace.path)):
            raise ValueError("Path escapes workspace directory")

        if not os.path.exists(abs_path):
            return []

        entries = []
        for item in sorted(os.listdir(abs_path)):
            full = os.path.join(abs_path, item)
            rel = os.path.join(relative_path, item) if relative_path else item
            is_dir = os.path.isdir(full)
            size = os.path.getsize(full) if os.path.isfile(full) else None
            entries.append(
                FileEntry(
                    name=item,
                    path=rel.replace("\\", "/"),
                    type="directory" if is_dir else "file",
                    size=size,
                )
            )

        # Directories first, then files, both alphabetical
        entries.sort(key=lambda e: (0 if e.type == "directory" else 1, e.name.lower()))
        return entries

    def read_file(self, workspace: Workspace, relative_path: str) -> str:
        """Read a text file from the workspace.

        Args:
            workspace: The workspace to read from.
            relative_path: Path relative to workspace root.

        Returns:
            File contents as string.

        Raises:
            ValueError: If path escapes workspace or file is binary.
            FileNotFoundError: If file does not exist.
        """
        abs_path = self._resolve_path(workspace, relative_path)

        if not os.path.isfile(abs_path):
            raise FileNotFoundError(f"Not a file: {relative_path}")

        # Basic binary check — read first 8KB and look for null bytes
        with open(abs_path, "rb") as f:
            chunk = f.read(8192)
            if b"\x00" in chunk:
                raise ValueError("Binary files cannot be viewed")

        with open(abs_path, encoding="utf-8", errors="replace") as f:
            return f.read()

    def write_file(
        self,
        workspace: Workspace,
        relative_path: str,
        content: str,
    ) -> None:
        """Write a text file to the workspace.

        Args:
            workspace: The workspace to write to.
            relative_path: Path relative to workspace root.
            content: File contents.

        Raises:
            ValueError: If path escapes workspace.
        """
        abs_path = self._resolve_path(workspace, relative_path)
        os.makedirs(os.path.dirname(abs_path), exist_ok=True)

        with open(abs_path, "w", encoding="utf-8") as f:
            f.write(content)

        logger.info("Wrote file %s in workspace %s", relative_path, workspace.id)

    def _resolve_path(self, workspace: Workspace, relative_path: str) -> str:
        """Resolve a relative path to absolute, with security check."""
        abs_path = os.path.normpath(os.path.join(workspace.path, relative_path))
        workspace_root = os.path.normpath(workspace.path)

        if not abs_path.startswith(workspace_root):
            raise ValueError("Path escapes workspace directory")

        return abs_path
