# -*- coding: utf-8 -*-
"""
STEP107-01 Deleted Article Sync

현재 네이버 중개사 매물 목록에 없는 published article_no를 삭제매물로 판단하고,
blog_post_private_queue에 비공개 작업을 중복 없이 등록한다.
"""

import sys
import json
from pathlib import Path
from datetime import datetime

BASE_DIR = Path(__file__).resolve().parents[1]
if str(BASE_DIR) not in sys.path:
    sys.path.append(str(BASE_DIR))

from db import get_conn


def clean_text(value):
    return str(value or "").strip()


def log(*args):
    print(*args, flush=True)


def table_exists(conn, table_name):
    try:
        with conn.cursor() as cur:
            cur.execute("SHOW TABLES LIKE %s", (table_name,))
            return cur.fetchone() is not None
    except Exception:
        return False


def table_columns(conn, table_name):
    try:
        with conn.cursor() as cur:
            cur.execute(f"SHOW COLUMNS FROM {table_name}")
            rows = cur.fetchall()
        return {row["Field"] for row in rows}
    except Exception:
        return set()


def load_realtor(conn, realtor_id):
    with conn.cursor() as cur:
        cur.execute("SELECT * FROM blog_realtors WHERE id=%s LIMIT 1", (int(realtor_id),))
        row = cur.fetchone()
    if not row:
        raise RuntimeError(f"realtor not found: {realtor_id}")
    return row


def fetch_current_article_nos_from_db(conn, realtor_id):
    """
    최신 수집 결과를 현재 네이버 매물 목록으로 사용한다.
    collect 직후 실행되는 것을 전제로 한다.
    """
    if not table_exists(conn, "blog_realtor_articles"):
        return []

    cols = table_columns(conn, "blog_realtor_articles")
    if "article_no" not in cols:
        return []

    where = ["realtor_id=%s"]
    params = [int(realtor_id)]

    if "is_deleted" in cols:
        where.append("COALESCE(is_deleted,0)=0")
    if "is_active" in cols:
        where.append("COALESCE(is_active,1)=1")
    if "collect_status" in cols:
        where.append("COALESCE(collect_status,'') NOT IN ('deleted','removed','private','hidden')")

    order_col = "article_no"
    for key in ["article_confirm_date", "confirmed_at", "registered_at", "updated_at", "created_at"]:
        if key in cols:
            order_col = key
            break

    with conn.cursor() as cur:
        cur.execute(f"""
            SELECT DISTINCT article_no
            FROM blog_realtor_articles
            WHERE {' AND '.join(where)}
            ORDER BY {order_col} DESC
        """, params)
        rows = cur.fetchall()

    return [clean_text(row.get("article_no")) for row in rows if clean_text(row.get("article_no"))]


def fetch_published_articles(conn, realtor_id):
    published = {}

    if table_exists(conn, "blog_publish_queue"):
        cols = table_columns(conn, "blog_publish_queue")
        if "article_no" in cols:
            url_expr = "publish_result_url" if "publish_result_url" in cols else "NULL"
            with conn.cursor() as cur:
                cur.execute(f"""
                    SELECT id AS queue_id, draft_id, article_no, {url_expr} AS blog_url,
                           finished_at, updated_at
                    FROM blog_publish_queue
                    WHERE realtor_id=%s
                      AND COALESCE(queue_status,'')='published'
                      AND COALESCE(article_no,'') <> ''
                    ORDER BY COALESCE(finished_at, updated_at, created_at) DESC, id DESC
                """, (int(realtor_id),))
                rows = cur.fetchall()

            for row in rows:
                article_no = clean_text(row.get("article_no"))
                if article_no and article_no not in published:
                    published[article_no] = dict(row)

    if table_exists(conn, "blog_article_drafts"):
        cols = table_columns(conn, "blog_article_drafts")
        url_col = None
        for key in ["blog_url", "post_url", "published_url", "naver_blog_url"]:
            if key in cols:
                url_col = key
                break

        if "article_no" in cols and url_col:
            with conn.cursor() as cur:
                cur.execute(f"""
                    SELECT id AS draft_id, article_no, {url_col} AS blog_url, published_at, updated_at
                    FROM blog_article_drafts
                    WHERE realtor_id=%s
                      AND COALESCE(article_no,'') <> ''
                      AND COALESCE({url_col},'') <> ''
                    ORDER BY COALESCE(published_at, updated_at, created_at) DESC, id DESC
                """, (int(realtor_id),))
                rows = cur.fetchall()

            for row in rows:
                article_no = clean_text(row.get("article_no"))
                if not article_no:
                    continue
                if article_no not in published:
                    published[article_no] = dict(row)
                elif not clean_text(published[article_no].get("blog_url")):
                    published[article_no]["blog_url"] = clean_text(row.get("blog_url"))
                    if not published[article_no].get("draft_id"):
                        published[article_no]["draft_id"] = row.get("draft_id")

    return list(published.values())



