# -*- coding: utf-8 -*-
"""
운영용 특정 매물 초안 재생성 도구

목적:
- 이미 생성된 초안/발행대기 큐를 안전하게 정리하고
- jobs/generate_blog_drafts.py를 특정 article_no 기준으로 다시 실행한다.

주의:
- published 된 queue는 기본적으로 건드리지 않는다.
- pending/failed/ready/waiting queue만 cancelled 처리한다.
- 기존 draft는 hard delete하지 않고 status/draft_status를 regenerated_old 로 표시한다.
- generate_blog_drafts.py가 article_no 직접 옵션을 지원하는 현재 구조를 사용한다.

사용 예:
  cd /d D:\honghee\blog_api

  1건:
  python tools\regenerate_drafts_for_articles.py --realtor-id 7 --article-nos 2632918847

  여러 건:
  python tools\regenerate_drafts_for_articles.py --realtor-id 7 --article-nos 2632918847,2633001541,2633005438,2632992123

  먼저 확인만:
  python tools\regenerate_drafts_for_articles.py --realtor-id 7 --article-nos 2632918847 --dry-run
"""

import argparse
import subprocess
import sys
import json
from pathlib import Path
from datetime import datetime

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))

from db import get_conn

PYTHON = sys.executable
DRAFT_JOB = ROOT / "jobs" / "generate_blog_drafts.py"


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


def clean(v):
    return str(v or "").strip()


def parse_article_nos(value):
    result = []
    for item in str(value or "").replace("\n", ",").replace(" ", ",").split(","):
        item = item.strip()
        if item:
            result.append(item)
    return result


def fetch_article(conn, realtor_id, article_no):
    with conn.cursor() as cur:
        cur.execute("""
            SELECT article_no, realtor_id, collect_status, detail_collected, article_name, created_at, updated_at
            FROM blog_realtor_articles
            WHERE realtor_id=%s AND article_no=%s
            LIMIT 1
        """, (int(realtor_id), str(article_no)))
        return cur.fetchone()


def fetch_drafts(conn, realtor_id, article_no):
    with conn.cursor() as cur:
        cur.execute("""
            SELECT id, realtor_id, article_no, draft_status, status, created_at, updated_at
            FROM blog_article_drafts
            WHERE realtor_id=%s AND article_no=%s
            ORDER BY id DESC
        """, (int(realtor_id), str(article_no)))
        return cur.fetchall()


def fetch_queues(conn, realtor_id, article_no):
    with conn.cursor() as cur:
        cur.execute("""
            SELECT id, realtor_id, draft_id, article_no, queue_status, worker_status, publish_result_url, created_at, updated_at
            FROM blog_publish_queue
            WHERE realtor_id=%s AND article_no=%s
            ORDER BY id DESC
        """, (int(realtor_id), str(article_no)))
        return cur.fetchall()


def mark_old_drafts(conn, realtor_id, article_no):
    """
    draft를 삭제하지 않고 이전본으로 표시한다.
    generate_blog_drafts.py가 기존 draft 존재 여부를 체크할 수 있으므로,
    status/draft_status를 ready/generated에서 벗어나게 만든다.
    """
    with conn.cursor() as cur:
        cur.execute("""
            UPDATE blog_article_drafts
            SET
                draft_status = CASE
                    WHEN draft_status IN ('published') THEN draft_status
                    ELSE 'regenerated_old'
                END,
                status = CASE
                    WHEN status IN ('published') THEN status
                    ELSE 'regenerated_old'
                END,
                updated_at = NOW()
            WHERE realtor_id=%s
              AND article_no=%s
              AND COALESCE(status, '') <> 'published'
              AND COALESCE(draft_status, '') <> 'published'
        """, (int(realtor_id), str(article_no)))
        return cur.rowcount


def cancel_pending_queues(conn, realtor_id, article_no):
    """
    이미 발행된 queue는 건드리지 않는다.
    미발행 queue만 cancelled 처리한다.
    """
    with conn.cursor() as cur:
        cur.execute("""
            UPDATE blog_publish_queue
            SET
                queue_status = 'cancelled',
                worker_status = 'cancelled',
                error_message = '재생성을 위해 기존 미발행 큐 취소',
                updated_at = NOW()
            WHERE realtor_id=%s
              AND article_no=%s
              AND COALESCE(queue_status, '') NOT IN ('published')
        """, (int(realtor_id), str(article_no)))
        return cur.rowcount


