"""기존 file_hash 중복 그룹 backfill — plan ds-s1-backend-1 B-4.

목적:
  A-1 migration 287 로 추가된 duplicate_of / duplicate_count 를 *기존* 중복 그룹에 채운다.
  migration(단일 트랜잭션)과 분리한 별 배치(database.py:29-30 정책 — 대량 UPDATE 를
  startup migration 에 넣지 않는다). 업로드 시점 채움(B-1)은 신규 행만 다루므로 과거는 이 스크립트.

판정:
  - file_hash exact 그룹(OFF-whitelist=law_monitor 제외, deleted 제외, count>1).
    near_duplicate 는 영속화 보류(on-the-fly) — 여기서 다루지 않는다.
  - canonical = 그룹 최古(min id). canonical.duplicate_of=NULL, duplicate_count=group_size-1.
  - 비-canonical 멤버 = duplicate_of=canonical, duplicate_count=0.

안전:
  - 멱등 — 이미 목표값인 행은 UPDATE 안 함(재실행 안전). --dry-run 이 적용될 정확한 set 미리보기.
  - --chunk(기본 500)행/txn 청크 커밋 — 28,941행 단일 트랜잭션 lock 회피.

실행:
  docker compose exec fastapi python /app/scripts/backfill_dedup.py --dry-run
  docker compose exec fastapi python /app/scripts/backfill_dedup.py --apply
  # 변경 전 안전망은 E-3 pre-B-4 pg_dump (별 단계).
"""

import argparse
import asyncio
import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "app"))

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

from services.dedup import reconcile_dedup  # 코어 재계산 (야간 잡과 공유)


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

    try:
        async with session_factory() as session:
            result = await reconcile_dedup(session, apply=apply, chunk_size=chunk_size)

            print(f"=== dedup 그룹 {result['groups']}개 · 관련 문서 {result['docs']}건 ===")
            if result["groups"] == 0:
                print("dedup 그룹 없음(OFF-whitelist 제외 후 count>1 없음) — 종료.")
                return 0

            already = result["docs"] - result["changes"]
            print(f"변경 필요 {result['changes']}건 / 이미 목표값 {already}건 (멱등)")
            if result["changes"] == 0:
                print("모두 목표값 — 적용할 변경 없음.")
                return 0

            # 적용될/된 정확한 UPDATE set 미리보기 (상위 40건)
            print("\n=== UPDATE set (id → duplicate_of / duplicate_count) ===")
            for s in result["sample"]:
                role = "canonical" if s["duplicate_of"] is None else f"dup→{s['duplicate_of']}"
                print(
                    f"  id={s['id']:>7}  duplicate_of={s['duplicate_of']}  "
                    f"duplicate_count={s['duplicate_count']}  [{role}]"
                )
            if result["changes"] > len(result["sample"]):
                print(f"  ... 외 {result['changes'] - len(result['sample'])}건")

            if not apply:
                print(f"\n[dry-run] {result['changes']}건 변경 예정. --apply 로 실제 적용.")
            else:
                print(f"\n[apply] 완료 — {result['applied']}건 갱신.")
            return 0
    finally:
        await engine.dispose()


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--apply", action="store_true", help="실제 적용 (기본 dry-run)")
    parser.add_argument("--dry-run", action="store_true", help="명시적 dry-run (default 동등)")
    parser.add_argument("--chunk", type=int, default=500, help="txn 당 UPDATE 행 수 (기본 500)")
    args = parser.parse_args()
    if args.apply and args.dry_run:
        parser.error("--apply 와 --dry-run 동시 지정 불가")
    return asyncio.run(run(apply=args.apply, chunk_size=args.chunk))


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