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

from app.api.deps import require_roles
from app.core.security import generate_public_token, hash_token
from app.db.session import get_db
from app.models import AdminUser, Certificate, CertificateAccessToken, Learner, ProjectCourse
from app.schemas.certificate import CertificateCreate, CertificateOut, PdfPregenerationJobCreate, PdfPregenerationJobOut
from app.services.certificate_issuance import CertificateIssueData, DuplicateCertificate, issue_certificate
from app.services.logs import log_action
from app.services.pdf import PdfGenerationBusy, render_certificate_pdf
from app.services.pdf_pregeneration import pdf_pregeneration_manager
from app.services.system_settings import get_pdf_generation_concurrency_limit

router = APIRouter()


def _create_token(db: Session, certificate_id: int, token_type: str) -> int:
    raw_token = generate_public_token()
    token = CertificateAccessToken(
        certificate_id=certificate_id,
        token_hash=hash_token(raw_token),
        token_value=raw_token,
        token_type=token_type,
    )
    db.add(token)
    db.flush()
    return token.id


@router.get("", response_model=list[CertificateOut])
def list_certificates(
    keyword: str | None = Query(default=None),
    status_value: str | None = Query(default=None, alias="status"),
    pdf_status: str | None = Query(default=None),
    import_batch_id: int | None = Query(default=None),
    db: Session = Depends(get_db),
    _: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")),
) -> list[dict[str, object]]:
    query = db.query(Certificate, Learner).outerjoin(Learner, Learner.id == Certificate.learner_id).order_by(Certificate.id.desc())
    if keyword:
        like = f"%{keyword}%"
        query = query.filter(
            or_(
                Certificate.certificate_no.like(like),
                Certificate.certificate_name.like(like),
                Certificate.project_code.like(like),
                Certificate.course_name.like(like),
                Certificate.stage_name.like(like),
                Learner.current_name.like(like),
                Learner.phone.like(like),
            )
        )
    if status_value:
        query = query.filter(Certificate.status == status_value)
    if pdf_status:
        query = query.filter(Certificate.pdf_status == pdf_status)
    if import_batch_id:
        query = query.filter(Certificate.import_batch_id == import_batch_id)
    return [_certificate_payload(certificate, learner) for certificate, learner in query.limit(200).all()]


@router.post("", response_model=CertificateOut, status_code=status.HTTP_201_CREATED)
def create_certificate(
    payload: CertificateCreate,
    db: Session = Depends(get_db),
    admin: AdminUser = Depends(require_roles("system_admin", "certificate_admin")),
) -> Certificate:
    try:
        project = db.query(ProjectCourse).filter(ProjectCourse.code == payload.project_code, ProjectCourse.status == "active").first()
        if not project:
            raise ValueError("项目代码不存在或已停用")
        certificate, learner, learner_created = issue_certificate(
            db,
            CertificateIssueData(
                learner_name=payload.learner_name,
                learner_phone=payload.learner_phone,
                project_code=payload.project_code,
                class_name=payload.class_name,
                course_name=payload.course_name,
                stage_name=payload.stage_name,
                course_start_date=payload.course_start_date,
                course_end_date=payload.course_end_date,
                issue_date=payload.issue_date,
                template_code=payload.template_code or project.default_template_code,
                remark=payload.remark,
            ),
            source="manual_certificate",
        )
    except DuplicateCertificate as exc:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
    except (ValueError, FileNotFoundError) as exc:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
    log_action(db, admin, "create_certificate", "certificate", certificate.id, {"certificate_no": certificate.certificate_no, "learner_name": learner.current_name, "learner_created": learner_created, "project_code": certificate.project_code, "issue_date": certificate.issue_date})
    db.commit()
    db.refresh(certificate)
    return certificate


@router.get("/pdf-pregeneration-jobs", response_model=list[PdfPregenerationJobOut])
def list_pdf_pregeneration_jobs(
    _: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")),
) -> list[dict[str, object]]:
    return [job.to_dict() for job in pdf_pregeneration_manager.list()]


