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
from app.services.certificate_number import build_certificate_no
from app.services.logs import log_action
from app.services.pdf import PdfGenerationBusy, render_certificate_pdf
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"),
    db: Session = Depends(get_db),
    _: AdminUser = Depends(require_roles("system_admin", "certificate_admin", "readonly")),
) -> list[Certificate]:
    query = db.query(Certificate).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),
            )
        )
    if status_value:
        query = query.filter(Certificate.status == status_value)
    return query.limit(100).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:
    learner = db.get(Learner, payload.learner_id)
    if not learner:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Learner not found")
    project = db.query(ProjectCourse).filter(ProjectCourse.code == payload.project_code, ProjectCourse.status == "active").first()
    if not project:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Project code is inactive or missing")

    certificate = Certificate(
        learner_id=payload.learner_id,
        project_code=payload.project_code,
        certificate_no="PENDING",
        certificate_name=project.name,
        class_name=payload.class_name,
        course_name=payload.course_name or project.default_course_name or project.name,
        stage_name=payload.stage_name or project.default_stage_name,
        issue_date=payload.issue_date,
        issuer_name=payload.issuer_name or project.default_issuer_name,
        remark=payload.remark,
    )
    db.add(certificate)
    db.flush()
    certificate.certificate_no = build_certificate_no(certificate.id, certificate.project_code, certificate.issue_date)
    certificate.public_token_id = _create_token(db, certificate.id, "public_link")
    certificate.qr_token_id = _create_token(db, certificate.id, "qr_verify")
    log_action(db, admin, "create_certificate", "certificate", certificate.id, {"certificate_no": certificate.certificate_no, "learner_name": learner.current_name, "project_code": certificate.project_code, "issue_date": certificate.issue_date})
    db.commit()
    db.refresh(certificate)
    return certificate


@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


@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 "",
        "certificate_name": certificate.certificate_name,
        "project_code": certificate.project_code,
        "course_name": certificate.course_name,
        "stage_name": certificate.stage_name,
        "issue_date": certificate.issue_date.isoformat(),
        "issuer_name": certificate.issuer_name,
        "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