def ensure_v2_private_queue_columns_for_sync(conn):
    """
    STEP107-33:
    deleted sync가 생성하는 private queue를 v2_private_queue_router가 바로 claim할 수 있게
    필요한 컬럼을 보장한다.
    """
    if not table_exists(conn, "blog_post_private_queue"):
        return False

    cols = table_columns(conn, "blog_post_private_queue")
    alters = []

    if "execution_target" not in cols:
        alters.append("ADD COLUMN execution_target VARCHAR(20) NOT NULL DEFAULT 'server'")
    if "claimed_by" not in cols:
        alters.append("ADD COLUMN claimed_by VARCHAR(100) DEFAULT NULL")
    if "claim_token" not in cols:
        alters.append("ADD COLUMN claim_token VARCHAR(80) DEFAULT NULL")
    if "claimed_at" not in cols:
        alters.append("ADD COLUMN claimed_at DATETIME DEFAULT NULL")
    if "heartbeat_at" not in cols:
        alters.append("ADD COLUMN heartbeat_at DATETIME DEFAULT NULL")
    if "last_claim_error" not in cols:
        alters.append("ADD COLUMN last_claim_error VARCHAR(1000) DEFAULT NULL")
    if "private_result_url" not in cols:
        alters.append("ADD COLUMN private_result_url VARCHAR(1000) DEFAULT NULL")
    if "private_completed_at" not in cols:
        alters.append("ADD COLUMN private_completed_at DATETIME DEFAULT NULL")

    if alters:
        with conn.cursor() as cur:
            for alter in alters:
                cur.execute(f"ALTER TABLE blog_post_private_queue {alter}")
        conn.commit()

    return True


def private_queue_existing(conn, realtor_id, article_no, blog_url=""):
    if not table_exists(conn, "blog_post_private_queue"):
        return None

    cols = table_columns(conn, "blog_post_private_queue")
    parts = []
    params = [int(realtor_id)]

    if "article_no" in cols:
        parts.append("article_no=%s")
        params.append(str(article_no))

    if blog_url and "blog_url" in cols:
        parts.append("blog_url=%s")
        params.append(str(blog_url))

    if not parts:
        return None

    status_expr = "COALESCE(queue_status, COALESCE(status,''))"
    if "queue_status" not in cols and "status" not in cols:
        status_filter = "1=1"
    else:
        status_filter = f"{status_expr} IN ('pending','waiting','ready','processing','private_done','done','success')"

    with conn.cursor() as cur:
        cur.execute(f"""
            SELECT *
            FROM blog_post_private_queue
            WHERE realtor_id=%s
              AND ({' OR '.join(parts)})
              AND {status_filter}
            ORDER BY id DESC
            LIMIT 1
        """, params)
        return cur.fetchone()


