from dataclasses import dataclass
from datetime import date

from sqlalchemy.orm import Session

from app.core.security import generate_public_token, hash_token
from app.models import Certificate, CertificateAccessToken, Learner, ProjectCourse
from app.services.certificate_number import build_certificate_no
from app.services.certificate_templates import certificate_field_keys, get_certificate_template
from app.services.learner_identity import resolve_learner


class DuplicateCertificate(ValueError):
    def __init__(self, certificate: Certificate):
        self.certificate = certificate
        super().__init__(f"已存在相同证书：{certificate.certificate_no}")


@dataclass(frozen=True)
class CertificateIssueData:
    learner_name: str
    learner_phone: str
    project_code: str
    template_code: str
    issue_date: date
    course_start_date: date | None = None
    course_end_date: date | None = None
    course_name: str | None = None
    stage_name: str | None = None
    class_name: str | None = None
    remark: str | None = None
    import_batch_id: int | None = None


def issue_certificate(db: Session, data: CertificateIssueData, *, source: str) -> tuple[Certificate, Learner, bool]:
    template = get_certificate_template(data.template_code)
    project_code = data.project_code.strip().upper()
    project = db.query(ProjectCourse).filter(ProjectCourse.code == project_code, ProjectCourse.status == "active").first()
    if not project:
        raise ValueError(f"项目代码不存在或已停用：{project_code}")

    values = _template_values(data, project)
    _validate_template_values(template, values)
    learner, learner_created = resolve_learner(
        db,
        data.learner_name,
        data.learner_phone,
        source=source,
        create_if_missing=True,
    )
    assert learner is not None
    duplicate = find_duplicate_certificate(db, learner.id, project_code, template.code, values)
    if duplicate:
        raise DuplicateCertificate(duplicate)

    certificate = Certificate(
        learner_id=learner.id,
        import_batch_id=data.import_batch_id,
        project_code=project_code,
        certificate_no="PENDING",
        certificate_name=project.default_certificate_name or project.name,
        class_name=data.class_name,
        course_name=values["course_name"],
        stage_name=values["stage_name"],
        course_start_date=values["course_start_date"],
        course_end_date=values["course_end_date"],
        issue_date=data.issue_date,
        template_code=template.code,
        remark=data.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_access_token(db, certificate.id, "public_link")
    certificate.qr_token_id = _create_access_token(db, certificate.id, "qr_verify")
    return certificate, learner, learner_created


def find_duplicate_certificate(
    db: Session,
    learner_id: int,
    project_code: str,
    template_code: str,
    values: dict[str, object],
) -> Certificate | None:
    return (
        db.query(Certificate)
        .filter(Certificate.learner_id == learner_id)
        .filter(Certificate.project_code == project_code)
        .filter(Certificate.template_code == template_code)
        .filter(Certificate.course_name == values["course_name"])
        .filter(Certificate.stage_name == values["stage_name"])
        .filter(Certificate.course_start_date == values["course_start_date"])
        .filter(Certificate.course_end_date == values["course_end_date"])
        .filter(Certificate.issue_date == values["issue_date"])
        .filter(Certificate.status == "valid")
        .first()
    )


def _template_values(data: CertificateIssueData, project: ProjectCourse) -> dict[str, object]:
    template = get_certificate_template(data.template_code)
    keys = certificate_field_keys(template)
    return {
        "course_name": (data.course_name or project.default_course_name) if "course_name" in keys else None,
        "stage_name": (data.stage_name or project.default_stage_name) if "stage_name" in keys else None,
        "course_start_date": data.course_start_date if "course_start_date" in keys else None,
        "course_end_date": data.course_end_date if "course_end_date" in keys else None,
        "issue_date": data.issue_date,
    }


def _validate_template_values(template, values: dict[str, object]) -> None:
    for field in template.fields:
        if field.key == "learner_name":
            continue
        if field.required and not values.get(field.key):
            raise ValueError(f"{template.name}需要填写{field.label}")
    start_date = values.get("course_start_date")
    end_date = values.get("course_end_date")
    if start_date and end_date and end_date < start_date:
        raise ValueError("课程结束日期不能早于课程开始日期")


def _create_access_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
