import logging
from typing import Annotated, Any
from uuid import uuid4

from app.auth import AuthenticatedUser, get_current_user
from app.config import get_settings
from app.core.celery_app import celery_app
from app.core.storage import OSSStorageService, get_storage_service
from app.dependencies import (
    get_asset_library_repository,
    get_asset_repository,
    get_ingest_job_repository,
    get_project_repository,
)
from app.schemas.upload import (
    DirectUploadCompleteRequest,
    DirectUploadCompleteResponse,
    DirectUploadPrepareRequest,
    DirectUploadPrepareResponse,
    UploadAssetRequest,
    UploadAssetResponse,
)
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, status

from packages.application import GetProjectUseCase, SubmitIngestJobCommand, SubmitIngestJobUseCase

logger = logging.getLogger(__name__)

router = APIRouter()

# 允许上传的文件 MIME 类型
ALLOWED_MIME_TYPES = frozenset(
    {
        # 视频
        "video/mp4",
        "video/mpeg",
        "video/quicktime",
        "video/x-msvideo",
        "video/webm",
        "video/x-matroska",
        "video/3gpp",
        # 音频
        "audio/mpeg",
        "audio/wav",
        "audio/ogg",
        "audio/flac",
        "audio/aac",
        "audio/mp3",
        "audio/x-m4a",
        "audio/webm",
        # 图片
        "image/jpeg",
        "image/png",
        "image/gif",
        "image/webp",
        "image/bmp",
        "image/tiff",
        "image/svg+xml",
    }
)


def _validate_mime_type(content_type: str | None) -> str:
    """验证并返回标准化的 MIME 类型，如果无效则抛出异常。"""
    if not content_type:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Content-Type header is required",
        )

    # 处理带参数的类型，如 "video/mp4; charset=utf-8"
    base_type = content_type.split(";")[0].strip().lower()

    if base_type not in ALLOWED_MIME_TYPES:
        raise HTTPException(
            status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
            detail=f"File type '{base_type}' is not supported. Allowed types: video, audio, and image files.",
        )

    return base_type


def _require_project_and_library(
    project_id: str,
    library_id: str,
    project_repository: Any,
    asset_library_repository: Any,
) -> None:
    project = GetProjectUseCase(project_repository).execute(project_id)
    if project is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")

    libraries = asset_library_repository.find_by_project(project_id)
    if not any(item.id == library_id for item in libraries):
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Asset library not found")


def _submit_ingest_job(
    project_id: str,
    library_id: str,
    storage_key: str,
    ingest_job_repository: Any,
    file_hash: str = "",
) -> Any:
    use_case = SubmitIngestJobUseCase(ingest_job_repository)
    job = use_case.execute(
        SubmitIngestJobCommand(
            project_id=project_id,
            library_id=library_id,
            storage_key=storage_key,
            file_hash=file_hash,
        )
    )
    celery_app.send_task("worker.ingest_asset", args=[job.id])
    return job


@router.post("/direct/prepare", response_model=DirectUploadPrepareResponse)
async def prepare_direct_upload(
    request: DirectUploadPrepareRequest,
    authenticated_user: AuthenticatedUser = Depends(get_current_user),
    project_repository: Any = Depends(get_project_repository),
    asset_library_repository: Any = Depends(get_asset_library_repository),
    storage_service: OSSStorageService = Depends(get_storage_service),
) -> DirectUploadPrepareResponse:
    """创建浏览器直传 OSS 的短期表单签名。"""
    settings = get_settings()
    max_size_bytes = settings.OSS_DIRECT_UPLOAD_MAX_MB * 1024 * 1024
    if request.file_size > max_size_bytes:
        raise HTTPException(
            status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
            detail=f"File exceeds upload limit ({settings.OSS_DIRECT_UPLOAD_MAX_MB}MB)",
        )

    # P2-5: 服务端验证 MIME 类型
    validated_content_type = _validate_mime_type(request.content_type)

    _require_project_and_library(
        request.project_id,
        request.library_id,
        project_repository,
        asset_library_repository,
    )

    file_id = uuid4().hex[:8]
    safe_filename = request.filename.replace("/", "_").replace("\\", "_")
    storage_key = f"uploads/{file_id}/{safe_filename}"
    try:
        payload = storage_service.create_direct_upload_post(
            storage_key=storage_key,
            content_type=validated_content_type,
            max_size_bytes=max_size_bytes,
            expires_seconds=settings.OSS_DIRECT_UPLOAD_EXPIRE_SECONDS,
        )
    except RuntimeError as error:
        logger.error("OSS not configured for direct upload prepare: %s", error)
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(error)) from error
    except Exception as error:
        logger.exception("Unexpected error in direct upload prepare: %s", error)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to prepare upload: {type(error).__name__}",
        ) from error

    return DirectUploadPrepareResponse(
        upload_url=str(payload["url"]),
        method=str(payload["method"]),
        storage_key=str(payload["storage_key"]),
        expires_at=str(payload["expires_at"]),
        fields={str(key): str(value) for key, value in dict(payload["fields"]).items()},
        max_size_bytes=max_size_bytes,
    )