@router.post("/pdf-pregeneration-jobs", response_model=PdfPregenerationJobOut, status_code=status.HTTP_201_CREATED)
def start_pdf_pregeneration_job(
    payload: PdfPregenerationJobCreate,
    db: Session = Depends(get_db),
    admin: AdminUser = Depends(require_roles("system_admin", "certificate_admin")),
) -> dict[str, object]:
    certificate_ids = _resolve_pregeneration_certificate_ids(db, payload)
    if not certificate_ids:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="没有可预生成的证书")
    concurrency_limit = get_pdf_generation_concurrency_limit(db)
    job = pdf_pregeneration_manager.start(certificate_ids, concurrency_limit)
    log_action(
        db,
        admin,
        "start_pdf_pregeneration",
        "certificate",
        job.id,
        detail={
            "total": job.total,
            "concurrency_limit": concurrency_limit,
            "import_batch_id": payload.import_batch_id,
        },
    )
    db.commit()
    return job.to_dict()


@router.get("/pdf-pregeneration-jobs/{job_id}", response_model=PdfPregenerationJobOut)
def get_pdf_pregeneration_job(
    job_id: str,
    _: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")),
) -> dict[str, object]:
    job = pdf_pregeneration_manager.get(job_id)
    if not job:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="预生成任务不存在或已过期")
    return job.to_dict()


@router.get("/{certificate_id}", response_model=CertificateOut)
def get_certificate(
    certificate_id: int,
    db: Session = Depends(get_db),
    _: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")),
) -> Certificate:
    certificate = db.get(Certificate, certificate_id)
    if not certificate:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Certificate not found")
    return certificate


def _certificate_payload(certificate: Certificate, learner: Learner | None = None) -> dict[str, object]:
    return {
        "id": certificate.id,
        "learner_id": certificate.learner_id,
        "learner_name": learner.current_name if learner else None,
        "learner_phone": learner.phone if learner else None,
        "import_batch_id": certificate.import_batch_id,
        "project_code": certificate.project_code,
        "certificate_no": certificate.certificate_no,
        "certificate_name": certificate.certificate_name,
        "class_name": certificate.class_name,
        "course_name": certificate.course_name,
        "stage_name": certificate.stage_name,
        "course_start_date": certificate.course_start_date,
        "course_end_date": certificate.course_end_date,
        "issue_date": certificate.issue_date,
        "template_code": certificate.template_code,
        "status": certificate.status,
        "pdf_status": certificate.pdf_status,
        "created_at": certificate.created_at,
        "updated_at": certificate.updated_at,
    }


def _resolve_pregeneration_certificate_ids(db: Session, payload: PdfPregenerationJobCreate) -> list[int]:
    ids: list[int] = []
    if payload.import_batch_id:
        ids.extend(
            item[0]
            for item in db.query(Certificate.id)
            .filter(Certificate.import_batch_id == payload.import_batch_id)
            .filter(Certificate.status == "valid")
            .order_by(Certificate.id.asc())
            .all()
        )
    selected_ids = _dedupe_ids(payload.certificate_ids)
    if selected_ids:
        valid_selected_ids = {
            item[0]
            for item in db.query(Certificate.id)
            .filter(Certificate.id.in_(selected_ids))
            .filter(Certificate.status == "valid")
            .all()
        }
        ids.extend(certificate_id for certificate_id in selected_ids if certificate_id in valid_selected_ids)
    seen: set[int] = set()
    result: list[int] = []
    for item in ids:
        if item not in seen:
            seen.add(item)
            result.append(item)
    return result


def _dedupe_ids(values: list[int]) -> list[int]:
    seen: set[int] = set()
    result: list[int] = []
    for value in values:
        if value not in seen:
            seen.add(value)
            result.append(value)
    return result