def create_private_queue(conn, realtor_id, article_no, blog_url="", draft_id=None, publish_queue_id=None):
    if not table_exists(conn, "blog_post_private_queue"):
        return {"ok": False, "created": False, "skipped": True, "reason": "table_missing", "article_no": article_no}

    existing = private_queue_existing(conn, realtor_id, article_no, blog_url)
    if existing:
        return {"ok": True, "created": False, "skipped": True, "reason": "exists", "queue_id": existing.get("id"), "article_no": article_no}

    ensure_v2_private_queue_columns_for_sync(conn)
    cols = table_columns(conn, "blog_post_private_queue")
    data = {
        "realtor_id": int(realtor_id),
        "article_no": str(article_no),
        "blog_url": clean_text(blog_url),
        "draft_id": int(draft_id) if draft_id else None,
        "publish_queue_id": int(publish_queue_id) if publish_queue_id else None,
        "queue_status": "pending",
        "status": "pending",
        "execution_target": "server",
        "reason": "naver_article_not_found",
        "private_reason": "naver_article_not_found",
        "source": "v2_deleted_article_sync",
        "pipeline_version": "v2",
        "worker_version": "v2",
        "retry_count": 0,
        "created_at": datetime.now(),
        "updated_at": datetime.now(),
    }
    filtered = {k: v for k, v in data.items() if k in cols and v is not None}

    if not filtered:
        return {"ok": False, "created": False, "skipped": True, "reason": "no_matching_columns", "article_no": article_no}

    keys = list(filtered.keys())
    values = [filtered[k] for k in keys]

    with conn.cursor() as cur:
        cur.execute(
            f"INSERT INTO blog_post_private_queue ({', '.join(keys)}) VALUES ({', '.join(['%s'] * len(keys))})",
            values,
        )
        queue_id = cur.lastrowid

    conn.commit()
    return {"ok": True, "created": True, "queue_id": queue_id, "article_no": article_no, "blog_url": blog_url}



def fetch_latest_success_snapshot_run(conn, realtor_id, before_run_id=None, snapshot_date=None):
    if not table_exists(conn, "blog_realtor_article_snapshot_runs"):
        return None

    where = ["realtor_id=%s", "status='success'"]
    params = [int(realtor_id)]

    if before_run_id is not None:
        where.append("id < %s")
        params.append(int(before_run_id))

    if snapshot_date is not None:
        where.append("snapshot_date <= %s")
        params.append(snapshot_date)

    sql = f"""
        SELECT *
        FROM blog_realtor_article_snapshot_runs
        WHERE {' AND '.join(where)}
        ORDER BY snapshot_date DESC, id DESC
        LIMIT 1
    """

    with conn.cursor() as cur:
        cur.execute(sql, params)
        return cur.fetchone()


def fetch_previous_success_snapshot_run(conn, realtor_id, current_run):
    if not current_run:
        return None

    return fetch_latest_success_snapshot_run(
        conn=conn,
        realtor_id=realtor_id,
        before_run_id=current_run.get("id"),
        snapshot_date=current_run.get("snapshot_date"),
    )


def fetch_snapshot_article_nos(conn, run_id):
    if not table_exists(conn, "blog_realtor_article_snapshot_items"):
        return set()

    with conn.cursor() as cur:
        cur.execute("""
            SELECT article_no
            FROM blog_realtor_article_snapshot_items
            WHERE run_id=%s
              AND COALESCE(article_no,'') <> ''
        """, (int(run_id),))
        rows = cur.fetchall() or []

    return {clean_text(row.get("article_no")) for row in rows if clean_text(row.get("article_no"))}