def reset_article_for_draft(conn, realtor_id, article_no):
    """
    regenerate 대상이 다시 draft target으로 잡히도록 상태 보강.
    collected/detail_collected는 유지한다.
    """
    with conn.cursor() as cur:
        cur.execute("""
            UPDATE blog_realtor_articles
            SET
                collect_status = 'collected',
                detail_collected = 1,
                updated_at = NOW()
            WHERE realtor_id=%s
              AND article_no=%s
        """, (int(realtor_id), str(article_no)))
        return cur.rowcount


def run_generate_draft(realtor_id, article_no):
    if not DRAFT_JOB.exists():
        raise FileNotFoundError(DRAFT_JOB)

    cmd = [
        PYTHON,
        str(DRAFT_JOB),
        "--realtor-id",
        str(int(realtor_id)),
        "--article-no",
        str(article_no),
        "--limit",
        "1",
        "--no-db-lock",
    ]

    log("[RUN]", " ".join(cmd))

    return subprocess.run(
        cmd,
        cwd=str(ROOT),
        text=True,
        encoding="utf-8",
        errors="replace",
        timeout=1800,
    )


def fetch_latest_result(conn, realtor_id, article_no):
    drafts = fetch_drafts(conn, realtor_id, article_no)
    queues = fetch_queues(conn, realtor_id, article_no)
    return {
        "drafts": drafts[:3],
        "queues": queues[:3],
    }


def process_one(conn, realtor_id, article_no, dry_run=False):
    log("=" * 80)
    log("[REGENERATE TARGET]", f"realtor_id={realtor_id}", f"article_no={article_no}")

    article = fetch_article(conn, realtor_id, article_no)
    if not article:
        raise RuntimeError(f"article not found: realtor_id={realtor_id}, article_no={article_no}")

    log("[ARTICLE]", json.dumps(article, ensure_ascii=False, default=str))

    before = fetch_latest_result(conn, realtor_id, article_no)
    log("[BEFORE]", json.dumps(before, ensure_ascii=False, default=str))

    if dry_run:
        log("[DRY RUN] skip update and generation")
        return {
            "article_no": article_no,
            "dry_run": True,
            "before": before,
        }

    q_changed = cancel_pending_queues(conn, realtor_id, article_no)
    d_changed = mark_old_drafts(conn, realtor_id, article_no)
    a_changed = reset_article_for_draft(conn, realtor_id, article_no)

    conn.commit()

    log("[CANCEL QUEUES]", q_changed)
    log("[MARK OLD DRAFTS]", d_changed)
    log("[RESET ARTICLE]", a_changed)

    result = run_generate_draft(realtor_id, article_no)

    log("[GENERATE RETURNCODE]", result.returncode)

    if result.returncode != 0:
        raise RuntimeError(f"generate_blog_drafts failed: article_no={article_no}, returncode={result.returncode}")

    # 새 connection으로 결과 확인
    conn2 = get_conn()
    try:
        after = fetch_latest_result(conn2, realtor_id, article_no)
    finally:
        conn2.close()

    log("[AFTER]", json.dumps(after, ensure_ascii=False, default=str))

    return {
        "article_no": article_no,
        "dry_run": False,
        "returncode": result.returncode,
        "after": after,
    }


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--realtor-id", type=int, required=True)
    parser.add_argument("--article-nos", required=True, help="comma separated article_nos")
    parser.add_argument("--dry-run", action="store_true")
    args = parser.parse_args()

    article_nos = parse_article_nos(args.article_nos)
    if not article_nos:
        raise RuntimeError("article_nos empty")

    log("=" * 80)
    log("[OPERATING REGENERATE DRAFTS START]")
    log("TIME:", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    log("realtor_id:", args.realtor_id)
    log("article_nos:", article_nos)
    log("dry_run:", args.dry_run)
    log("=" * 80)

    conn = get_conn()
    success = 0
    failed = 0

    try:
        for article_no in article_nos:
            try:
                process_one(conn, args.realtor_id, article_no, dry_run=args.dry_run)
                success += 1
            except Exception as e:
                failed += 1
                try:
                    conn.rollback()
                except Exception:
                    pass
                log("[FAILED]", article_no, str(e))

        log("=" * 80)
        log("[DONE]", f"success={success}", f"failed={failed}")
        log("=" * 80)

        if failed:
            raise SystemExit(1)

    finally:
        conn.close()


if __name__ == "__main__":
    main()
