"""concept_curriculum — 이론공부 홈 재료 (오늘의 개념 · 진도 · 회독 SR).

개념문서 = documents (user_tags = @library/{topic}/{과목}/... , 가스기사). is_read = 회독,
md_content 의 ★ 개수 = 빈출 tier(★★★=3 / ★★=2 / else 1). 회독 SR = study_concept_progress
+ sr_schedule(문제 SR 공용 산술). 읽기 전용 집계 + mark_read(회독+SR 입고)만 write. LLM 0.

문제풀이 표면 무접촉 — 여기서 읽는 study_question_progress 는 '문항 due 카운트'만(홈 표시용).
"""

from __future__ import annotations

from datetime import datetime, timezone

from sqlalchemy import func, or_, select, text
from sqlalchemy.ext.asyncio import AsyncSession

from models.document_read import DocumentRead
from models.study_concept_progress import StudyConceptProgress
from models.study_question_progress import StudyQuestionProgress
from models.study_topic import StudyTopic
from services.study.concept_parser import parse_concept, resolve_related
from services.study.sr_schedule import advance, first_due

# 개념 행 조회 — 태그로 개념문서 필터 + 회독 진행 LEFT JOIN. md_content 는 전송 안 하고
# ★ 유무만 서버측 boolean 으로(홈이 자주 호출돼도 페이로드 최소).
# is_read = document_reads(회독 정본, is_read 컬럼 아님) EXISTS. library unread 와 동일 기준.
_CONCEPT_ROWS_SQL = text(
    """
    SELECT d.id AS doc_id,
           d.title AS title,
           EXISTS (
             SELECT 1 FROM document_reads r
             WHERE r.document_id = d.id AND r.user_id = :uid
           ) AS is_read,
           (d.md_content LIKE '%★★★%') AS f3,
           (d.md_content LIKE '%★★%')  AS f2,
           split_part(replace(d.user_tags::text, '"', ''), '/', 3) AS subject,
           p.review_stage AS review_stage,
           p.due_at AS due_at,
           p.last_read_at AS last_read_at
    FROM documents d
    LEFT JOIN study_concept_progress p
      ON p.concept_doc_id = d.id AND p.user_id = :uid
    WHERE d.user_tags::text LIKE :like
      AND d.deleted_at IS NULL
    """
)


async def _topic_name(session: AsyncSession, topic_id: int) -> str | None:
    return (
        await session.execute(select(StudyTopic.name).where(StudyTopic.id == topic_id))
    ).scalar_one_or_none()


async def _concept_rows(session: AsyncSession, user_id: int, topic_name: str):
    like = f"%@library/{topic_name}/%"
    return (
        await session.execute(_CONCEPT_ROWS_SQL, {"uid": user_id, "like": like})
    ).mappings().all()


def _freq(row) -> int:
    if row["f3"]:
        return 3
    if row["f2"]:
        return 2
    return 1


def _is_due(row, now: datetime) -> bool:
    return (
        row["due_at"] is not None
        and row["due_at"] <= now
        and (row["review_stage"] or 0) < 4
    )


def _item(row) -> dict:
    return {
        "doc_id": row["doc_id"],
        "title": row["title"],
        "subject": row["subject"],
        "freq": _freq(row),
        "review_stage": row["review_stage"],
        "due_at": row["due_at"],
    }


async def _question_due_count(session: AsyncSession, user_id: int, topic_id: int, now: datetime) -> int:
    """문항 복습 due (기존 study_question_progress 엔진 재사용, 홈 표시용)."""
    return (
        await session.execute(
            select(func.count())
            .select_from(StudyQuestionProgress)
            .where(
                StudyQuestionProgress.user_id == user_id,
                StudyQuestionProgress.study_topic_id == topic_id,
                StudyQuestionProgress.due_at.is_not(None),
                StudyQuestionProgress.due_at <= now,
                or_(
                    StudyQuestionProgress.review_stage.is_(None),
                    StudyQuestionProgress.review_stage < 4,
                ),
            )
        )
    ).scalar_one()


async def curriculum(session: AsyncSession, user_id: int, topic_id: int) -> dict:
    """과목별 회독 진도 + 개념/문항 복습 due 요약 (진도 대시보드)."""
    name = await _topic_name(session, topic_id)
    rows = await _concept_rows(session, user_id, name) if name else []
    now = datetime.now(timezone.utc)

    subj: dict[str, dict] = {}
    for r in rows:
        s = subj.setdefault(r["subject"], {"subject": r["subject"], "total": 0, "read": 0})
        s["total"] += 1
        if r["is_read"]:
            s["read"] += 1

    total = len(rows)
    read = sum(1 for r in rows if r["is_read"])
    concept_due = sum(1 for r in rows if _is_due(r, now))
    question_due = await _question_due_count(session, user_id, topic_id, now)

    return {
        "topic_id": topic_id,
        "topic_name": name,
        "subjects": sorted(subj.values(), key=lambda x: x["subject"]),
        "total": total,
        "read": read,
        "concept_due": concept_due,
        "question_due": question_due,
    }