def fetch_snapshot_deleted_article_nos(conn, realtor_id, current_run_id=None):
    if not table_exists(conn, "blog_realtor_article_snapshot_runs") or not table_exists(conn, "blog_realtor_article_snapshot_items"):
        return {
            "ok": False,
            "message": "snapshot 테이블이 없습니다.",
            "current_run": None,
            "previous_run": None,
            "current_set": set(),
            "previous_set": set(),
            "deleted_article_nos": [],
        }

    if current_run_id:
        with conn.cursor() as cur:
            cur.execute("""
                SELECT *
                FROM blog_realtor_article_snapshot_runs
                WHERE id=%s
                  AND realtor_id=%s
                  AND status='success'
                LIMIT 1
            """, (int(current_run_id), int(realtor_id)))
            current_run = cur.fetchone()
    else:
        current_run = fetch_latest_success_snapshot_run(conn, realtor_id)

    if not current_run:
        return {
            "ok": False,
            "message": "현재 SUCCESS snapshot이 없습니다.",
            "current_run": None,
            "previous_run": None,
            "current_set": set(),
            "previous_set": set(),
            "deleted_article_nos": [],
        }

    previous_run = fetch_previous_success_snapshot_run(conn, realtor_id, current_run)

    if not previous_run:
        return {
            "ok": True,
            "message": "이전 SUCCESS snapshot이 없어 삭제 비교를 건너뜁니다.",
            "current_run": current_run,
            "previous_run": None,
            "current_set": fetch_snapshot_article_nos(conn, current_run.get("id")),
            "previous_set": set(),
            "deleted_article_nos": [],
        }

    current_set = fetch_snapshot_article_nos(conn, current_run.get("id"))
    previous_set = fetch_snapshot_article_nos(conn, previous_run.get("id"))

    deleted_article_nos = sorted(
        list(previous_set - current_set),
        key=lambda x: int(x) if str(x).isdigit() else 0,
        reverse=True,
    )

    return {
        "ok": True,
        "message": "",
        "current_run": current_run,
        "previous_run": previous_run,
        "current_set": current_set,
        "previous_set": previous_set,
        "deleted_article_nos": deleted_article_nos,
    }


def fetch_published_articles_by_article_nos(conn, realtor_id, article_nos):
    article_nos = [clean_text(x) for x in (article_nos or []) if clean_text(x)]
    if not article_nos:
        return []

    placeholders = ",".join(["%s"] * len(article_nos))
    with conn.cursor() as cur:
        cur.execute(f"""
            SELECT *
            FROM blog_publish_queue
            WHERE realtor_id=%s
              AND article_no IN ({placeholders})
              AND COALESCE(queue_status,'') IN ('published','success','done')
              AND COALESCE(publish_result_url,'') <> ''
            ORDER BY id DESC
        """, [int(realtor_id)] + article_nos)
        return cur.fetchall() or []



def ensure_v2_migration_columns(conn):
    """
    STEP107-21:
    V1 → V2 전환 상태를 blog_realtors에 최소 컬럼으로 저장한다.
    없으면 자동 추가한다.
    """
    if not table_exists(conn, "blog_realtors"):
        return False

    cols = table_columns(conn, "blog_realtors")
    alters = []

    if "v2_migration_status" not in cols:
        alters.append("ADD COLUMN v2_migration_status VARCHAR(30) DEFAULT NULL")
    if "v2_baseline_snapshot_run_id" not in cols:
        alters.append("ADD COLUMN v2_baseline_snapshot_run_id BIGINT UNSIGNED DEFAULT NULL")
    if "v2_baseline_created_at" not in cols:
        alters.append("ADD COLUMN v2_baseline_created_at DATETIME DEFAULT NULL")
    if "v2_private_sync_enabled" not in cols:
        alters.append("ADD COLUMN v2_private_sync_enabled TINYINT(1) NOT NULL DEFAULT 1")

    if alters:
        with conn.cursor() as cur:
            for alter in alters:
                cur.execute(f"ALTER TABLE blog_realtors {alter}")
        conn.commit()

    return True


