"""memo_task_state backfill — 기존 메모의 `- [x]` 항목에 checked_at 채우기.

feat(memo) auto-hide 배포 이전에 만들어진 note 들은 memo_task_state = '{}' 인 상태.
본문에 `- [x]` 로 저장된 체크 항목이 있어도 checked_at 이 없어 자동 숨김이 동작하지 않음.
이 스크립트는 모든 file_type='note' 행을 돌며 content 와 state 를 동기화한다
(배포 시각 = checked_at 으로 기록 → 배포 직후 10초 내 순차 숨김).

실행:
  docker compose exec fastapi python /app/scripts/backfill_memo_task_state.py --dry-run
  docker compose exec fastapi python /app/scripts/backfill_memo_task_state.py --apply
"""

import argparse
import asyncio
import os
import re
import sys
from datetime import datetime, timezone

from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

TASK_LINE_RE = re.compile(r"^(\s*- \[)([ xX])(\].*)$")


def sync_task_state(content: str, existing_state: dict | None, now_iso: str) -> dict:
    """memos.py 의 _sync_task_state_with_content 와 동일 로직 (스크립트 독립 실행용 복제)."""
    state = dict(existing_state or {})
    current_keys: set[str] = set()
    task_idx = 0
    for line in (content or "").split("\n"):
        m = TASK_LINE_RE.match(line)
        if not m:
            continue
        key = str(task_idx)
        is_checked = m.group(2).lower() == "x"
        if is_checked:
            current_keys.add(key)
            entry = state.get(key) or {}
            if not entry.get("checked_at"):
                state[key] = {"checked_at": now_iso}
        task_idx += 1
    for k in list(state.keys()):
        if k not in current_keys:
            state.pop(k, None)
    return state


async def run(apply: bool) -> int:
    database_url = os.getenv(
        "DATABASE_URL",
        "postgresql+asyncpg://pkm:pkm@postgres:5432/pkm",
    )
    engine = create_async_engine(database_url)
    session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

    now_iso = datetime.now(timezone.utc).isoformat()

    async with session_factory() as session:
        rows = (
            await session.execute(
                text(
                    """
                    SELECT id, extracted_text, memo_task_state
                      FROM documents
                     WHERE file_type = 'note'
                       AND deleted_at IS NULL
                    """
                )
            )
        ).all()
        print(f"총 note 대상 {len(rows)}건 스캔")

        to_update: list[tuple[int, dict, dict]] = []
        for r in rows:
            old_state = dict(r.memo_task_state or {})
            new_state = sync_task_state(r.extracted_text or "", old_state, now_iso)
            if new_state != old_state:
                to_update.append((r.id, old_state, new_state))

        print(f"변경 예정: {len(to_update)}건")
        sample = to_update[:5]
        for memo_id, old, new in sample:
            added = {k: v for k, v in new.items() if k not in old}
            removed = [k for k in old.keys() if k not in new]
            print(f"  id={memo_id} added={list(added.keys())} removed={removed}")

        if not apply:
            print("\n--dry-run (변경 없음). 실제 적용하려면 --apply")
            await engine.dispose()
            return 0

        if not to_update:
            print("변경 대상 없음.")
            await engine.dispose()
            return 0

        import json
        for memo_id, _old, new in to_update:
            await session.execute(
                text(
                    "UPDATE documents SET memo_task_state = CAST(:s AS jsonb) WHERE id = :id"
                ),
                {"s": json.dumps(new), "id": memo_id},
            )
        await session.commit()
        print(f"\n{len(to_update)}건 반영 완료 (checked_at={now_iso})")

    await engine.dispose()
    return 0


def main() -> int:
    p = argparse.ArgumentParser()
    g = p.add_mutually_exclusive_group(required=True)
    g.add_argument("--dry-run", action="store_true")
    g.add_argument("--apply", action="store_true")
    args = p.parse_args()
    return asyncio.run(run(apply=args.apply))


if __name__ == "__main__":
    sys.exit(main())
