"""
ArangoDB MCP Server - Backup Utilities

This module provides functionality to backup ArangoDB collections to JSON files.
Supports exporting single or multiple collections with optional document limits.

Functions:
- validate_output_directory() - Validate/sanitize output directory for backups
- backup_collections_to_dir() - Export collections to JSON files in a directory
"""

from __future__ import annotations

import json
import os
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional

from arango.database import StandardDatabase


def validate_output_directory(output_dir: str) -> str:
    """Validate and sanitize output directory using safe sandboxing approach.

    Uses pathlib.Path.resolve() to normalize paths and prefix checking against
    allowed root directories to prevent path traversal attacks.

    Args:
        output_dir: The requested output directory path

    Returns:
        Validated and normalized absolute path

    Raises:
        ValueError: If the path is invalid or outside allowed directories
    """
    import tempfile

    # Convert to Path object and resolve to normalize (handles .. components safely)
    try:
        requested_path = Path(output_dir).resolve()
    except (OSError, ValueError) as e:
        raise ValueError(f"Invalid path: {e}")

    # Define allowed root directories
    allowed_roots = [
        Path.cwd().resolve(),  # Current working directory
        Path(tempfile.gettempdir()).resolve(),  # System temp directory
    ]

    # Add user-specific temp directories if they exist
    user_temp_dirs = []
    if os.name == 'nt':  # Windows
        if 'LOCALAPPDATA' in os.environ:
            user_temp_dirs.append(Path(os.environ['LOCALAPPDATA']) / 'Temp')
        if 'TEMP' in os.environ:
            user_temp_dirs.append(Path(os.environ['TEMP']))
    else:  # Unix-like
        if 'TMPDIR' in os.environ:
            user_temp_dirs.append(Path(os.environ['TMPDIR']))
        user_temp_dirs.extend([Path('/tmp'), Path('/var/tmp')])

    # Add existing user temp directories to allowed roots
    for temp_dir in user_temp_dirs:
        try:
            if temp_dir.exists():
                allowed_roots.append(temp_dir.resolve())
        except (OSError, ValueError):
            continue  # Skip invalid temp directories

    # Check if the resolved path is within any allowed root
    for allowed_root in allowed_roots:
        try:
            # This will succeed if requested_path is within allowed_root
            requested_path.relative_to(allowed_root)
            return str(requested_path)
        except ValueError:
            continue  # Not within this root, try next

    # If we get here, the path is not within any allowed root
    allowed_paths = [str(root) for root in allowed_roots]
    raise ValueError(
        f"Output directory '{output_dir}' is outside allowed directories. "
        f"Allowed roots: {allowed_paths}"
    )


def backup_collections_to_dir(
    db: StandardDatabase,
    output_dir: Optional[str] = None,
    collections: Optional[List[str]] = None,
    doc_limit: Optional[int] = None,
) -> Dict[str, object]:
    """
    Dump selected (or all non-system) collections to JSON files in output_dir.

    Each collection is written as a JSON array of documents: <name>.json
    Returns a report dict with written file paths and record counts.
    """
    # Determine target directory
    if output_dir is None or not output_dir.strip():
        ts = datetime.now().strftime("%Y%m%d_%H%M%S")
        output_dir = os.path.join("backups", ts)

    # Validate and sanitize the output directory
    try:
        output_dir = validate_output_directory(output_dir)
    except ValueError as e:
        raise ValueError(f"Invalid output directory: {e}")

    os.makedirs(output_dir, exist_ok=True)

    # Resolve which collections to export
    all_cols = [c["name"] for c in db.collections() if not c.get("isSystem")]
    target_cols = collections if collections else all_cols

    written: List[Dict[str, object]] = []

    for name in target_cols:
        if name not in all_cols:
            # Skip unknown/non-existing or system collections silently
            continue

        col = db.collection(name)
        path = os.path.join(output_dir, f"{name}.json")

        # Use streaming approach to handle large collections
        try:
            cursor = col.all()
            count = 0

            with open(path, "w", encoding="utf-8") as f:
                f.write('[')
                first_doc = True

                for i, doc in enumerate(cursor):
                    if doc_limit is not None and i >= doc_limit:
                        break

                    if not first_doc:
                        f.write(',')
                    f.write('\n  ')
                    json.dump(doc, f, ensure_ascii=False)
                    first_doc = False
                    count += 1

                f.write('\n]')

            written.append({"collection": name, "path": path, "count": count})

        except Exception as e:
            # Log error but continue with other collections
            written.append({
                "collection": name,
                "path": path,
                "count": 0,
                "error": str(e)
            })
        finally:
            # Ensure cursor is closed if it exists
            if 'cursor' in locals() and hasattr(cursor, 'close'):
                try:
                    cursor.close()
                except Exception:
                    pass  # Ignore cleanup errors

    return {
        "output_dir": output_dir,
        "written": written,
        "total_collections": len(written),
        "total_documents": sum(int(x["count"]) for x in written),
    }
