"""study_card_enqueue — 버전키 폴러 (공부 암기노트 Phase 1).

ready ai_explanation 인데 '현재 버전' card_extract job 이 없는 question 을 enqueue.
버전 멱등(핵심): NOT EXISTS(job WHERE source_kind='question' AND source_id=q.id
                            AND source_version=q.ai_explanation_generated_at)
  - 같은 버전 재추출 차단 — completed/all_dropped job 도 현 버전에 존재하면 재enqueue 0(livelock 방지).
  - explanation 재생성(새 generated_at)이면 새 버전 job 부재 → 자동 재추출(정정-stale 해소).
NULL 가드: ai_explanation_generated_at IS NOT NULL 전제 — NULL 이면 NULL=NULL=UNKNOWN 으로
  NOT EXISTS 가 항상 참이 되어 매 폴 재enqueue 폭주. ready 전이 직후 race 를 이 가드가 막는다.
thundering-herd: per-poll LIMIT(CARD_ENQUEUE_BATCH) + 최근(generated_at desc) 우선으로 backfill 완만.
"""

from __future__ import annotations

import logging

from sqlalchemy import select

from core.config import settings
from core.database import async_session
from models.study_memo_card_job import StudyMemoCardJob, enqueue_study_memo_card_job
from models.study_question import StudyQuestion

logger = logging.getLogger("study_card_enqueue")

CARD_ENQUEUE_BATCH = 20
SOURCE_KIND_QUESTION = "question"


async def run() -> None:
    """APScheduler 진입점. ready & 현 버전 job 부재 question 을 BATCH 만큼 enqueue."""
    if not getattr(settings, "study_card_extract_enabled", True):
        return

    async with async_session() as session:
        # 현재 ai_explanation_generated_at 버전에 대한 job 이 이미 있는지 (correlated NOT EXISTS).
        job_exists = (
            select(StudyMemoCardJob.id)
            .where(
                StudyMemoCardJob.source_kind == SOURCE_KIND_QUESTION,
                StudyMemoCardJob.source_id == StudyQuestion.id,
                StudyMemoCardJob.source_version == StudyQuestion.ai_explanation_generated_at,
            )
            .exists()
        )
        rows = (
            await session.execute(
                select(
                    StudyQuestion.id,
                    StudyQuestion.user_id,
                    StudyQuestion.ai_explanation_generated_at,
                )
                .where(
                    StudyQuestion.deleted_at.is_(None),
                    StudyQuestion.ai_explanation_status == "ready",
                    StudyQuestion.ai_explanation_generated_at.is_not(None),
                    ~job_exists,
                )
                .order_by(StudyQuestion.ai_explanation_generated_at.desc())
                .limit(CARD_ENQUEUE_BATCH)
            )
        ).all()
        if not rows:
            return

        enqueued = 0
        for r in rows:
            ok = await enqueue_study_memo_card_job(
                session,
                user_id=r.user_id,
                source_kind=SOURCE_KIND_QUESTION,
                source_id=r.id,
                source_version=r.ai_explanation_generated_at,
                kind="card_extract",
            )
            if ok:
                enqueued += 1
        await session.commit()
        if enqueued:
            logger.info("study_card_enqueue candidates=%d enqueued=%d", len(rows), enqueued)
