from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import FileResponse
from pydantic import BaseModel, Field, model_validator
from sqlalchemy.orm import Session

from app.core.security import hash_token
from app.db.session import get_db
from app.models import Certificate, CertificateAccessToken, Learner, ProjectCourse
from app.services.captcha import make_captcha, verify_captcha
from app.services.logs import log_action, mask_phone
from app.services.pdf import PdfGenerationBusy, render_certificate_pdf
from app.services.rate_limit import hit_too_often
from app.services.sms import generate_sms_code, verify_sms_code
from app.services.system_settings import get_pdf_generation_concurrency_limit

router = APIRouter()

DISCLAIMER = "本证书仅用于证明学员完成相关培训课程/阶段学习，不代表国家学历、学位、职业资格或职业技能等级认证。"


class SendSmsRequest(BaseModel):
    phone: str = Field(min_length=1, max_length=32)
    captcha_id: str = Field(min_length=1, max_length=128)
    captcha_code: str = Field(min_length=1, max_length=16)


class CertificateSearchRequest(BaseModel):
    certificate_no: str | None = Field(default=None, max_length=64)
    phone: str | None = Field(default=None, max_length=32)
    name: str = Field(min_length=1, max_length=64)
    captcha_id: str = Field(min_length=1, max_length=128)
    captcha_code: str = Field(min_length=1, max_length=16)

    @model_validator(mode="after")
    def certificate_no_or_phone_required(self) -> "CertificateSearchRequest":
        if not (self.certificate_no or "").strip() and not (self.phone or "").strip():
            raise ValueError("请填写证书编号或手机号其中一项")
        return self


class SmsSearchRequest(BaseModel):
    phone: str = Field(min_length=1, max_length=32)
    sms_id: str = Field(min_length=1, max_length=128)
    sms_code: str = Field(min_length=1, max_length=16)


@router.get("/captcha")
def get_captcha() -> dict[str, str]:
    return make_captcha()


@router.post("/sms/send")
def send_sms_code(
    payload: SendSmsRequest,
    request: Request,
) -> dict[str, str]:
    """发送短信验证码"""
    client_ip = request.client.host if request.client else "unknown"
    if hit_too_often(f"sms:{client_ip}", limit=5, window_seconds=60):
        raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="发送过于频繁，请稍后再试")
    if hit_too_often(f"sms:{payload.phone}", limit=3, window_seconds=300):
        raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="该手机号发送过于频繁，请稍后再试")
    if not verify_captcha(payload.captcha_id, payload.captcha_code):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="图形验证码错误或已过期，请重新输入")
    
    sms_id = generate_sms_code(payload.phone)
    return {"sms_id": sms_id, "message": "验证码已发送"}


@router.post("/certificates/search")
def search_certificate(
    payload: CertificateSearchRequest,
    request: Request,
    db: Session = Depends(get_db),
) -> dict[str, object]:
    client_ip = request.client.host if request.client else "unknown"
    if hit_too_often(f"search:{client_ip}", limit=30, window_seconds=60):
        raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="访问过于频繁，请稍后再试")
    if not verify_captcha(payload.captcha_id, payload.captcha_code):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="验证码错误或已过期，请重新输入")

    name = payload.name.strip()
    certificate_no = (payload.certificate_no or "").strip().upper()
    phone = (payload.phone or "").strip()

    query = db.query(Certificate, Learner).join(Learner, Learner.id == Certificate.learner_id)
    if certificate_no:
        result = query.filter(Certificate.certificate_no == certificate_no).filter(Learner.current_name == name).all()
    else:
        result = (
            query.filter(Learner.phone == phone)
            .filter(Learner.current_name == name)
            .order_by(Certificate.issue_date.desc(), Certificate.id.desc())
            .all()
        )

    if not result:
        log_action(db, None, "public_search_certificate", "public_access", detail={"mode": "certificate_no" if certificate_no else "phone", "name": name, "certificate_no": certificate_no or None, "phone": mask_phone(phone), "matched_count": 0, "result": "not_found"})
        db.commit()
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="未查询到匹配的证书信息，请核对后重试")
    log_action(db, None, "public_search_certificate", "public_access", result[0][0].certificate_no, {"mode": "certificate_no" if certificate_no else "phone", "name": name, "certificate_no": certificate_no or None, "phone": mask_phone(phone), "matched_count": len(result), "result": "matched"})
    db.commit()
    return {"items": [public_certificate_payload(db, certificate, learner) for certificate, learner in result]}


