"""Document CRUD endpoints."""
from __future__ import annotations

import uuid
from typing import Optional

import structlog as _structlog

_log = _structlog.get_logger(__name__)

from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from fastapi.responses import JSONResponse
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from db.models import Document, Folder, Share, User
from deps.auth import get_regular_user
from deps.db import get_db
from deps.utils import get_client_ip
from services import classifier, storage
from services.audit import write_audit_log
from services.rate_limiting import account_limiter
from storage import get_storage_backend_for_document as _get_storage_backend_for_document
from tasks.document_tasks import extract_and_classify

from api.documents.shared import DocumentPatch, get_accessible_document, get_owned_document

router = APIRouter()


async def _shared_document_ids(session: AsyncSession, user_id: uuid.UUID) -> set[uuid.UUID]:
    result = await session.execute(select(Share.document_id).where(Share.owner_id == user_id))
    return {row[0] for row in result.fetchall()}


async def _decorate_shared_flags(
    session: AsyncSession,
    user_id: uuid.UUID,
    items: list[dict],
) -> list[dict]:
    shared_ids = await _shared_document_ids(session, user_id)
    for item in items:
        try:
            doc_id = uuid.UUID(item.get("id", ""))
        except (TypeError, ValueError):
            item["is_shared"] = False
        else:
            item["is_shared"] = doc_id in shared_ids
    return items


@account_limiter.limit("100/minute")
async def list_documents(
    request: Request,
    topic: Optional[str] = Query(None),
    page: int = Query(1, ge=1),
    per_page: int = Query(20, ge=1, le=100),
    sort: str = Query("date"),
    order: str = Query("desc"),
    folder_id: Optional[str] = Query(None),
    q: Optional[str] = Query(None),
    session: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_regular_user),
):
    """List documents with optional sort, folder filter, and full-text search."""
    request.state.current_user = current_user
    if folder_id is None and q is None and sort == "date" and order == "desc":
        docs = await storage.list_metadata(session, user_id=current_user.id, topic=topic)
        total = len(docs)
        start = (page - 1) * per_page
        items = await _decorate_shared_flags(
            session,
            current_user.id,
            docs[start : start + per_page],
        )
        return {"items": items, "total": total, "page": page, "per_page": per_page}

    from db.models import DocumentTopic, Topic  # noqa: PLC0415 (avoid circular at module top)

    stmt = select(Document).where(Document.user_id == current_user.id)

    if topic is not None:
        stmt = (
            stmt.join(DocumentTopic, DocumentTopic.document_id == Document.id)
            .join(Topic, Topic.id == DocumentTopic.topic_id)
            .where(Topic.name == topic)
        )

    if folder_id is not None:
        try:
            folder_uuid = uuid.UUID(folder_id)
        except ValueError:
            raise HTTPException(status_code=404, detail="Folder not found")
        stmt = stmt.where(Document.folder_id == folder_uuid)

    sort_col = Document.created_at  # default: date
    if sort == "name":
        sort_col = Document.filename
    elif sort == "size":
        sort_col = Document.size_bytes

    order_fn = sort_col.asc if order == "asc" else sort_col.desc
    stmt = stmt.order_by(order_fn())

    fts_requested = q is not None and len(q) >= 2
    if fts_requested:
        fts_stmt = stmt.where(
            func.to_tsvector("english", func.coalesce(Document.extracted_text, "")).op("@@")(
                func.plainto_tsquery("english", q)
            )
        )
        try:
            result = await session.execute(fts_stmt)
        except Exception:
            result = await session.execute(stmt)
    else:
        result = await session.execute(stmt)
    docs_orm = result.scalars().all()

    all_items = []
    shared_ids = await _shared_document_ids(session, current_user.id)
    for doc in docs_orm:
        from services.storage import _doc_to_dict, _load_topic_names  # noqa: PLC0415

        topic_names = await _load_topic_names(session, doc.id)
        d = _doc_to_dict(doc, topic_names)
        d["is_shared"] = doc.id in shared_ids
        all_items.append(d)

    total = len(all_items)
    start = (page - 1) * per_page
    return {
        "items": all_items[start : start + per_page],
        "total": total,
        "page": page,
        "per_page": per_page,
    }


@router.get("/{doc_id}")
@account_limiter.limit("100/minute")
async def get_document(
    request: Request,
    doc_id: str,
    session: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_regular_user),
):
    """Return document metadata by ID."""
    request.state.current_user = current_user
    _, is_recipient = await get_accessible_document(session, doc_id, current_user.id)

    meta = await storage.get_metadata(session, doc_id)
    if meta is None:
        raise HTTPException(404, "Document not found")
    if is_recipient:
        meta.pop("extracted_text", None)
    return meta


@router.patch("/{doc_id}")
@account_limiter.limit("100/minute")
async def patch_document(
    request: Request,
    doc_id: str,
    body: DocumentPatch,
    session: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_regular_user),
):
    """Update document metadata."""
    request.state.current_user = current_user
    doc = await get_owned_document(session, doc_id, current_user.id)

    if not body.model_fields_set:
        raise HTTPException(422, "At least one field (filename, folder_id) must be provided")

    if "filename" in body.model_fields_set and body.filename is not None:
        doc.filename = body.filename

    if "folder_id" in body.model_fields_set:
        if body.folder_id is not None:
            target = await session.get(Folder, body.folder_id)
            if target is None or target.user_id != current_user.id:
                raise HTTPException(404, "Folder not found")
        doc.folder_id = body.folder_id

    await session.commit()

    meta = await storage.get_metadata(session, doc_id)
    if meta is None:
        raise HTTPException(404, "Document not found")
    return meta


@router.delete("/{doc_id}")
@account_limiter.limit("100/minute")
async def delete_document(
    doc_id: str,
    request: Request,
    remove_only: bool = Query(default=False),
    session: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_regular_user),
):
    """Delete a document and decrement quota when appropriate."""
    request.state.current_user = current_user
    doc = await get_owned_document(session, doc_id, current_user.id)

    is_cloud = doc.storage_backend != "minio"
    _doc_size = doc.size_bytes
    _doc_id = doc.id
    _ip = get_client_ip(request)

    if is_cloud and not remove_only:
        try:
            import api.documents as _doc_pkg

            _gsb = _doc_pkg.get_storage_backend_for_document
            cloud_backend = await _gsb(doc, current_user, session)
            await cloud_backend.delete_object(doc.object_key)
        except Exception as exc:
            _log.warning("cloud_delete_failed", provider=doc.storage_backend, error=str(exc))
            return JSONResponse(
                status_code=200,
                content={
                    "success": False,
                    "cloud_delete_failed": True,
                    "detail": "Cloud provider delete failed. You can remove from app only.",
                },
            )

    ok = await storage.delete_document(session, doc_id, skip_quota=is_cloud, auto_commit=False)
    if not ok:
        raise HTTPException(404, "Document not found")

    await write_audit_log(
        session,
        event_type="document.deleted",
        user_id=current_user.id,
        actor_id=current_user.id,
        resource_id=_doc_id,
        ip_address=_ip,
        metadata_={"size_bytes": _doc_size},
    )
    await session.commit()

    return {"success": True}


@router.post("/{doc_id}/classify")
@account_limiter.limit("100/minute")
async def classify_document(
    request: Request,
    doc_id: str,
    session: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_regular_user),
):
    """Re-queue a document for classification via Celery."""
    request.state.current_user = current_user
    doc = await get_owned_document(session, doc_id, current_user.id)

    doc.status = "processing"
    await session.commit()

    extract_and_classify.delay(str(doc.id))

    return {"document_id": str(doc.id), "status": "processing"}
