"""MCP upload routes.

Includes both the legacy synchronous upload endpoint and the asynchronous
submit+SSE streaming endpoints for long-running ingestion.
"""

import json
import logging
from typing import Any

from fastapi import (
    APIRouter,
    BackgroundTasks,
    Depends,
    File,
    Form,
    HTTPException,
    UploadFile,
    status,
)
from pydantic import BaseModel, Field
from sse_starlette.sse import EventSourceResponse

from app.core.progress import progress_manager
from app.core.security import verify_supabase_jwt
from app.services.mcp_service import generate_job_id, process_pdf_parallel, run_pdf_job_background

logger = logging.getLogger(__name__)
router = APIRouter(tags=["MCP"])


def _normalize_project_id(project_id: str) -> str:
    """Normalize the canonical project boundary for ingestion routes."""
    normalized = (project_id or "").strip()
    return normalized or "workspace-dev-01"


class PillarData(BaseModel):
    """Extracted pillar texts from the Literature node.

    Attributes:
        background: Research background and context.
        methodology: Methods and techniques used.
        gap: Research gaps identified.
        objective: Research objectives and goals.
        futurework: Future research directions.
    """

    background: str = ""
    methodology: str = ""
    gap: str = ""
    objective: str = ""
    futurework: str = ""


class IngestionResponse(BaseModel):
    """Response payload for successful PDF ingestion.

    Attributes:
        status: Processing result status.
        node_id: Neo4j elementId of the created Literature node.
        storage_url: Public URL of the PDF in Supabase Storage.
        title: Extracted document title.
        pillars: Five extracted research pillars.
    """

    status: str
    node_id: str
    storage_url: str
    title: str = ""
    pillars: PillarData = Field(default_factory=PillarData)


class SubmitResponse(BaseModel):
    """Response payload for async upload submission.

    Attributes:
        status: Submission status.
        job_id: Progress job identifier for SSE streaming.
    """

    status: str = "accepted"
    job_id: str


@router.post("/upload", response_model=IngestionResponse)
async def upload_pdf(
    file: UploadFile = File(..., description="PDF file to process"),
    project_id: str = Form(default="workspace-dev-01", description="Project identifier"),
    user_id: str = Depends(verify_supabase_jwt),
) -> IngestionResponse:
    """Upload and process a PDF file through parallel ingestion pipeline.

    Receives a PDF binary via multipart/form-data, then concurrently:
    1. Uploads the file to Supabase Storage for permanent storage.
    2. Processes the file through the Neo4j ingestion pipeline (parse, chunk, LLM extract, embed, store).

    Args:
        file: PDF file uploaded by the client.
        project_id: Project/workspace identifier for Neo4j scoping.
        user_id: Authenticated user id from JWT verification.

    Returns:
        IngestionResponse with node_id, storage URL, title, and pillar data.

    Raises:
        HTTPException: When file is not a PDF or processing fails.
    """
    if not file.filename or not file.filename.lower().endswith(".pdf"):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Only PDF files are accepted",
        )

    pdf_bytes = await file.read()

    if len(pdf_bytes) < 100:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="File is too small to be a valid PDF",
        )

    project_id = _normalize_project_id(project_id)

    logger.info(
        "PDF upload received",
        extra={
            "user_id": user_id,
            "pdf_filename": file.filename,
            "size_bytes": len(pdf_bytes),
            "project_id": project_id,
        },
    )

    result = await process_pdf_parallel(
        pdf_bytes=pdf_bytes,
        filename=file.filename,
        project_id=project_id,
        user_id=user_id,
    )

    return IngestionResponse(
        status=result.get("status", "success"),
        node_id=result.get("node_id", ""),
        storage_url=result.get("storage_url", ""),
        title=result.get("title", ""),
        pillars=PillarData(
            background=result.get("pillars", {}).get("background", ""),
            methodology=result.get("pillars", {}).get("methodology", ""),
            gap=result.get("pillars", {}).get("gap", ""),
            objective=result.get("pillars", {}).get("objective", ""),
            futurework=result.get("pillars", {}).get("futurework", ""),
        ),
    )


@router.post(
    "/upload/submit",
    response_model=SubmitResponse,
    status_code=status.HTTP_202_ACCEPTED,
)
async def submit_upload_pdf(
    background_tasks: BackgroundTasks,
    file: UploadFile = File(..., description="PDF file to process"),
    project_id: str = Form(default="workspace-dev-01", description="Project identifier"),
    user_id: str = Depends(verify_supabase_jwt),
) -> SubmitResponse:
    """Submit a PDF for background processing.

    This endpoint returns immediately with a `job_id` that can be used to
    subscribe to progress via `/mcp/upload/stream`.

    Args:
        background_tasks: FastAPI background task manager.
        file: PDF file uploaded by the client.
        project_id: Project/workspace identifier for Neo4j scoping.
        user_id: Authenticated user id from JWT verification.

    Returns:
        SubmitResponse with job_id.

    Raises:
        HTTPException: When file is not a PDF.
    """

    if not file.filename or not file.filename.lower().endswith(".pdf"):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Only PDF files are accepted",
        )

    pdf_bytes = await file.read()

    if len(pdf_bytes) < 100:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="File is too small to be a valid PDF",
        )

    project_id = _normalize_project_id(project_id)

    job_id = generate_job_id()
    await progress_manager.init_job(job_id, owner_user_id=user_id)

    logger.info(
        "Async PDF upload submitted",
        extra={
            "job_id": job_id,
            "user_id": user_id,
            "pdf_filename": file.filename,
            "size_bytes": len(pdf_bytes),
            "project_id": project_id,
        },
    )

    background_tasks.add_task(
        run_pdf_job_background,
        job_id,
        pdf_bytes,
        file.filename,
        project_id,
        user_id,
    )

    return SubmitResponse(job_id=job_id)


@router.get("/upload/stream")
async def stream_upload_progress(
    job_id: str,
    user_id: str = Depends(verify_supabase_jwt),
) -> EventSourceResponse:
    """Stream progress events for a submitted job via SSE.

    Args:
        job_id: Background job id returned by `/upload/submit`.
        user_id: Authenticated user id from JWT verification.

    Returns:
        EventSourceResponse: SSE stream of JSON events.

    Raises:
        HTTPException: When job id is unknown.
    """

    try:
        await progress_manager.snapshot(job_id)
        await progress_manager.assert_owner(job_id, user_id)
    except KeyError as exc:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="job_id not found",
        ) from exc
    except PermissionError as exc:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="forbidden",
        ) from exc

    logger.info(
        "SSE subscription opened",
        extra={"job_id": job_id, "user_id": user_id},
    )

    async def event_generator():
        async for event in progress_manager.subscribe(job_id):
            yield {
                "event": "progress",
                "data": json.dumps(event, ensure_ascii=False),
                "id": str(event.get("timestamp_ms", "")),
            }

    return EventSourceResponse(
        event_generator(),
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
        ping=15,
    )
