# -*- coding: utf-8 -*-
"""
MYBOX 업로드 Worker V1

역할:
- blog_mybox_assets 에 저장된 header_image/realtor_banner/location_guide 등의 pending_upload 자산을 찾는다.
- shorts_ai_api.py 의 /mybox-upload-file API를 호출한다.
- 업로드 성공 시 status=uploaded 로 변경한다.
- 아직 MYBOX 공유 URL/CDN URL 추출은 Step2에서 보강한다.

실행 예:
python workers/mybox_upload_worker.py --limit 5
python workers/mybox_upload_worker.py --asset-id 123
python workers/mybox_upload_worker.py --article-no 2629665455 --limit 10
"""

import argparse
import json
import os
import sys
import time
from datetime import datetime

import requests

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)

from db import get_conn


AI_SHORTS_API_URL = "http://61.32.69.107:9000"
AI_SHORTS_API_KEY = "honghee-shorts-secret-2026"


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


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 now():
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def log(message):
    print(f"[{now()}] {message}", flush=True)


def normalize_path(path):
    return str(path or "").strip().replace("\\", "/")


def build_mybox_target_folder(asset):
    article_no = str(asset.get("article_no") or "common").strip() or "common"
    asset_type = str(asset.get("asset_type") or "asset").strip() or "asset"

    y = datetime.now().strftime("%Y")
    m = datetime.now().strftime("%m")

    if asset_type == "header_image":
        return f"REAL_AUTO/header_images/{y}/{m}/{article_no}"

    if asset_type == "realtor_banner":
        return f"REAL_AUTO/realtor_banners/{y}/{m}/{article_no}"

    if asset_type == "location_guide":
        return f"REAL_AUTO/location_guides/{y}/{m}/{article_no}"

    return f"REAL_AUTO/assets/{y}/{m}/{article_no}"


def fetch_pending_assets(conn, limit=10, asset_id=0, article_no="", retry_failed=False):
    if not table_exists(conn, "blog_mybox_assets"):
        raise Exception("blog_mybox_assets 테이블이 없습니다.")

    columns = table_columns(conn, "blog_mybox_assets")

    where = []
    params = []

    if asset_id:
        where.append("id = %s")
        params.append(asset_id)
    else:
        if "status" in columns:
            if retry_failed:
                where.append("(status IN ('pending_upload', 'active', 'failed') OR status IS NULL OR status = '')")
            else:
                where.append("(status IN ('pending_upload', 'active') OR status IS NULL OR status = '')")

        if article_no:
            where.append("article_no = %s")
            params.append(str(article_no))

        if "file_url" in columns and "local_path" in columns:
            # 이미 네이버/MYBOX 공유 URL이 들어간 자산은 제외.
            # 현재 Python 서버 /mybox/generated URL은 업로드 대상이다.
            where.append("(local_path IS NOT NULL AND local_path <> '')")

    where_sql = " AND ".join(where) if where else "1=1"

    sql = f"""
        SELECT *
        FROM blog_mybox_assets
        WHERE {where_sql}
        ORDER BY id ASC
        LIMIT %s
    """
    params.append(int(limit))

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


def update_asset_status(conn, asset_id, status, message="", extra=None):
    columns = table_columns(conn, "blog_mybox_assets")

    data = {}

    if "status" in columns:
        data["status"] = status

    # 다양한 필드명 대응
    if "error_message" in columns:
        data["error_message"] = message[:1000] if message else None
    elif "last_error" in columns:
        data["last_error"] = message[:1000] if message else None

    if "uploaded_at" in columns and status == "uploaded":
        data["uploaded_at"] = datetime.now()

    if "last_uploaded_at" in columns and status == "uploaded":
        data["last_uploaded_at"] = datetime.now()

    if "updated_at" in columns:
        data["updated_at"] = datetime.now()

    if extra:
        for key, value in extra.items():
            if key in columns:
                data[key] = value

    if not data:
        return

    sets = ", ".join([f"{k} = %s" for k in data.keys()])
    values = list(data.values())
    values.append(asset_id)

    with conn.cursor() as cur:
        cur.execute(f"UPDATE blog_mybox_assets SET {sets} WHERE id = %s", values)

    conn.commit()


