"""Attachment REST API routes."""

from __future__ import annotations

import logging
import uuid

from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile

from peon_mcp.attachments._s3 import (
    delete_object,
    generate_presigned_url,
    generate_presigned_urls,
    get_s3_bucket,
    upload_fileobj,
)
from peon_mcp.attachments.schemas import AttachmentResponse
from peon_mcp.common.auth import AuthInfo, get_auth
from peon_mcp.common.dependencies import get_db
from peon_mcp.common.scoping import authorize_entity_project_access, authorize_project_access
from peon_mcp.db import row_to_dict

logger = logging.getLogger(__name__)

router = APIRouter(tags=["Attachments"])

_MAX_UPLOAD_BYTES = 25 * 1024 * 1024  # 25 MB

# Allowlist of content types safe to serve directly. Anything not in this set
# is stored as application/octet-stream to prevent stored XSS via S3 serving
# Content-Type: text/html or application/javascript.
_SAFE_CONTENT_TYPES = {
    "application/octet-stream",
    "application/pdf",
    "application/zip",
    "image/gif",
    "image/jpeg",
    "image/png",
    "image/svg+xml",
    "image/webp",
    "text/csv",
    "text/plain",
    "video/mp4",
    "video/webm",
}


def _require_s3() -> str:
    bucket = get_s3_bucket()
    if not bucket:
        raise HTTPException(
            status_code=501,
            detail="Attachments are not configured (DO_SPACES_BUCKET is not set).",
        )
    return bucket


async def _build_response(row: dict) -> AttachmentResponse:
    """Build an AttachmentResponse with a fresh pre-signed download URL."""
    bucket = _require_s3()
    url = await generate_presigned_url(row["s3_key"], bucket)
    return AttachmentResponse(**row, download_url=url)


@router.post(
    "/api/tasks/{task_id}/attachments",
    response_model=AttachmentResponse,
    status_code=201,
)
async def upload_attachment(
    task_id: int,
    request: Request,
    file: UploadFile = File(...),
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Upload a file attachment to a task (multipart/form-data, max 25 MB)."""
    bucket = _require_s3()

    # Validate task exists and caller has access
    rows = await db.execute_fetchall("SELECT * FROM tasks WHERE id = ?", (task_id,))
    if not rows:
        raise HTTPException(404, detail=f"Task {task_id} not found")
    task = row_to_dict(rows[0])
    await authorize_project_access(db, auth, task["project_id"], request)

    # Read file data with size limit
    data = await file.read(_MAX_UPLOAD_BYTES + 1)
    if len(data) > _MAX_UPLOAD_BYTES:
        raise HTTPException(413, detail="File exceeds the 25 MB upload limit.")

    filename = file.filename or "upload"
    raw_content_type = file.content_type or "application/octet-stream"
    content_type = raw_content_type if raw_content_type in _SAFE_CONTENT_TYPES else "application/octet-stream"
    size_bytes = len(data)
    s3_key = f"attachments/{uuid.uuid4()}/{filename}"

    # Upload to S3
    try:
        await upload_fileobj(s3_key, data, content_type, bucket)
    except Exception as exc:
        logger.error("S3 upload failed: %s", exc)
        raise HTTPException(500, detail="Failed to upload file to storage.") from exc

    # Persist to DB
    cursor = await db.execute(
        """
        INSERT INTO task_attachments
            (task_id, project_id, filename, content_type, size_bytes, s3_key, uploaded_by_user_id)
        VALUES (?, ?, ?, ?, ?, ?, ?)
        RETURNING id
        """,
        (
            task_id,
            task["project_id"],
            filename,
            content_type,
            size_bytes,
            s3_key,
            auth.user_id,
        ),
    )
    new_id = cursor.lastrowid
    await db.commit()

    att_rows = await db.execute_fetchall(
        "SELECT * FROM task_attachments WHERE id = ?", (new_id,)
    )
    return await _build_response(row_to_dict(att_rows[0]))


@router.get("/api/tasks/{task_id}/attachments", response_model=list[AttachmentResponse])
async def list_task_attachments(
    task_id: int,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """List all attachments for a task. Returns an empty list when S3 is not configured."""
    # Validate task exists and caller has access
    rows = await db.execute_fetchall("SELECT * FROM tasks WHERE id = ?", (task_id,))
    if not rows:
        raise HTTPException(404, detail=f"Task {task_id} not found")
    task = row_to_dict(rows[0])
    await authorize_project_access(db, auth, task["project_id"], request)

    bucket = get_s3_bucket()
    if not bucket:
        return []

    att_rows = await db.execute_fetchall(
        "SELECT * FROM task_attachments WHERE task_id = ? ORDER BY created_at ASC",
        (task_id,),
    )
    if not att_rows:
        return []
    rows = [row_to_dict(r) for r in att_rows]
    urls = await generate_presigned_urls([r["s3_key"] for r in rows], bucket)
    return [AttachmentResponse(**row, download_url=url) for row, url in zip(rows, urls)]


@router.get("/api/attachments/{attachment_id}", response_model=AttachmentResponse)
async def get_attachment(
    attachment_id: int,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Get a single attachment with a fresh pre-signed URL."""
    _require_s3()

    entity = await authorize_entity_project_access(
        db, auth, request, "task_attachments", attachment_id
    )
    return await _build_response(entity)


@router.delete("/api/attachments/{attachment_id}", response_model=AttachmentResponse)
async def delete_attachment(
    attachment_id: int,
    request: Request,
    auth: AuthInfo = Depends(get_auth),
    db=Depends(get_db),
):
    """Delete an attachment from S3 and the database.

    S3 deletion is attempted first. If it fails the DB row is preserved and a
    500 is returned so the caller can retry.
    """
    bucket = _require_s3()

    entity = await authorize_entity_project_access(
        db, auth, request, "task_attachments", attachment_id
    )
    att = entity

    # Delete from S3 first — don't touch DB if this fails
    try:
        await delete_object(att["s3_key"], bucket)
    except Exception as exc:
        logger.error("S3 delete failed for key %s: %s", att["s3_key"], exc)
        raise HTTPException(500, detail="Failed to delete file from storage.") from exc

    await db.execute("DELETE FROM task_attachments WHERE id = ?", (attachment_id,))
    await db.commit()

    # Return a response without a live URL (object is gone)
    return AttachmentResponse(**att, download_url="")
