from __future__ import annotations

from datetime import datetime
from pathlib import Path
from typing import Any
from urllib.parse import quote
from zipfile import ZipFile

from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session

from app.db.session import get_db
from app.schemas.document_archives import DocumentArchiveBatchDownloadRequest, DocumentArchiveGenerateResult
from app.services.auth import AuthContext, require_authenticated_user
from app.services.document_archives import (
    ARCHIVE_STATUS_READY,
    DOCUMENT_TYPE_PRODUCTION_INBOUND_SETTLEMENT,
    DOCUMENT_TYPE_PRODUCTION_MATERIAL_OUT,
    DOCUMENT_TYPE_PURCHASE_ORDER,
    DOCUMENT_TYPE_PURCHASE_RECEIPT,
    DOCUMENT_TYPE_QUALITY_INSPECTION,
    DOCUMENT_TYPE_SALES_ORDER,
    DOCUMENT_TYPE_WAREHOUSE_OPERATION,
    FILE_FORMAT_PDF,
    build_archive_zip,
    default_archive_root,
    generate_document_archive,
    get_latest_archive,
    safe_filename,
)

router = APIRouter(dependencies=[Depends(require_authenticated_user)])

DOCUMENT_TYPE_KEY_MAP = {
    "sales-order": DOCUMENT_TYPE_SALES_ORDER,
    "purchase-order": DOCUMENT_TYPE_PURCHASE_ORDER,
    "purchase-receipt": DOCUMENT_TYPE_PURCHASE_RECEIPT,
    "quality-inspection": DOCUMENT_TYPE_QUALITY_INSPECTION,
    "production-material-out": DOCUMENT_TYPE_PRODUCTION_MATERIAL_OUT,
    "production-inbound-settlement": DOCUMENT_TYPE_PRODUCTION_INBOUND_SETTLEMENT,
    "warehouse-operation": DOCUMENT_TYPE_WAREHOUSE_OPERATION,
    DOCUMENT_TYPE_SALES_ORDER: DOCUMENT_TYPE_SALES_ORDER,
    DOCUMENT_TYPE_PURCHASE_ORDER: DOCUMENT_TYPE_PURCHASE_ORDER,
    DOCUMENT_TYPE_PURCHASE_RECEIPT: DOCUMENT_TYPE_PURCHASE_RECEIPT,
    DOCUMENT_TYPE_QUALITY_INSPECTION: DOCUMENT_TYPE_QUALITY_INSPECTION,
    DOCUMENT_TYPE_PRODUCTION_MATERIAL_OUT: DOCUMENT_TYPE_PRODUCTION_MATERIAL_OUT,
    DOCUMENT_TYPE_PRODUCTION_INBOUND_SETTLEMENT: DOCUMENT_TYPE_PRODUCTION_INBOUND_SETTLEMENT,
    DOCUMENT_TYPE_WAREHOUSE_OPERATION: DOCUMENT_TYPE_WAREHOUSE_OPERATION,
}


def normalize_document_type_key(document_type_key: str) -> str:
    document_type = DOCUMENT_TYPE_KEY_MAP.get(document_type_key)
    if document_type is None:
        raise HTTPException(
            status_code=400,
            detail="单据类型只能选择销售订单、采购订单、到货入库单、质量校验单、生产领料出库单、生产入库结算单或仓库出入库单",
        )
    return document_type


def _content_disposition(filename: str, inline: bool) -> str:
    disposition = "inline" if inline else "attachment"
    ascii_filename = safe_filename(filename.encode("ascii", "ignore").decode("ascii"))
    encoded_filename = quote(filename, safe="")
    return f"{disposition}; filename=\"{ascii_filename}\"; filename*=UTF-8''{encoded_filename}"


def _archive_file_response(path: str | Path, filename: str, inline: bool) -> FileResponse:
    file_path = Path(path)
    if not file_path.exists():
        raise HTTPException(status_code=404, detail="归档文件不存在，请重新生成")
    return FileResponse(
        file_path,
        media_type="application/pdf",
        filename=filename,
        headers={"Content-Disposition": _content_disposition(filename, inline)},
    )


def _extract_user_id(context: AuthContext | dict[str, Any] | Any) -> int | None:
    if isinstance(context, dict):
        value = context.get("user_id", context.get("uid", context.get("id")))
        return int(value) if value is not None else None
    user = getattr(context, "user", None)
    value = getattr(user, "id", None)
    if value is None:
        value = getattr(context, "user_id", None)
    return int(value) if value is not None else None


def _latest_ready_pdf_or_404(db: Session, document_type: str, business_id: int):
    archive = get_latest_archive(db, document_type, business_id)
    if archive is None or archive.status != ARCHIVE_STATUS_READY or archive.file_format != FILE_FORMAT_PDF:
        raise HTTPException(status_code=404, detail="归档文件不存在，请重新生成")
    return archive


def _ensure_zip_has_downloadable_archives(zip_path: Path) -> None:
    with ZipFile(zip_path) as archive_zip:
        if not archive_zip.namelist():
            raise HTTPException(status_code=404, detail="没有可下载的归档文件，请先生成PDF归档")


@router.post("/{document_type_key}/{business_id}/generate", response_model=DocumentArchiveGenerateResult)
def generate_archive(
    document_type_key: str,
    business_id: int,
    context: AuthContext = Depends(require_authenticated_user),
    db: Session = Depends(get_db),
) -> DocumentArchiveGenerateResult:
    document_type = normalize_document_type_key(document_type_key)
    return generate_document_archive(db, document_type, business_id, created_by=_extract_user_id(context))


@router.get("/{document_type_key}/{business_id}/latest/preview")
def preview_latest_archive(document_type_key: str, business_id: int, db: Session = Depends(get_db)) -> FileResponse:
    document_type = normalize_document_type_key(document_type_key)
    archive = _latest_ready_pdf_or_404(db, document_type, business_id)
    return _archive_file_response(archive.file_path, archive.file_name, inline=True)


@router.get("/{document_type_key}/{business_id}/latest/download")
def download_latest_archive(document_type_key: str, business_id: int, db: Session = Depends(get_db)) -> FileResponse:
    document_type = normalize_document_type_key(document_type_key)
    archive = _latest_ready_pdf_or_404(db, document_type, business_id)
    return _archive_file_response(archive.file_path, archive.file_name, inline=False)


@router.post("/batch-download")
def batch_download_archives(payload: DocumentArchiveBatchDownloadRequest, db: Session = Depends(get_db)) -> FileResponse:
    document_type = normalize_document_type_key(payload.document_type)
    batch_dir = Path(default_archive_root()) / "batch"
    timestamp = datetime.now().strftime("%Y%m%d%H%M%S%f")
    zip_filename = safe_filename(f"{document_type}_归档_{timestamp}") + ".zip"
    zip_path = build_archive_zip(db, document_type, payload.business_ids, batch_dir / zip_filename)
    _ensure_zip_has_downloadable_archives(zip_path)
    return FileResponse(
        zip_path,
        media_type="application/zip",
        filename=zip_filename,
        headers={"Content-Disposition": _content_disposition(zip_filename, inline=False)},
    )