def call_mybox_upload_api(asset, headless=True):
    local_path = normalize_path(
        asset.get("local_path")
        or asset.get("file_path")
        or asset.get("image_path")
        or ""
    )

    if not local_path:
        raise Exception("local_path가 비어 있습니다.")

    if not os.path.exists(local_path):
        raise Exception(f"local_path 파일이 없습니다: {local_path}")

    payload = {
        "local_path": local_path,
        "article_no": str(asset.get("article_no") or ""),
        "asset_type": str(asset.get("asset_type") or "header_image"),
        "target_folder": build_mybox_target_folder(asset),
        "headless": bool(headless),
    }

    url = AI_SHORTS_API_URL.rstrip("/") + "/mybox-upload-file"

    res = requests.post(
        url,
        json=payload,
        headers={
            "Content-Type": "application/json",
            "X-API-KEY": AI_SHORTS_API_KEY,
        },
        timeout=300,
    )

    try:
        data = res.json()
    except Exception:
        raise Exception(f"MYBOX upload api json parse failed: HTTP {res.status_code} / {res.text[:500]}")

    if res.status_code >= 400 or not data.get("ok"):
        raise Exception(data.get("message") or data.get("error") or f"MYBOX upload failed: HTTP {res.status_code}")

    return data


def mark_article_draft_header_uploaded(conn, asset, upload_result):
    """
    Step1에서는 공유 URL이 없으므로 draft_html 교체는 하지 않는다.
    단, 관련 컬럼이 있으면 mybox 업로드 상태만 남긴다.
    """
    if not table_exists(conn, "blog_article_drafts"):
        return

    article_no = str(asset.get("article_no") or "").strip()
    if not article_no:
        return

    columns = table_columns(conn, "blog_article_drafts")

    data = {}

    if "mybox_upload_status" in columns:
        data["mybox_upload_status"] = "uploaded"

    if "mybox_uploaded_at" in columns:
        data["mybox_uploaded_at"] = datetime.now()

    if "updated_at" in columns:
        data["updated_at"] = datetime.now()

    if not data:
        return

    sets = ", ".join([f"{k} = %s" for k in data.keys()])
    values = list(data.values())
    values.append(article_no)

    with conn.cursor() as cur:
        cur.execute(
            f"UPDATE blog_article_drafts SET {sets} WHERE article_no = %s",
            values
        )

    conn.commit()


def process_one(conn, asset, headless=True):
    asset_id = int(asset.get("id") or 0)
    article_no = asset.get("article_no")
    asset_type = asset.get("asset_type")

    log(f"UPLOAD START asset_id={asset_id}, article_no={article_no}, type={asset_type}")

    update_asset_status(conn, asset_id, "uploading", "")

    try:
        result = call_mybox_upload_api(asset, headless=headless)

        extra = {
            "mybox_page_url": result.get("mybox_page_url") or "",
            "mybox_url": result.get("mybox_url") or "",
            "target_folder": result.get("target_folder") or "",
        }

        # 컬럼이 없으면 자동 무시됨.
        update_asset_status(
            conn,
            asset_id,
            "uploaded",
            "MYBOX 업로드 완료",
            extra=extra
        )

        mark_article_draft_header_uploaded(conn, asset, result)

        log(f"UPLOAD OK asset_id={asset_id}, folder={result.get('target_folder')}")
        return True

    except Exception as e:
        message = str(e)
        update_asset_status(conn, asset_id, "failed", message)
        log(f"UPLOAD FAIL asset_id={asset_id}: {message}")
        return False


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--limit", type=int, default=5)
    parser.add_argument("--asset-id", type=int, default=0)
    parser.add_argument("--article-no", default="")
    parser.add_argument("--retry-failed", action="store_true")
    parser.add_argument("--headful", action="store_true", help="브라우저를 보이게 실행")
    args = parser.parse_args()

    conn = get_conn()

    try:
        assets = fetch_pending_assets(
            conn,
            limit=args.limit,
            asset_id=args.asset_id,
            article_no=args.article_no,
            retry_failed=args.retry_failed,
        )

        log(f"TARGET ASSETS {len(assets)}")

        ok_count = 0
        fail_count = 0

        for asset in assets:
            if process_one(conn, asset, headless=(not args.headful)):
                ok_count += 1
            else:
                fail_count += 1

            time.sleep(1)

        log(f"DONE ok={ok_count}, fail={fail_count}")

    finally:
        conn.close()


if __name__ == "__main__":
    main()
