"""Document upload, listing, editing, and deletion for user graph documents.

Source of truth is PostgreSQL. Content is synced to OpenSearch for search.
"""

import logging

from fastapi import APIRouter, Depends, HTTPException
from starlette import status as http_status

from robosystems.database import SessionFactory
from robosystems.middleware.auth.dependencies import get_current_user_with_graph
from robosystems.models.api.common import RESOURCE_ERROR_RESPONSES
from robosystems.models.api.search import (
  DocumentDetailResponse,
  DocumentListItem,
  DocumentListResponse,
  DocumentUpdateRequest,
  DocumentUploadRequest,
  DocumentUploadResponse,
)
from robosystems.models.core import User
from robosystems.models.core.document import Document
from robosystems.operations.document_service import DocumentService

logger = logging.getLogger(__name__)

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


def _block_shared_repository(graph_id: str) -> None:
  """Block document operations on shared repository graphs."""
  from robosystems.config.shared_repositories import (
    is_shared_repository_or_subgraph,
  )

  if is_shared_repository_or_subgraph(graph_id):
    raise HTTPException(
      status_code=http_status.HTTP_403_FORBIDDEN,
      detail="Document operations are not allowed on shared repository graphs",
    )


def _enforce_graph_access(graph_id: str, require_write: bool = False) -> None:
  """Check graph lifecycle and subscription status.

  Wraps require_graph_access with a fresh session. Raises HTTPException
  if the graph is suspended, deprovisioned, or subscription is non-active.
  """
  from robosystems.middleware.billing.enforcement import require_graph_access

  session = SessionFactory()
  try:
    require_graph_access(graph_id, session, require_write=require_write)
  finally:
    session.close()


def _resolve_tier(graph_id: str, session) -> str:
  """Resolve the subscription tier for a graph using an existing session."""
  from robosystems.models.core.graph import Graph

  try:
    graph = Graph.get_by_id(graph_id, session)
    if graph is None or graph.graph_tier is None:
      raise HTTPException(500, "Unable to determine subscription tier")
    return graph.graph_tier
  except HTTPException:
    raise
  except Exception as e:
    logger.error(f"Tier lookup failed for {graph_id}: {e}")
    raise HTTPException(500, "Unable to determine subscription tier")


def _document_to_list_item(doc: Document) -> DocumentListItem:
  """Convert a Document model to a DocumentListItem."""
  return DocumentListItem(
    id=doc.id,
    document_title=doc.title,
    section_count=doc.sections_indexed,
    source_type=doc.source_type,
    folder=doc.folder,
    tags=doc.tags,
    created_at=doc.created_at.isoformat() if doc.created_at else "",
    updated_at=doc.updated_at.isoformat() if doc.updated_at else "",
  )


def _document_to_detail(doc: Document) -> DocumentDetailResponse:
  """Convert a Document model to a DocumentDetailResponse."""
  return DocumentDetailResponse(
    id=doc.id,
    graph_id=doc.graph_id,
    user_id=doc.user_id,
    title=doc.title,
    content=doc.content,
    tags=doc.tags,
    folder=doc.folder,
    external_id=doc.external_id,
    source_type=doc.source_type,
    source_provider=doc.source_provider,
    sections_indexed=doc.sections_indexed,
    created_at=doc.created_at.isoformat() if doc.created_at else "",
    updated_at=doc.updated_at.isoformat() if doc.updated_at else "",
  )


@router.get(
  "",
  summary="List Documents",
  operation_id="list_documents",
  responses={**RESOURCE_ERROR_RESPONSES},
)
async def list_documents(
  graph_id: str,
  source_type: str | None = None,
  current_user: User = Depends(get_current_user_with_graph),
) -> DocumentListResponse:
  _block_shared_repository(graph_id)
  _enforce_graph_access(graph_id)
  session = SessionFactory()
  try:
    service = DocumentService(session)
    docs = service.list_documents(graph_id, source_type)
    return DocumentListResponse(
      total=len(docs),
      documents=[_document_to_list_item(d) for d in docs],
      graph_id=graph_id,
    )
  finally:
    session.close()