async def today_concepts(
    session: AsyncSession, user_id: int, topic_id: int, limit: int = 6
) -> dict:
    """오늘 공부할 개념 = 재복습(SR due) 먼저 → 미독(빈출 우선). 졸업/재복습대기 제외."""
    name = await _topic_name(session, topic_id)
    rows = await _concept_rows(session, user_id, name) if name else []
    now = datetime.now(timezone.utc)

    due = [r for r in rows if _is_due(r, now)]
    due.sort(key=lambda r: r["due_at"])

    # 미독 & 아직 SR 큐 진입 전(due_at NULL) → 빈출 높은 순
    unread = [r for r in rows if not r["is_read"] and r["due_at"] is None]
    unread.sort(key=lambda r: (-_freq(r), r["subject"], r["title"]))

    picked = [{**_item(r), "reason": "재복습"} for r in due]
    picked += [{**_item(r), "reason": "신규"} for r in unread]

    return {
        "concepts": picked[:limit],
        "due_total": len(due),
        "unread_total": len(unread),
    }


async def mark_read(
    session: AsyncSession, user_id: int, topic_id: int, doc_id: int, now: datetime | None = None
) -> dict:
    """개념 회독 처리 = document_reads(+1) + 회독 SR 입고/전진.

    회독 정본 = document_reads(append-only), documents.is_read 컬럼 아님(library unread 와 정합).
    첫 회독 → first_due(stage 0, 내일). 이후 회독은 'due 도래(due_at<=now)' 때만 correct 로 전진
    (이른 재열람/다중클릭 과전진 방지). stage 4 졸업 후엔 due_at NULL 이라 전진 없음.
    """
    now = now or datetime.now(timezone.utc)

    # 회독 로그 append (+1) — 사용자 명시 회독. 자동 아님(엔드포인트 = 명시 POST).
    session.add(DocumentRead(user_id=user_id, document_id=doc_id, read_at=now))

    prog = (
        await session.execute(
            select(StudyConceptProgress).where(
                StudyConceptProgress.user_id == user_id,
                StudyConceptProgress.concept_doc_id == doc_id,
            )
        )
    ).scalar_one_or_none()

    if prog is None:
        stage, due = first_due(now)
        prog = StudyConceptProgress(
            user_id=user_id,
            study_topic_id=topic_id,
            concept_doc_id=doc_id,
            review_stage=stage,
            due_at=due,
            last_read_at=now,
        )
        session.add(prog)
    else:
        # due 도래 시에만 전진 — 미래 due(재열람 이른 클릭)는 stage 불변, last_read_at 만 갱신.
        if prog.due_at is not None and prog.due_at <= now:
            res = advance(prog.review_stage, "correct", now)
            if res is not None:
                prog.review_stage, prog.due_at = res
        prog.last_read_at = now

    await session.commit()
    await session.refresh(prog)
    return {"ok": True, "review_stage": prog.review_stage, "due_at": prog.due_at}


_CONCEPT_ONE_SQL = text(
    """
    SELECT d.id AS doc_id, d.title AS title, d.md_content AS md_content,
           split_part(replace(d.user_tags::text, '"', ''), '/', 3) AS subject,
           (d.md_content LIKE '%★★★%') AS f3,
           (d.md_content LIKE '%★★%')  AS f2,
           EXISTS (
             SELECT 1 FROM document_reads r
             WHERE r.document_id = d.id AND r.user_id = :uid
           ) AS is_read,
           p.review_stage AS review_stage,
           p.due_at AS due_at
    FROM documents d
    LEFT JOIN study_concept_progress p ON p.concept_doc_id = d.id AND p.user_id = :uid
    WHERE d.id = :doc_id AND d.deleted_at IS NULL AND d.user_tags::text LIKE :like
    """
)


async def concept_detail(
    session: AsyncSession, user_id: int, topic_id: int, doc_id: int
) -> dict | None:
    """개념 리더 재료 — md 구조 파싱 + 관련개념 백링크 해소 + 회독/SR 상태 + 같은 과목 이전/다음."""
    name = await _topic_name(session, topic_id)
    if not name:
        return None
    like = f"%@library/{name}/%"
    row = (
        await session.execute(
            _CONCEPT_ONE_SQL, {"uid": user_id, "doc_id": doc_id, "like": like}
        )
    ).mappings().first()
    if row is None:
        return None

    parsed = parse_concept(row["md_content"] or "")

    # 백링크 해소 + 이전/다음 = 같은 토픽 개념 title 인덱스(회독 rows 재사용)
    idx = await _concept_rows(session, user_id, name)
    title_index = [(r["doc_id"], r["title"], r["subject"]) for r in idx]
    resolved = resolve_related(parsed["related"], title_index)

    # 이전/다음 = 같은 과목, title 순
    same = sorted(
        [(r["doc_id"], r["title"]) for r in idx if r["subject"] == row["subject"]],
        key=lambda x: (x[1] or "", x[0]),
    )
    ids = [d for d, _ in same]
    prev_id = next_id = None
    if doc_id in ids:
        pos = ids.index(doc_id)
        if pos > 0:
            prev_id = ids[pos - 1]
        if pos < len(ids) - 1:
            next_id = ids[pos + 1]

    freq = 3 if row["f3"] else (2 if row["f2"] else 1)

    return {
        "doc_id": row["doc_id"],
        "db_title": row["title"],
        "title": parsed["title"] or row["title"],
        "subject": row["subject"],
        "freq": freq,
        "summary": parsed["summary"],
        "body": parsed["body"],
        "bincheol": parsed["bincheol"],
        "related": resolved,
        "is_read": row["is_read"],
        "review_stage": row["review_stage"],
        "due_at": row["due_at"],
        "prev_id": prev_id,
        "next_id": next_id,
    }
