"""Media upload and parsing endpoints."""

import logging
import os
import shutil
import subprocess
import tempfile
from pathlib import Path
from typing import Optional

from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, status
from sqlalchemy.orm import Session

from database import get_db
from minio_client import upload_file, get_presigned_url
from models import Project, Frame
from schemas import FrameOut
from services.frame_parser import parse_video, parse_dicom, upload_frames_to_minio

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/media", tags=["Media"])

ALLOWED_EXTENSIONS = {".mp4", ".avi", ".mov", ".mkv", ".webm", ".png", ".jpg", ".jpeg", ".dcm"}


def _get_ext(filename: str) -> str:
    return Path(filename).suffix.lower()


@router.post(
    "/upload",
    status_code=status.HTTP_201_CREATED,
    summary="Upload a media file",
)
async def upload_media(
    file: UploadFile = File(...),
    project_id: Optional[int] = Form(None),
    db: Session = Depends(get_db),
) -> dict:
    """Accept a video, image, or DICOM file and store it in MinIO.

    If project_id is provided, the video_path of the project is updated.
    Returns the presigned URL of the uploaded object.
    """
    if not file.filename:
        raise HTTPException(status_code=400, detail="Missing filename")

    ext = _get_ext(file.filename)
    if ext not in ALLOWED_EXTENSIONS:
        raise HTTPException(
            status_code=400,
            detail=f"Unsupported file type: {ext}",
        )

    data = await file.read()
    object_name = f"uploads/{project_id or 'general'}/{file.filename}"

    try:
        upload_file(object_name, data, content_type=file.content_type or "application/octet-stream", length=len(data))
    except Exception as exc:  # noqa: BLE001
        logger.error("Upload failed: %s", exc)
        raise HTTPException(status_code=500, detail="Upload to storage failed") from exc

    file_url = get_presigned_url(object_name, expires=3600)

    if project_id:
        project = db.query(Project).filter(Project.id == project_id).first()
        if project:
            project.video_path = object_name
            db.commit()
            logger.info("Linked upload to project_id=%s", project_id)
        else:
            logger.warning("Project id=%s not found for upload linkage", project_id)

    # TODO: enqueue async parsing job (Celery / background task)
    logger.info("Upload complete: %s (size=%d bytes). Async parsing queued.", object_name, len(data))

    return {
        "object_name": object_name,
        "file_url": file_url,
        "size": len(data),
        "message": "Upload successful. Parsing job queued.",
    }


@router.post(
    "/parse",
    status_code=status.HTTP_202_ACCEPTED,
    summary="Trigger frame extraction",
)
def parse_media(
    project_id: int,
    source_type: str = "video",  # video | dicom
    db: Session = Depends(get_db),
) -> dict:
    """Trigger frame extraction for a project's uploaded media.

    * video: uses FFmpeg or OpenCV fallback.
    * dicom: uses pydicom to read DCM frames.

    Extracted frames are uploaded to MinIO and registered in the database.
    """
    project = db.query(Project).filter(Project.id == project_id).first()
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")

    if not project.video_path:
        raise HTTPException(status_code=400, detail="Project has no media uploaded")

    # Download from MinIO to a temp directory
    from minio_client import download_file

    try:
        media_bytes = download_file(project.video_path)
    except Exception as exc:  # noqa: BLE001
        logger.error("Failed to download media for parsing: %s", exc)
        raise HTTPException(status_code=500, detail="Failed to retrieve media from storage") from exc

    tmp_dir = tempfile.mkdtemp(prefix=f"seg_parse_{project_id}_")
    local_path = os.path.join(tmp_dir, Path(project.video_path).name)

    with open(local_path, "wb") as f:
        f.write(media_bytes)

    output_dir = os.path.join(tmp_dir, "frames")
    os.makedirs(output_dir, exist_ok=True)

    try:
        if source_type == "dicom":
            # For DICOM, treat local_path as a directory if it contains multiple .dcm
            # If a single .dcm file was uploaded, put it in its own sub-dir
            dcm_dir = os.path.join(tmp_dir, "dcm")
            os.makedirs(dcm_dir, exist_ok=True)
            if local_path.lower().endswith(".dcm"):
                shutil.move(local_path, os.path.join(dcm_dir, os.path.basename(local_path)))
            else:
                shutil.unpack_archive(local_path, dcm_dir) if shutil.which("unzip") else shutil.move(local_path, dcm_dir)
            frame_files = parse_dicom(dcm_dir, output_dir)
        else:
            frame_files = parse_video(local_path, output_dir, fps=30)
    except Exception as exc:  # noqa: BLE001
        logger.error("Frame extraction failed: %s", exc)
        shutil.rmtree(tmp_dir, ignore_errors=True)
        raise HTTPException(status_code=500, detail="Frame extraction failed") from exc

    # Upload frames to MinIO
    try:
        object_names = upload_frames_to_minio(frame_files, project_id)
    except Exception as exc:  # noqa: BLE001
        logger.error("Frame upload failed: %s", exc)
        shutil.rmtree(tmp_dir, ignore_errors=True)
        raise HTTPException(status_code=500, detail="Frame upload to storage failed") from exc

    # Register frames in DB
    frames_out = []
    for idx, obj_name in enumerate(object_names):
        # Get image dimensions
        local_frame = frame_files[idx]
        try:
            import cv2
            img = cv2.imread(local_frame)
            h, w = img.shape[:2] if img is not None else (None, None)
        except Exception:  # noqa: BLE001
            h, w = None, None

        frame = Frame(
            project_id=project_id,
            frame_index=idx,
            image_url=obj_name,
            width=w,
            height=h,
        )
        db.add(frame)
        frames_out.append(frame)

    db.commit()
    for f in frames_out:
        db.refresh(f)

    # Cleanup temp files
    shutil.rmtree(tmp_dir, ignore_errors=True)

    project.status = "ready"
    db.commit()

    logger.info("Parsed %d frames for project_id=%s", len(frames_out), project_id)
    return {
        "project_id": project_id,
        "frames_extracted": len(frames_out),
        "status": "ready",
        "message": "Frame extraction completed successfully.",
    }
