"""Code writer — writes generated code files to the project safely."""

from __future__ import annotations

import logging
import shutil
from datetime import datetime
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)

# Max files an agent can write in one execution
MAX_FILES_PER_EXECUTION = 50

# Max file size an agent can write (500KB)
MAX_WRITE_SIZE = 512_000

# Files that should never be overwritten by agents
PROTECTED_FILES = {
    ".env",
    ".env.local",
    ".env.production",
    ".gitignore",
    ".git/config",
    "rigovo.yml",
    "rigovo.yml.example",
}


class CodeWriter:
    """
    Writes code files generated by agents to the project.

    Safety features:
    - Path traversal prevention (sandboxed to project root)
    - Protected file list (won't overwrite .env, .git/config, etc.)
    - Backup before overwrite (stored in .rigovo/backups/)
    - File size limits
    - File count limits per execution
    """

    def __init__(self, project_root: Path) -> None:
        self._root = project_root.resolve()
        self._backup_dir = self._root / ".rigovo" / "backups"
        self._files_written: int = 0

    def _safe_path(self, relative_path: str) -> Path:
        """Resolve and validate a path is within the project root."""
        resolved = (self._root / relative_path).resolve()
        try:
            resolved.relative_to(self._root)
        except ValueError:
            raise PermissionError(f"Path traversal detected: {relative_path} escapes project root")
        return resolved

    def write_file(self, path: str, content: str) -> dict[str, Any]:
        """
        Write content to a file. Creates parent directories if needed.

        Returns {path, created, backed_up, size} on success.
        Returns {error} on failure.
        """
        if self._files_written >= MAX_FILES_PER_EXECUTION:
            return {"error": f"File write limit reached ({MAX_FILES_PER_EXECUTION} per execution)"}

        if len(content.encode("utf-8")) > MAX_WRITE_SIZE:
            return {
                "error": f"Content too large ({len(content)} chars, max {MAX_WRITE_SIZE} bytes)"
            }

        # Check protected files
        normalized = path.replace("\\", "/").strip("/")
        if normalized in PROTECTED_FILES:
            return {"error": f"Protected file cannot be overwritten: {path}"}

        try:
            safe = self._safe_path(path)
        except PermissionError as e:
            return {"error": str(e)}

        created = not safe.exists()
        backed_up = False

        # Backup existing file before overwrite
        if safe.exists():
            try:
                self._backup_file(safe, path)
                backed_up = True
            except Exception as e:
                logger.warning("Failed to backup %s: %s", path, e)

        # Create parent directories
        try:
            safe.parent.mkdir(parents=True, exist_ok=True)
            safe.write_text(content, encoding="utf-8")
            self._files_written += 1

            logger.info(
                "Wrote %s (%d bytes, %s)",
                path,
                len(content.encode("utf-8")),
                "created" if created else "overwritten",
            )

            return {
                "path": path,
                "created": created,
                "backed_up": backed_up,
                "size": len(content.encode("utf-8")),
            }
        except Exception as e:
            return {"error": f"Failed to write {path}: {e}"}

    def _backup_file(self, filepath: Path, relative_path: str) -> None:
        """Create a timestamped backup of a file before overwriting."""
        self._backup_dir.mkdir(parents=True, exist_ok=True)
        timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
        safe_name = relative_path.replace("/", "__").replace("\\", "__")
        backup_path = self._backup_dir / f"{timestamp}__{safe_name}"
        shutil.copy2(filepath, backup_path)

    def reset_counter(self) -> None:
        """Reset the per-execution file write counter."""
        self._files_written = 0