@router.post("/certificates/search-by-sms")
def search_certificate_by_sms(
    payload: SmsSearchRequest,
    request: Request,
    db: Session = Depends(get_db),
) -> dict[str, object]:
    """通过手机号和短信验证码查询证书"""
    client_ip = request.client.host if request.client else "unknown"
    if hit_too_often(f"search_sms:{client_ip}", limit=30, window_seconds=60):
        raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="访问过于频繁，请稍后再试")
    
    if not verify_sms_code(payload.sms_id, payload.sms_code):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="验证码错误或已过期，请重新获取")

    phone = payload.phone.strip()

    query = db.query(Certificate, Learner).join(Learner, Learner.id == Certificate.learner_id)
    result = (
        query.filter(Learner.phone == phone)
        .order_by(Certificate.issue_date.desc(), Certificate.id.desc())
        .all()
    )

    if not result:
        log_action(db, None, "public_search_certificate_sms", "public_access", detail={"mode": "phone_sms", "phone": mask_phone(phone), "matched_count": 0, "result": "not_found"})
        db.commit()
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="未查询到匹配的证书信息，请核对手机号后重试")
    
    log_action(db, None, "public_search_certificate_sms", "public_access", result[0][0].certificate_no, {"mode": "phone_sms", "phone": mask_phone(phone), "matched_count": len(result), "result": "matched"})
    db.commit()
    return {"items": [public_certificate_payload(db, certificate, learner) for certificate, learner in result]}


@router.get("/certificates/token/{token}")
def get_certificate_by_token(token: str, db: Session = Depends(get_db)) -> dict[str, object]:
    access_token = get_active_token(db, token)
    certificate, learner = get_certificate_and_learner(db, access_token.certificate_id)
    return public_certificate_payload(db, certificate, learner)


@router.get("/certificates/token/{token}/download")
def download_certificate_pdf(token: str, db: Session = Depends(get_db)) -> FileResponse:
    access_token = get_active_token(db, token)
    certificate, learner = get_certificate_and_learner(db, access_token.certificate_id)
    if certificate.status != "valid":
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="证书已作废，不支持下载正常 PDF")

    project = db.query(ProjectCourse).filter(ProjectCourse.code == certificate.project_code).first()
    try:
        pdf_path = render_certificate_pdf(
            certificate,
            learner,
            project,
            access_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
    log_action(db, None, "public_download_certificate", "certificate", certificate.id, {"certificate_no": certificate.certificate_no, "learner_name": learner.current_name, "project_code": certificate.project_code})
    db.commit()
    return FileResponse(pdf_path, media_type="application/pdf", filename=f"{certificate.certificate_no}.pdf")


def public_certificate_payload(db: Session, certificate: Certificate, learner: Learner) -> dict[str, object]:
    token = db.get(CertificateAccessToken, certificate.public_token_id) if certificate.public_token_id else None
    token_value = token.token_value if token and token.status == "active" else None
    return {
        "certificate_no": certificate.certificate_no,
        "learner_name": learner.current_name,
        "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,
        "pdf_status": certificate.pdf_status,
        "public_token": token_value,
        "public_url": f"/cert/{token_value}" if token_value else None,
        "download_url": f"/api/public/certificates/token/{token_value}/download" if token_value and certificate.status == "valid" else None,
        "can_download_pdf": certificate.status == "valid" and bool(token_value),
        "disclaimer": DISCLAIMER,
    }


def get_active_token(db: Session, token: str) -> CertificateAccessToken:
    access_token = (
        db.query(CertificateAccessToken)
        .filter(CertificateAccessToken.token_hash == hash_token(token))
        .filter(CertificateAccessToken.status == "active")
        .first()
    )
    if not access_token:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="链接无效或已失效")
    return access_token


def get_certificate_and_learner(db: Session, certificate_id: int) -> tuple[Certificate, Learner]:
    result = (
        db.query(Certificate, Learner)
        .join(Learner, Learner.id == Certificate.learner_id)
        .filter(Certificate.id == certificate_id)
        .first()
    )
    if not result:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="证书不存在")
    return result
