"""study_reminder — focus 토픽 due 요약 cron (공부 암기노트 Phase 1, A 워크스트림).

09/13/19 KST 발화(main.py CronTrigger). '공부중'(focused_at IS NOT NULL) 토픽별 복습 due
건수를 집계해 study_reminders 에 append. LLM 0 (순수 집계 → GPU 분석 측).

due 술어는 quiz_selection.py:141 의 due_review 와 동일하게 SQL 로 재현:
  due_at IS NOT NULL AND due_at <= now AND (review_stage IS NULL OR review_stage < 4)
  (= Python `(review_stage or 0) < 4` 와 NULL 의미 동일).
quiz_selection 은 단일 토픽 ORM 순회라 import 불가 → 재현 + 측정 등가성 게이트(테스트).

fired_at 은 시간 슬롯(분/초 절삭)으로 박아 UNIQUE(user, fired_at) on_conflict_do_nothing 멱등.
due 0 이면 row 미생성(noise 방지). 놓친 시각은 그냥 skip(stale 복구 미적용 — 시각 민감).
"""

from __future__ import annotations

import logging
from collections import defaultdict
from datetime import datetime, timezone

from sqlalchemy import func, or_, select
from sqlalchemy.dialects.postgresql import insert as pg_insert

from core.database import async_session
from models.study_question_progress import StudyQuestionProgress
from models.study_reminder import StudyReminder
from models.study_topic import StudyTopic
from models.user import User  # noqa: F401  (mapper 초기화 defensive)

logger = logging.getLogger("study_reminder")


async def run() -> None:
    """APScheduler cron 진입점. focus 토픽 due 집계 → study_reminders append."""
    now = datetime.now(timezone.utc)
    slot = now.replace(minute=0, second=0, microsecond=0)  # 시간 슬롯 truncate (멱등 키)

    async with async_session() as session:
        topics = (
            await session.execute(
                select(StudyTopic.id, StudyTopic.user_id, StudyTopic.name)
                .where(
                    StudyTopic.focused_at.is_not(None),
                    StudyTopic.deleted_at.is_(None),
                )
            )
        ).all()
        if not topics:
            return

        by_user: dict[int, dict] = defaultdict(lambda: {"due": 0, "names": []})
        for t in topics:
            due = (
                await session.execute(
                    select(func.count())
                    .select_from(StudyQuestionProgress)
                    .where(
                        StudyQuestionProgress.user_id == t.user_id,
                        StudyQuestionProgress.study_topic_id == t.id,
                        StudyQuestionProgress.due_at.is_not(None),
                        StudyQuestionProgress.due_at <= now,
                        or_(
                            StudyQuestionProgress.review_stage.is_(None),
                            StudyQuestionProgress.review_stage < 4,
                        ),
                    )
                )
            ).scalar_one()
            by_user[t.user_id]["due"] += due
            by_user[t.user_id]["names"].append(
                {"topic_id": t.id, "name": t.name, "due": due}
            )

        inserted = 0
        for uid, agg in by_user.items():
            if agg["due"] <= 0:
                continue  # due 0 → reminder 미생성
            result = await session.execute(
                pg_insert(StudyReminder)
                .values(
                    user_id=uid,
                    study_topic_id=None,
                    due_count=agg["due"],
                    focus_topic_names=agg["names"],
                    fired_at=slot,
                )
                .on_conflict_do_nothing(index_elements=["user_id", "fired_at"])
            )
            inserted += result.rowcount or 0
        await session.commit()
        if inserted:
            logger.info("study_reminder fired slot=%s users=%d", slot.isoformat(), inserted)
