#!/usr/bin/env python3
"""rollback_wiki_sync.py - wiki__ 프리픽스 문서 롤백 스크립트

Usage:
  python3 rollback_wiki_sync.py --dry-run
  python3 rollback_wiki_sync.py --execute
  python3 rollback_wiki_sync.py --execute --also-wiki-collection
"""

import argparse
import json
import logging
import os
import sys
from datetime import datetime, timezone
from typing import Any

import firebase_admin
from firebase_admin import credentials, firestore

# ---------------------------------------------------------------------------
# 상수
# ---------------------------------------------------------------------------
DOCUMENTS_COLLECTION = "documents"
WIKI_COLLECTION = "wiki"
WIKI_PREFIX = "wiki__"
BATCH_SIZE = 400  # WriteBatch 한도(500) 대비 안전 마진

DEFAULT_CRED_PATH = os.environ.get(
    "GOOGLE_APPLICATION_CREDENTIALS",
    os.path.join(
        os.path.dirname(__file__),
        "../temp.j2h/insuwiki-j2h-firebase-adminsdk-fbsvc-b6203cbf51.json",
    ),
)
FIREBASE_PROJECT_ID = "insuwiki-j2h"

# ---------------------------------------------------------------------------
# 로깅
# ---------------------------------------------------------------------------
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s  %(levelname)-8s  %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger("rollback_wiki_sync")


# ---------------------------------------------------------------------------
# Firebase 초기화
# ---------------------------------------------------------------------------
def init_firebase(cred_path: str) -> Any:
    """Firebase Admin SDK를 초기화하고 Firestore 클라이언트를 반환합니다."""
    if not cred_path or not os.path.isfile(cred_path):
        log.error(
            "서비스 계정 JSON 파일을 찾을 수 없습니다: %s\n"
            "  환경변수 GOOGLE_APPLICATION_CREDENTIALS 또는 --cred 옵션으로 경로를 지정하세요.",
            cred_path,
        )
        sys.exit(1)

    if not firebase_admin._apps:
        cred = credentials.Certificate(cred_path)
        firebase_admin.initialize_app(cred, {"projectId": FIREBASE_PROJECT_ID})
        log.info("Firebase 초기화 완료 (project: %s)", FIREBASE_PROJECT_ID)
    else:
        log.info("Firebase 이미 초기화됨 — 기존 앱 재사용")

    return firestore.client()


# ---------------------------------------------------------------------------
# 문서 조회
# ---------------------------------------------------------------------------
def fetch_wiki_prefix_docs(db: Any, collection: str) -> list[dict[str, Any]]:
    """지정 컬렉션에서 wiki__ 프리픽스 문서를 전체 조회합니다."""
    log.info("[%s] wiki__ 프리픽스 문서 조회 중...", collection)
    try:
        docs = db.collection(collection).stream()
    except Exception as exc:
        log.error("[%s] Firestore 조회 실패: %s", collection, exc)
        return []
    results: list[dict[str, Any]] = []
    for doc in docs:
        if doc.id.startswith(WIKI_PREFIX):
            data = doc.to_dict() or {}
            results.append({"id": doc.id, "collection": collection, "data": data})
    log.info("[%s] 조회 완료: %d건", collection, len(results))
    if collection == WIKI_COLLECTION and len(results) == 0:
        log.warning(
            "[%s] wiki 컬렉션 문서는 wiki__ 프리픽스를 사용하지 않습니다. "
            "wiki 컬렉션 롤백은 sync-status.json 기반 역매핑이 필요합니다.",
            collection,
        )
    return results


# ---------------------------------------------------------------------------
# 백업
# ---------------------------------------------------------------------------
def save_backup(targets: list[dict[str, Any]], timestamp: str) -> str:
    """삭제 대상 문서를 로컬 JSON 파일로 백업합니다."""
    backup_path = os.path.join(
        os.path.dirname(__file__),
        f"rollback_backup_{timestamp}.json",
    )

    # Firestore Timestamp 같은 직렬화 불가 객체를 문자열로 변환
    def default_serializer(obj: Any) -> str:
        return str(obj)

    with open(backup_path, "w", encoding="utf-8") as f:
        json.dump(targets, f, ensure_ascii=False, indent=2, default=default_serializer)

    log.info("백업 파일 생성: %s  (%d건)", backup_path, len(targets))
    return backup_path


# ---------------------------------------------------------------------------
# 배치 삭제
# ---------------------------------------------------------------------------
def delete_in_batches(
    db: Any,
    targets: list[dict[str, Any]],
) -> tuple[int, int]:
    """400건 단위 WriteBatch로 문서를 삭제합니다.

    Returns:
        (success_count, failure_count) 튜플
    """
    success = 0
    failure = 0

    for batch_start in range(0, len(targets), BATCH_SIZE):
        chunk = targets[batch_start : batch_start + BATCH_SIZE]
        batch = db.batch()
        for item in chunk:
            ref = db.collection(item["collection"]).document(item["id"])
            batch.delete(ref)

        try:
            batch.commit()
            for item in chunk:
                log.info("  삭제됨: [%s] %s", item["collection"], item["id"])
            success += len(chunk)
        except Exception as exc:  # pylint: disable=broad-except
            log.error(
                "배치 커밋 실패 (batch_start=%d): %s", batch_start, exc
            )
            for item in chunk:
                log.error("  실패 문서: [%s] %s", item["collection"], item["id"])
            failure += len(chunk)

    return success, failure