@router.get("/{certificate_id}/preview")
def preview_certificate(
    certificate_id: int,
    db: Session = Depends(get_db),
    _: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")),
) -> dict[str, object]:
    certificate = db.get(Certificate, certificate_id)
    if not certificate:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Certificate not found")
    learner = db.get(Learner, certificate.learner_id)
    public_token = db.get(CertificateAccessToken, certificate.public_token_id) if certificate.public_token_id else None
    qr_token = db.get(CertificateAccessToken, certificate.qr_token_id) if certificate.qr_token_id else None
    return {
        "certificate_no": certificate.certificate_no,
        "learner_name": learner.current_name if learner else "",
        "learner_phone": learner.phone if learner else "",
        "certificate_name": certificate.certificate_name,
        "project_code": certificate.project_code,
        "course_name": certificate.course_name,
        "stage_name": certificate.stage_name,
        "course_start_date": certificate.course_start_date.isoformat() if certificate.course_start_date else None,
        "course_end_date": certificate.course_end_date.isoformat() if certificate.course_end_date else None,
        "issue_date": certificate.issue_date.isoformat(),
        "template_code": certificate.template_code,
        "status": certificate.status,
        "public_url": f"/cert/{public_token.token_value}" if public_token else "",
        "verify_url": f"/verify/{qr_token.token_value}" if qr_token else "",
    }


@router.get("/{certificate_id}/download")
def download_certificate(
    certificate_id: int,
    db: Session = Depends(get_db),
    _: AdminUser = Depends(require_roles("system_admin", "certificate_admin")),
) -> FileResponse:
    certificate = db.get(Certificate, certificate_id)
    if not certificate:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Certificate not found")
    if certificate.status != "valid":
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Voided certificate cannot download normal PDF")
    learner = db.get(Learner, certificate.learner_id)
    token = db.get(CertificateAccessToken, certificate.qr_token_id or certificate.public_token_id)
    project = db.query(ProjectCourse).filter(ProjectCourse.code == certificate.project_code).first()
    if not learner or not token:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Certificate data is incomplete")
    try:
        pdf_path = render_certificate_pdf(
            certificate,
            learner,
            project,
            token,
            concurrency_limit=get_pdf_generation_concurrency_limit(db),
        )
    except PdfGenerationBusy as exc:
        raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail=str(exc)) from exc
    db.commit()
    return FileResponse(pdf_path, media_type="application/pdf", filename=f"{certificate.certificate_no}.pdf")


@router.post("/{certificate_id}/void", response_model=CertificateOut)
def void_certificate(
    certificate_id: int,
    db: Session = Depends(get_db),
    admin: AdminUser = Depends(require_roles("system_admin", "certificate_admin")),
) -> Certificate:
    certificate = db.get(Certificate, certificate_id)
    if not certificate:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Certificate not found")
    certificate.status = "voided"
    certificate.pdf_status = "not_generated"
    certificate.pdf_file_path = None
    learner = db.get(Learner, certificate.learner_id)
    log_action(db, admin, "void_certificate", "certificate", certificate.id, {"certificate_no": certificate.certificate_no, "learner_name": learner.current_name if learner else "", "previous_status": "valid", "status": "voided"})
    db.commit()
    db.refresh(certificate)
    return certificate


@router.post("/{certificate_id}/token/regenerate", response_model=CertificateOut)
def regenerate_public_token(
    certificate_id: int,
    db: Session = Depends(get_db),
    admin: AdminUser = Depends(require_roles("system_admin", "certificate_admin")),
) -> Certificate:
    certificate = db.get(Certificate, certificate_id)
    if not certificate:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Certificate not found")
    if certificate.public_token_id:
        old_token = db.get(CertificateAccessToken, certificate.public_token_id)
        if old_token:
            old_token.status = "revoked"
    certificate.public_token_id = _create_token(db, certificate.id, "public_link")
    learner = db.get(Learner, certificate.learner_id)
    log_action(
        db,
        admin,
        "regenerate_public_token",
        "certificate",
        certificate.id,
        {"certificate_no": certificate.certificate_no, "learner_name": learner.current_name if learner else ""},
    )
    db.commit()
    db.refresh(certificate)
    return certificate
