"""REST API routes for document operations (U7).

Thin wrapper over DocumentService. All business logic lives in the
service layer — routes handle HTTP concerns (auth, file upload/download,
request validation).

Endpoints:
- POST /api/v1/documents/create — create a document from Markdown/JSON
- POST /api/v1/documents/upload-template — upload a .docx template
- GET  /api/v1/documents/conversation/{conversation_id} — list docs
- GET  /api/v1/documents/download/{doc_id} — download a document
"""

from __future__ import annotations

import hmac
import logging
import uuid
from typing import Any

from fastapi import (
    APIRouter,
    Depends,
    File,
    HTTPException,
    Request,
    Security,
    UploadFile,
)
from fastapi.responses import FileResponse
from fastapi.security import APIKeyHeader, APIKeyQuery
from pydantic import BaseModel

from agentkit.documents.service import DocumentService

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/documents", tags=["documents"])

MAX_TEMPLATE_SIZE = 50 * 1024 * 1024  # 50 MB

# ---------------------------------------------------------------------------
# Authentication (mirrors kb_management.py pattern)
# ---------------------------------------------------------------------------

_api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
_api_key_query = APIKeyQuery(name="api_key", auto_error=False)


async def _verify_api_key(
    request: Request,
    api_key_header: str | None = Security(_api_key_header),
    api_key_query: str | None = Security(_api_key_query),
) -> None:
    """Verify API key for document endpoints. Raises 401 if invalid."""
    configured: str | None = None
    if hasattr(request.app.state, "server_config") and request.app.state.server_config:
        configured = request.app.state.server_config.api_key
    if configured is None and hasattr(request.app.state, "api_key"):
        configured = request.app.state.api_key

    # No key configured → allow all (backwards compat, same as kb_management)
    if configured is None:
        return

    provided = api_key_header or api_key_query
    if not hmac.compare_digest((provided or "").encode(), configured.encode()):
        raise HTTPException(
            status_code=401,
            detail="Invalid or missing API key. Provide via X-API-Key header or api_key query.",
        )


def _get_document_service(request: Request) -> DocumentService:
    """Get DocumentService from app.state. Raises 500 if not initialized."""
    service = getattr(request.app.state, "document_service", None)
    if service is None:
        raise HTTPException(
            status_code=503,
            detail="Document service not available. Server may not have initialized it.",
        )
    return service


# ---------------------------------------------------------------------------
# Request / response models
# ---------------------------------------------------------------------------


class CreateDocumentRequest(BaseModel):
    format: str  # "word" | "excel" | "pdf"
    content: str
    conversation_id: str
    filename: str | None = None
    template: str | None = None  # template file path (stored_name in uploads)
    template_data: dict[str, Any] | None = None


class DocumentResponse(BaseModel):
    id: str
    filename: str
    format: str
    size: int
    conversation_id: str
    created_at: str
    download_url: str


# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------


@router.post("/create", dependencies=[Depends(_verify_api_key)])
async def create_document(
    body: CreateDocumentRequest,
    request: Request,
) -> dict[str, Any]:
    """Create a document from Markdown content or a template.

    Returns document metadata including a download URL.
    """
    service = _get_document_service(request)

    # If template is provided, resolve its path from stored_name
    template_path: str | None = None
    if body.template:
        # Security: prevent path traversal — resolved path must stay within upload_dir.
        # Also rejects null bytes and other invalid path characters (OS-level defense).
        upload_dir_resolved = service.upload_dir.resolve()
        try:
            candidate = (upload_dir_resolved / body.template).resolve()
            candidate.relative_to(upload_dir_resolved)
        except (ValueError, OSError) as exc:
            raise HTTPException(
                status_code=400,
                detail="Invalid template name: path traversal or invalid characters detected",
            ) from exc
        if not candidate.exists():
            raise HTTPException(status_code=404, detail=f"Template not found: {body.template}")
        template_path = str(candidate)

    try:
        meta = await service.create_document(
            format=body.format,
            content=body.content,
            conversation_id=body.conversation_id,
            filename=body.filename,
            template_path=template_path,
            template_data=body.template_data,
        )
        meta.download_url = f"/api/v1/documents/download/{meta.id}"
        return {"success": True, "document": meta.to_dict()}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e)) from e
    except FileNotFoundError as e:
        raise HTTPException(status_code=404, detail=str(e)) from e
    except Exception as e:
        logger.error(f"Document creation failed: {e}")
        raise HTTPException(status_code=500, detail="Document creation failed") from e


@router.post("/upload-template", dependencies=[Depends(_verify_api_key)])
async def upload_template(
    request: Request,
    file: UploadFile = File(...),
) -> dict[str, Any]:
    """Upload a .docx template file for later use in document creation.

    Returns the stored_name to use in the /create endpoint's template field.
    """
    if file.size is not None and file.size > MAX_TEMPLATE_SIZE:
        raise HTTPException(status_code=413, detail="Template exceeds 50 MB limit")

    if not (file.filename or "").lower().endswith(".docx"):
        raise HTTPException(status_code=400, detail="Only .docx templates are supported")

    service = _get_document_service(request)
    upload_dir = service._ensure_upload_dir()
    stored_name = f"template-{uuid.uuid4().hex}.docx"
    file_path = upload_dir / stored_name

    try:
        contents = await file.read()
        if len(contents) > MAX_TEMPLATE_SIZE:
            raise HTTPException(status_code=413, detail="Template exceeds 50 MB limit")
        file_path.write_bytes(contents)
    except HTTPException:
        raise
    except Exception as exc:
        logger.error(f"Failed to save template: {exc}")
        raise HTTPException(status_code=500, detail="Failed to save template") from exc
    finally:
        await file.close()

    return {
        "success": True,
        "stored_name": stored_name,
        "filename": file.filename,
        "size": file_path.stat().st_size,
        "message": f"Template uploaded. Use '{stored_name}' as the template field in /create.",
    }


@router.get(
    "/conversation/{conversation_id}",
    dependencies=[Depends(_verify_api_key)],
)
async def list_conversation_documents(
    conversation_id: str,
    request: Request,
) -> dict[str, Any]:
    """List all documents for a conversation, newest first."""
    service = _get_document_service(request)
    docs = await service.get_conversation_documents(conversation_id)
    for doc in docs:
        doc.download_url = f"/api/v1/documents/download/{doc.id}"
    return {
        "success": True,
        "conversation_id": conversation_id,
        "documents": [d.to_dict() for d in docs],
        "count": len(docs),
    }


@router.get("/download/{doc_id}", dependencies=[Depends(_verify_api_key)])
async def download_document(
    doc_id: str,
    request: Request,
) -> FileResponse:
    """Download a document by its ID."""
    service = _get_document_service(request)

    # Verify the document exists in metadata
    meta = await service.get_document(doc_id)
    if meta is None:
        raise HTTPException(status_code=404, detail="Document not found")

    # Find the file on disk
    file_path = service.get_download_path(doc_id)
    if file_path is None or not file_path.exists():
        raise HTTPException(status_code=404, detail="Document file not found on disk")

    return FileResponse(
        path=str(file_path),
        filename=meta.filename,
        media_type="application/octet-stream",
    )