@router.get(
  "/{document_id}",
  summary="Get Document",
  operation_id="get_document",
  responses={**RESOURCE_ERROR_RESPONSES},
)
async def get_document(
  graph_id: str,
  document_id: str,
  current_user: User = Depends(get_current_user_with_graph),
) -> DocumentDetailResponse:
  _block_shared_repository(graph_id)
  _enforce_graph_access(graph_id)
  session = SessionFactory()
  try:
    service = DocumentService(session)
    doc = service.get_document(graph_id, document_id)
    if doc is None:
      raise HTTPException(status_code=404, detail="Document not found")
    return _document_to_detail(doc)
  finally:
    session.close()


@router.post(
  "",
  summary="Upload Document",
  description="Stored in PostgreSQL, synced to OpenSearch for search. Not allowed on shared repositories.",
  operation_id="upload_document",
  responses={**RESOURCE_ERROR_RESPONSES},
)
async def upload_document(
  graph_id: str,
  request: DocumentUploadRequest,
  current_user: User = Depends(get_current_user_with_graph),
) -> DocumentUploadResponse:
  _block_shared_repository(graph_id)
  _enforce_graph_access(graph_id, require_write=True)
  session = SessionFactory()
  try:
    tier = _resolve_tier(graph_id, session)
    service = DocumentService(session)
    _doc, response = service.create_document(
      graph_id=graph_id,
      user_id=current_user.id,
      request=request,
      tier=tier,
    )
    return response
  except ValueError as e:
    raise HTTPException(
      status_code=http_status.HTTP_422_UNPROCESSABLE_ENTITY,
      detail=str(e),
    )
  finally:
    session.close()


@router.put(
  "/{document_id}",
  summary="Update Document",
  description="Updates content and/or metadata. Re-syncs to OpenSearch.",
  operation_id="update_document",
  responses={**RESOURCE_ERROR_RESPONSES},
)
async def update_document(
  graph_id: str,
  document_id: str,
  request: DocumentUpdateRequest,
  current_user: User = Depends(get_current_user_with_graph),
) -> DocumentUploadResponse:
  _block_shared_repository(graph_id)
  _enforce_graph_access(graph_id, require_write=True)
  session = SessionFactory()
  try:
    service = DocumentService(session)
    # Only pass fields that were explicitly provided in the request
    kwargs: dict = {}
    if "title" in request.model_fields_set:
      kwargs["title"] = request.title
    if "content" in request.model_fields_set:
      kwargs["content"] = request.content
    if "tags" in request.model_fields_set:
      kwargs["tags"] = request.tags
    if "folder" in request.model_fields_set:
      kwargs["folder"] = request.folder
    _doc, response = service.update_document(
      graph_id=graph_id,
      document_id=document_id,
      **kwargs,
    )
    return response
  except KeyError as e:
    raise HTTPException(status_code=404, detail=str(e))
  except ValueError as e:
    raise HTTPException(
      status_code=http_status.HTTP_422_UNPROCESSABLE_ENTITY,
      detail=str(e),
    )
  finally:
    session.close()


@router.delete(
  "/{document_id}",
  summary="Delete Document",
  operation_id="delete_document",
  status_code=http_status.HTTP_204_NO_CONTENT,
  responses={**RESOURCE_ERROR_RESPONSES},
)
async def delete_document(
  graph_id: str,
  document_id: str,
  current_user: User = Depends(get_current_user_with_graph),
) -> None:
  _block_shared_repository(graph_id)
  _enforce_graph_access(graph_id, require_write=True)
  session = SessionFactory()
  try:
    service = DocumentService(session)
    deleted = service.delete_document(graph_id, document_id)
    if not deleted:
      raise HTTPException(status_code=404, detail="Document not found")
  finally:
    session.close()