def get_v2_migration_state(conn, realtor_id):
    ensure_v2_migration_columns(conn)
    cols = table_columns(conn, "blog_realtors")

    select_cols = ["id"]
    for col in [
        "v2_migration_status",
        "v2_baseline_snapshot_run_id",
        "v2_baseline_created_at",
        "v2_private_sync_enabled",
    ]:
        if col in cols:
            select_cols.append(col)

    with conn.cursor() as cur:
        cur.execute(f"""
            SELECT {", ".join(select_cols)}
            FROM blog_realtors
            WHERE id=%s
            LIMIT 1
        """, (int(realtor_id),))
        row = cur.fetchone() or {}

    return {
        "status": clean_text(row.get("v2_migration_status")),
        "baseline_run_id": row.get("v2_baseline_snapshot_run_id"),
        "baseline_created_at": row.get("v2_baseline_created_at"),
        "private_sync_enabled": int(row.get("v2_private_sync_enabled") if row.get("v2_private_sync_enabled") is not None else 1),
    }


def set_v2_migration_baseline(conn, realtor_id, baseline_run_id, status="baseline_ready", private_sync_enabled=0):
    ensure_v2_migration_columns(conn)
    with conn.cursor() as cur:
        cur.execute("""
            UPDATE blog_realtors
            SET v2_migration_status=%s,
                v2_baseline_snapshot_run_id=%s,
                v2_baseline_created_at=NOW(),
                v2_private_sync_enabled=%s
            WHERE id=%s
        """, (
            status,
            int(baseline_run_id) if baseline_run_id else None,
            int(private_sync_enabled),
            int(realtor_id),
        ))
    conn.commit()


def complete_v2_migration(conn, realtor_id):
    ensure_v2_migration_columns(conn)
    with conn.cursor() as cur:
        cur.execute("""
            UPDATE blog_realtors
            SET v2_migration_status='normal',
                v2_private_sync_enabled=1
            WHERE id=%s
        """, (int(realtor_id),))
    conn.commit()


