"""Project and Frame CRUD endpoints."""

import logging
from typing import List

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

from database import get_db
from models import Annotation, Mask, Project, Frame, User
from routers.auth import get_current_user, require_editor
from schemas import ProjectCopyRequest, ProjectCreate, ProjectOut, ProjectUpdate, FrameCreate, FrameOut
from minio_client import get_presigned_url

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


def _next_project_copy_name(db: Session, source_name: str) -> str:
    base_name = f"{source_name} 副本"
    existing_names = {
        row[0]
        for row in db.query(Project.name)
        .filter(Project.name.like(f"{base_name}%"))
        .all()
    }
    if base_name not in existing_names:
        return base_name
    suffix = 2
    while f"{base_name} {suffix}" in existing_names:
        suffix += 1
    return f"{base_name} {suffix}"


def _prepare_project_response(project: Project) -> Project:
    project.frame_count = len(project.frames)
    if project.thumbnail_url:
        project.thumbnail_url = get_presigned_url(project.thumbnail_url, expires=3600)
    return project


# ---------------------------------------------------------------------------
# Projects
# ---------------------------------------------------------------------------
@router.post(
    "",
    response_model=ProjectOut,
    status_code=status.HTTP_201_CREATED,
    summary="Create a new project",
)
def create_project(
    payload: ProjectCreate,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_editor),
) -> Project:
    """Create a new segmentation project."""
    project = Project(**payload.model_dump(), owner_user_id=current_user.id)
    db.add(project)
    db.commit()
    db.refresh(project)
    logger.info("Created project id=%s name=%s", project.id, project.name)
    return project


@router.get(
    "",
    response_model=List[ProjectOut],
    summary="List all projects",
)
def list_projects(
    skip: int = 0,
    limit: int = 100,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
) -> List[Project]:
    """Retrieve a paginated list of projects."""
    projects = (
        db.query(Project)
        .offset(skip)
        .limit(limit)
        .all()
    )
    for p in projects:
        _prepare_project_response(p)
    return projects


@router.get(
    "/{project_id}",
    response_model=ProjectOut,
    summary="Get a single project",
)
def get_project(
    project_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
) -> Project:
    """Retrieve a project by its ID."""
    project = db.query(Project).filter(Project.id == project_id).first()
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    return _prepare_project_response(project)


@router.post(
    "/{project_id}/copy",
    response_model=ProjectOut,
    status_code=status.HTTP_201_CREATED,
    summary="Copy a project",
)
def copy_project(
    project_id: int,
    payload: ProjectCopyRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_editor),
) -> Project:
    """Copy a project. Reset copies media/frame sequence; full also copies annotations and mask metadata."""
    source = db.query(Project).filter(Project.id == project_id).first()
    if not source:
        raise HTTPException(status_code=404, detail="Project not found")

    next_name = (payload.name or "").strip() if payload.name is not None else ""
    if not next_name:
        next_name = _next_project_copy_name(db, source.name)

    copied = Project(
        name=next_name,
        description=source.description,
        video_path=source.video_path,
        thumbnail_url=source.thumbnail_url,
        status=source.status,
        source_type=source.source_type,
        original_fps=source.original_fps,
        parse_fps=source.parse_fps,
        owner_user_id=current_user.id,
    )
    db.add(copied)
    db.flush()

    frame_id_map: dict[int, int] = {}
    for frame in sorted(source.frames, key=lambda item: item.frame_index):
        copied_frame = Frame(
            project_id=copied.id,
            frame_index=frame.frame_index,
            image_url=frame.image_url,
            width=frame.width,
            height=frame.height,
            timestamp_ms=frame.timestamp_ms,
            source_frame_number=frame.source_frame_number,
        )
        db.add(copied_frame)
        db.flush()
        frame_id_map[frame.id] = copied_frame.id

    if payload.mode == "full":
        for annotation in sorted(source.annotations, key=lambda item: item.id):
            copied_annotation = Annotation(
                project_id=copied.id,
                frame_id=frame_id_map.get(annotation.frame_id) if annotation.frame_id is not None else None,
                template_id=annotation.template_id,
                mask_data=annotation.mask_data,
                points=annotation.points,
                bbox=annotation.bbox,
            )
            db.add(copied_annotation)
            db.flush()
            for mask in annotation.masks:
                db.add(Mask(
                    annotation_id=copied_annotation.id,
                    mask_url=mask.mask_url,
                    format=mask.format,
                ))

    db.commit()
    db.refresh(copied)
    logger.info("Copied project id=%s to id=%s mode=%s", project_id, copied.id, payload.mode)
    return _prepare_project_response(copied)


@router.patch(
    "/{project_id}",
    response_model=ProjectOut,
    summary="Update a project",
)
def update_project(
    project_id: int,
    payload: ProjectUpdate,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_editor),
) -> Project:
    """Update project fields partially."""
    project = db.query(Project).filter(Project.id == project_id).first()
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")

    for key, value in payload.model_dump(exclude_unset=True).items():
        if key == "name":
            value = (value or "").strip()
            if not value:
                raise HTTPException(status_code=400, detail="Project name is required")
        setattr(project, key, value)

    db.commit()
    db.refresh(project)
    logger.info("Updated project id=%s", project_id)
    return project


@router.delete(
    "/{project_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Delete a project",
)
def delete_project(
    project_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_editor),
) -> None:
    """Delete a project and all related frames and annotations."""
    project = db.query(Project).filter(Project.id == project_id).first()
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")

    db.delete(project)
    db.commit()
    logger.info("Deleted project id=%s", project_id)


# ---------------------------------------------------------------------------
# Frames
# ---------------------------------------------------------------------------
@router.post(
    "/{project_id}/frames",
    response_model=FrameOut,
    status_code=status.HTTP_201_CREATED,
    summary="Add a frame to a project",
)
def create_frame(
    project_id: int,
    payload: FrameCreate,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_editor),
) -> Frame:
    """Register a new frame under a project."""
    project = db.query(Project).filter(Project.id == project_id).first()
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")

    frame = Frame(project_id=project_id, **payload.model_dump(exclude={"project_id"}))
    db.add(frame)
    db.commit()
    db.refresh(frame)
    return frame


@router.get(
    "/{project_id}/frames",
    response_model=List[FrameOut],
    summary="List frames for a project",
)
def list_frames(
    project_id: int,
    skip: int = 0,
    limit: int = 1000,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
) -> List[Frame]:
    """Retrieve all frames belonging to a project."""
    project = db.query(Project).filter(Project.id == project_id).first()
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")

    frames = (
        db.query(Frame)
        .filter(Frame.project_id == project_id)
        .order_by(Frame.frame_index)
        .offset(skip)
        .limit(limit)
        .all()
    )
    for frame in frames:
        frame.image_url = get_presigned_url(frame.image_url, expires=3600)
    return frames


@router.get(
    "/{project_id}/frames/{frame_id}",
    response_model=FrameOut,
    summary="Get a single frame",
)
def get_frame(
    project_id: int,
    frame_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
) -> Frame:
    """Retrieve a specific frame by ID."""
    frame = (
        db.query(Frame)
        .join(Project, Project.id == Frame.project_id)
        .filter(
            Frame.project_id == project_id,
            Frame.id == frame_id,
        )
        .first()
    )
    if not frame:
        raise HTTPException(status_code=404, detail="Frame not found")
    return frame