# ---------------------------------------------------------------------------
# 메인 로직
# ---------------------------------------------------------------------------
def run(
    *,
    dry_run: bool,
    also_wiki_collection: bool,
    cred_path: str,
) -> None:
    started_at = datetime.now(timezone.utc)
    timestamp = started_at.strftime("%Y%m%d_%H%M%S")
    log.info("=" * 60)
    log.info("rollback_wiki_sync 시작: %s", started_at.isoformat())
    log.info("모드: %s", "DRY-RUN (실제 삭제 없음)" if dry_run else "EXECUTE (실제 삭제)")
    log.info("=" * 60)

    # --- Firebase 초기화 ---
    db = init_firebase(cred_path)

    # --- 삭제 대상 수집 ---
    targets: list[dict[str, Any]] = []
    targets.extend(fetch_wiki_prefix_docs(db, DOCUMENTS_COLLECTION))

    if also_wiki_collection:
        targets.extend(fetch_wiki_prefix_docs(db, WIKI_COLLECTION))

    if not targets:
        log.info("삭제 대상 문서가 없습니다. 종료합니다.")
        return

    # --- 목록 출력 ---
    log.info("-" * 60)
    log.info("삭제 대상 문서 목록 (총 %d건):", len(targets))
    for item in targets:
        log.info("  [%s] %s", item["collection"], item["id"])
    log.info("-" * 60)

    if dry_run:
        log.info("DRY-RUN 모드 — 실제 삭제는 수행하지 않습니다.")
        log.info("실제 삭제를 실행하려면 --execute 옵션을 사용하세요.")
        _print_summary(started_at, total=len(targets), success=0, failure=0, dry_run=True)
        return

    # --- 사용자 확인 프롬프트 ---
    print()
    print(f"[경고] {len(targets)}건의 문서를 영구 삭제합니다.")
    if also_wiki_collection:
        print("       대상 컬렉션: documents, wiki")
    else:
        print("       대상 컬렉션: documents")
    print("       이 작업은 되돌릴 수 없습니다 (백업 JSON은 생성됩니다).")
    print()

    try:
        answer = input("계속 진행하시겠습니까? (y/N): ").strip().lower()
    except (EOFError, KeyboardInterrupt):
        print()
        log.info("사용자가 취소했습니다.")
        sys.exit(0)

    if answer != "y":
        log.info("사용자가 취소했습니다.")
        sys.exit(0)

    # --- 백업 저장 ---
    try:
        backup_path = save_backup(targets, timestamp)
        log.info("백업 저장 완료: %s", backup_path)
    except Exception as exc:  # pylint: disable=broad-except
        log.error("백업 저장 실패: %s — 안전을 위해 삭제를 중단합니다.", exc)
        sys.exit(1)

    # --- 실제 삭제 ---
    log.info("삭제 시작...")
    success, failure = delete_in_batches(db, targets)

    _print_summary(
        started_at,
        total=len(targets),
        success=success,
        failure=failure,
        dry_run=False,
    )

    if failure > 0:
        sys.exit(1)


def _print_summary(
    started_at: datetime,
    *,
    total: int,
    success: int,
    failure: int,
    dry_run: bool,
) -> None:
    finished_at = datetime.now(timezone.utc)
    elapsed = (finished_at - started_at).total_seconds()
    log.info("=" * 60)
    log.info("rollback_wiki_sync 종료: %s", finished_at.isoformat())
    log.info("소요 시간: %.1f초", elapsed)
    log.info("처리 결과:")
    log.info("  대상 합계 : %d건", total)
    if dry_run:
        log.info("  (DRY-RUN — 실제 삭제 없음)")
    else:
        log.info("  성공      : %d건", success)
        log.info("  실패      : %d건", failure)
    log.info("=" * 60)


# ---------------------------------------------------------------------------
# CLI 진입점
# ---------------------------------------------------------------------------
def main() -> None:
    parser = argparse.ArgumentParser(
        description="Firestore documents 컬렉션에서 wiki__ 프리픽스 문서를 롤백(삭제)합니다.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__,
    )

    mode_group = parser.add_mutually_exclusive_group()
    mode_group.add_argument(
        "--dry-run",
        action="store_true",
        default=True,
        help="삭제 대상 목록만 출력하고 실제 삭제는 하지 않습니다 (기본값)",
    )
    mode_group.add_argument(
        "--execute",
        action="store_true",
        default=False,
        help="실제 삭제를 실행합니다",
    )
    parser.add_argument(
        "--also-wiki-collection",
        action="store_true",
        default=False,
        help="documents 컬렉션 외에 wiki 컬렉션의 대응 문서도 함께 삭제합니다",
    )
    parser.add_argument(
        "--cred",
        metavar="PATH",
        default=DEFAULT_CRED_PATH,
        help=(
            "Firebase 서비스 계정 JSON 파일 경로 "
            "(기본값: GOOGLE_APPLICATION_CREDENTIALS 환경변수 또는 기본 경로)"
        ),
    )

    args = parser.parse_args()

    # --execute 가 명시되면 dry_run=False
    dry_run = not args.execute

    run(
        dry_run=dry_run,
        also_wiki_collection=args.also_wiki_collection,
        cred_path=args.cred,
    )


if __name__ == "__main__":
    main()