@router.post("/direct/complete", response_model=DirectUploadCompleteResponse)
async def complete_direct_upload(
    request: DirectUploadCompleteRequest,
    authenticated_user: AuthenticatedUser = Depends(get_current_user),
    ingest_job_repository: Any = Depends(get_ingest_job_repository),
    project_repository: Any = Depends(get_project_repository),
    asset_library_repository: Any = Depends(get_asset_library_repository),
    asset_repository: Any = Depends(get_asset_repository),
    storage_service: OSSStorageService = Depends(get_storage_service),
) -> DirectUploadCompleteResponse:
    """确认浏览器直传完成并创建导入任务。"""
    _require_project_and_library(
        request.project_id,
        request.library_id,
        project_repository,
        asset_library_repository,
    )
    normalized_key = storage_service._normalize_storage_key(request.storage_key)
    if not normalized_key.startswith("uploads/"):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid upload key")
    try:
        file_exists = storage_service.file_exists(normalized_key)
    except Exception as error:
        logger.exception("OSS error checking file existence for key=%s: %s", normalized_key, error)
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Storage service unavailable",
        ) from error
    if not file_exists:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Uploaded file not found")

    # ── 素材去重检测：同素材库 + 同 file_hash 视为重复 ──
    if request.file_hash:
        existing = asset_repository.find_by_library_and_file_hash(
            library_id=request.library_id,
            file_hash=request.file_hash,
        )
        if existing is not None:
            logger.info(
                "素材去重命中: library=%s hash=%s existing_asset=%s",
                request.library_id,
                request.file_hash,
                existing.id,
            )
            return DirectUploadCompleteResponse(
                storage_key=normalized_key,
                ingest_job_id="",
                duplicated=True,
                asset_id=existing.id,
            )

    job = _submit_ingest_job(
        project_id=request.project_id,
        library_id=request.library_id,
        storage_key=normalized_key,
        ingest_job_repository=ingest_job_repository,
        file_hash=request.file_hash,
    )
    return DirectUploadCompleteResponse(storage_key=normalized_key, ingest_job_id=job.id)


@router.post(
    "",
    response_model=UploadAssetResponse,
    summary="Upload Asset",
    description="上传素材文件（multipart/form-data），支持视频、音频、图片。触发导入流水线自动处理。",
)
async def upload_asset(
    project_id: str = Form(..., min_length=1, description="项目 ID"),
    library_id: str = Form(..., min_length=1, description="素材库 ID"),
    file: UploadFile = File(..., description="要上传的文件（视频、音频、图片等）"),
    file_hash: str = Form(default="", description="文件 MD5 哈希，用于去重检测"),
    authenticated_user: AuthenticatedUser = Depends(get_current_user),
    ingest_job_repository: Any = Depends(get_ingest_job_repository),
    project_repository: Any = Depends(get_project_repository),
    asset_library_repository: Any = Depends(get_asset_library_repository),
    asset_repository: Any = Depends(get_asset_repository),
    storage_service: OSSStorageService = Depends(get_storage_service),
) -> UploadAssetResponse:
    """上传素材文件并触发导入流水线。"""
    _require_project_and_library(project_id, library_id, project_repository, asset_library_repository)

    # ── 素材去重检测：上传前检查同素材库 + 同 file_hash ──
    if file_hash:
        existing = asset_repository.find_by_library_and_file_hash(
            library_id=library_id,
            file_hash=file_hash,
        )
        if existing is not None:
            logger.info(
                "素材去重命中(multipart): library=%s hash=%s existing_asset=%s",
                library_id,
                file_hash,
                existing.id,
            )
            return UploadAssetResponse(
                storage_key=existing.storage_key,
                ingest_job_id="",
                url="",
                duplicated=True,
                asset_id=existing.id,
            )

    # P2-5: 服务端验证 MIME 类型
    validated_content_type = _validate_mime_type(file.content_type)

    file_id = uuid4().hex[:8]
    safe_filename = file.filename.replace("/", "_").replace("\\", "_") if file.filename else "unknown"
    storage_key = f"uploads/{file_id}/{safe_filename}"

    try:
        file_url = storage_service.upload_file(
            file.file,
            storage_key,
            content_type=validated_content_type,
        )
    except RuntimeError as error:
        logger.error("OSS not configured for upload: %s", error)
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(error)) from error
    except Exception as error:
        logger.exception("Unexpected error uploading file to OSS: %s", error)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to upload file: {type(error).__name__}",
        ) from error

    job = _submit_ingest_job(
        project_id=project_id,
        library_id=library_id,
        storage_key=storage_key,
        ingest_job_repository=ingest_job_repository,
        file_hash=file_hash,
    )

    return UploadAssetResponse(
        storage_key=storage_key,
        ingest_job_id=job.id,
        url=file_url,
    )