def sync_deleted_articles_for_realtor(realtor_id, current_article_nos=None, dry_run=False, limit=None, expected_current_count=None, min_current_ratio=0.95, use_snapshot=False, current_run_id=None, migration_baseline_only=False, complete_migration_after_compare=False):
    conn = get_conn()
    try:
        realtor = load_realtor(conn, realtor_id)

        snapshot_info = None
        current_run = None
        previous_run = None

        if use_snapshot:
            snapshot_info = fetch_snapshot_deleted_article_nos(
                conn=conn,
                realtor_id=realtor_id,
                current_run_id=current_run_id,
            )
            current_run = snapshot_info.get("current_run")
            previous_run = snapshot_info.get("previous_run")
            current_set = snapshot_info.get("current_set") or set()

            migration_state = get_v2_migration_state(conn, realtor_id)

            # STEP107-21:
            # V1 → V2 전환 첫날은 현재 SUCCESS Snapshot을 baseline으로만 등록하고 비공개 비교는 하지 않는다.
            if migration_baseline_only:
                if current_run and not dry_run:
                    set_v2_migration_baseline(
                        conn=conn,
                        realtor_id=realtor_id,
                        baseline_run_id=current_run.get("id"),
                        status="baseline_ready",
                        private_sync_enabled=0,
                    )

                result = {
                    "ok": True,
                    "realtor_id": int(realtor_id),
                    "office_name": (realtor or {}).get("office_name") or (realtor or {}).get("name") or "",
                    "current_naver_count": len(current_set),
                    "published_count": 0,
                    "deleted_count": 0,
                    "private_queue_created": 0,
                    "private_queue_skipped": 0,
                    "safety_blocked": False,
                    "safety_reason": "",
                    "expected_current_count": expected_current_count,
                    "min_current_ratio": min_current_ratio,
                    "use_snapshot": True,
                    "migration_baseline_only": True,
                    "migration_status": "baseline_ready",
                    "current_run_id": current_run.get("id") if current_run else None,
                    "current_snapshot_date": str(current_run.get("snapshot_date")) if current_run else None,
                    "previous_run_id": previous_run.get("id") if previous_run else None,
                    "previous_snapshot_date": str(previous_run.get("snapshot_date")) if previous_run else None,
                    "snapshot_message": "V1→V2 전환 baseline만 생성하고 비공개 비교를 건너뜁니다.",
                    "errors": [],
                    "created": [],
                    "skipped": [],
                    "dry_run": dry_run,
                }
                print("[V2 DELETED ARTICLE SYNC]", json.dumps(result, ensure_ascii=False, default=str))
                return result

            # baseline_ready 상태에서 private_sync_enabled=0이면 다음 SUCCESS Snapshot 전까지 비공개 비교를 막는다.
            if use_snapshot and migration_state.get("status") == "baseline_ready" and migration_state.get("private_sync_enabled") == 0:
                baseline_run_id = migration_state.get("baseline_run_id")
                current_id = current_run.get("id") if current_run else None

                if not current_id or int(current_id) == int(baseline_run_id or 0):
                    snapshot_info["ok"] = True
                    snapshot_info["message"] = "baseline snapshot 이후 새 SUCCESS snapshot이 없어 비공개 비교를 건너뜁니다."
                    snapshot_info["deleted_article_nos"] = []
                else:
                    if baseline_run_id:
                        with conn.cursor() as cur:
                            cur.execute("""
                                SELECT *
                                FROM blog_realtor_article_snapshot_runs
                                WHERE id=%s AND realtor_id=%s AND status='success'
                                LIMIT 1
                            """, (int(baseline_run_id), int(realtor_id)))
                            baseline_run = cur.fetchone()
                        if baseline_run:
                            previous_run = baseline_run
                            previous_set = fetch_snapshot_article_nos(conn, previous_run.get("id"))
                            current_set = fetch_snapshot_article_nos(conn, current_run.get("id"))
                            snapshot_info["previous_run"] = previous_run
                            snapshot_info["previous_set"] = previous_set
                            snapshot_info["current_set"] = current_set
                            snapshot_info["deleted_article_nos"] = sorted(
                                list(previous_set - current_set),
                                key=lambda x: int(x) if str(x).isdigit() else 0,
                                reverse=True,
                            )

            # snapshot 비교에서 삭제 후보만 published_rows로 제한 조회한다.
            snapshot_deleted_nos = snapshot_info.get("deleted_article_nos") or []
            published_rows = fetch_published_articles_by_article_nos(conn, realtor_id, snapshot_deleted_nos)
        else:
            if current_article_nos is None:
                current_article_nos = fetch_current_article_nos_from_db(conn, realtor_id)

            current_set = {clean_text(x) for x in (current_article_nos or []) if clean_text(x)}
            published_rows = fetch_published_articles(conn, realtor_id)

        safety_blocked = False
        safety_reason = ""

        if use_snapshot and snapshot_info and not snapshot_info.get("ok"):
            safety_blocked = True
            safety_reason = snapshot_info.get("message") or "snapshot_compare_failed"

        # STEP107-08:
        # 삭제매물 판단은 "현재 네이버 전체 매물 수집"이 정상이라고 볼 수 있을 때만 허용한다.
        # 예: 화면 총계 53인데 current_set 22면 비공개 Queue를 만들면 안 된다.
        if expected_current_count is not None:
            try:
                expected = int(expected_current_count or 0)
                actual = len(current_set)
                ratio = float(actual) / float(expected) if expected > 0 else 1.0

                if expected > 0 and actual < expected and ratio < float(min_current_ratio or 0.95):
                    safety_blocked = True
                    safety_reason = (
                        f"current_count_suspicious: actual={actual}, "
                        f"expected={expected}, ratio={ratio:.3f}, "
                        f"min_ratio={float(min_current_ratio or 0.95):.3f}"
                    )
            except Exception as e:
                safety_blocked = True
                safety_reason = f"expected_current_count_check_error: {e}"

        # expected_current_count가 없더라도, 현재 수집 수가 published 수보다 비정상적으로 적으면 차단한다.
        if not safety_blocked and len(published_rows) >= 10:
            actual = len(current_set)
            published_count = len(published_rows)
            ratio = float(actual) / float(published_count) if published_count > 0 else 1.0

            if actual < published_count * 0.80:
                safety_blocked = True
                safety_reason = (
                    f"current_count_less_than_published_suspicious: "
                    f"actual={actual}, published={published_count}, ratio={ratio:.3f}"
                )

        deleted = []
        if not safety_blocked:
            for row in published_rows:
                article_no = clean_text(row.get("article_no"))
                if article_no and article_no not in current_set:
                    deleted.append(row)

            if limit is not None:
                deleted = deleted[:int(limit)]

        created, skipped, errors = [], [], []

        if safety_blocked:
            skipped.append({
                "reason": "safety_blocked",
                "message": safety_reason,
            })

        for row in deleted:
            article_no = clean_text(row.get("article_no"))
            blog_url = clean_text(row.get("blog_url"))
            draft_id = row.get("draft_id")
            publish_queue_id = row.get("queue_id")

            if dry_run:
                skipped.append({"article_no": article_no, "blog_url": blog_url, "reason": "dry_run"})
                continue

            try:
                result = create_private_queue(
                    conn=conn,
                    realtor_id=realtor_id,
                    article_no=article_no,
                    blog_url=blog_url,
                    draft_id=draft_id,
                    publish_queue_id=publish_queue_id,
                )
                if result.get("created"):
                    created.append(result)
                else:
                    skipped.append(result)
            except Exception as e:
                try:
                    conn.rollback()
                except Exception:
                    pass
                errors.append({"article_no": article_no, "blog_url": blog_url, "error": str(e)})

        summary = {
            "ok": len(errors) == 0,
            "realtor_id": int(realtor_id),
            "office_name": clean_text(realtor.get("office_name")),
            "current_naver_count": len(current_set),
            "published_count": len(published_rows),
            "deleted_count": len(deleted),
            "private_queue_created": len(created),
            "private_queue_skipped": len(skipped),
            "safety_blocked": bool(safety_blocked),
            "safety_reason": safety_reason,
            "expected_current_count": expected_current_count,
            "min_current_ratio": min_current_ratio,
            "use_snapshot": bool(use_snapshot),
            "migration_baseline_only": bool(migration_baseline_only),
            "complete_migration_after_compare": bool(complete_migration_after_compare),
            "current_run_id": current_run.get("id") if current_run else None,
            "current_snapshot_date": str(current_run.get("snapshot_date")) if current_run else None,
            "previous_run_id": previous_run.get("id") if previous_run else None,
            "previous_snapshot_date": str(previous_run.get("snapshot_date")) if previous_run else None,
            "snapshot_message": snapshot_info.get("message") if snapshot_info else "",
            "errors": errors,
            "created": created,
            "skipped": skipped,
            "dry_run": bool(dry_run),
        }

        log("[V2 DELETED ARTICLE SYNC]", json.dumps(summary, ensure_ascii=False, default=str))
        return summary

    finally:
        try:
            conn.close()
        except Exception:
            pass


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--realtor-id", type=int, required=True)
    parser.add_argument("--dry-run", action="store_true")
    parser.add_argument("--limit", type=int, default=None)
    parser.add_argument("--expected-current-count", type=int, default=None)
    parser.add_argument("--min-current-ratio", type=float, default=0.95)
    parser.add_argument("--use-snapshot", action="store_true")
    parser.add_argument("--current-run-id", type=int, default=None)
    parser.add_argument("--migration-baseline-only", action="store_true")
    parser.add_argument("--complete-migration-after-compare", action="store_true")
    args = parser.parse_args()

    result = sync_deleted_articles_for_realtor(args.realtor_id, dry_run=args.dry_run, limit=args.limit, expected_current_count=args.expected_current_count, min_current_ratio=args.min_current_ratio, use_snapshot=args.use_snapshot, current_run_id=args.current_run_id, migration_baseline_only=args.migration_baseline_only, complete_migration_after_compare=args.complete_migration_after_compare)
    print("[RESULT]", json.dumps(result, ensure_ascii=False, default=str))
