# -*- coding: utf-8 -*-
"""
tools/generate_realtor_static_map.py

중개사 주소를 실제 지도 이미지로 캡처하여 Cafe24 CDN에 업로드하고
blog_realtors.map_image_url / map_lat / map_lng 컬럼을 업데이트한다.

구조:
1) blog_realtors 주소 조회
2) 카카오 Local REST API로 주소 → 위도/경도 변환
3) Playwright headless 브라우저로 카카오 지도 HTML 렌더링
4) 중개사 위치 마커/라벨 표시 후 screenshot 저장
5) Cafe24 CDN 업로드
6) blog_realtors.map_* 업데이트

사용 예:
    cd D:\honghee\blog_api

    set KAKAO_REST_API_KEY=카카오_REST_API_키
    set KAKAO_JAVASCRIPT_KEY=카카오_JavaScript_키

    python tools\generate_realtor_static_map.py --realtor-id 4

    python tools\generate_realtor_static_map.py --all
"""

import os
import sys
import json
import argparse
import tempfile
import threading
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from functools import partial
from pathlib import Path
from datetime import datetime

import requests
from playwright.sync_api import sync_playwright

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

from db import get_conn
from services.cafe24_cdn_service import upload_header_image_to_cafe24


OUTPUT_DIR = BASE_DIR / "storage" / "realtor_maps"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

MAP_WIDTH = 900
MAP_HEIGHT = 540


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


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


def fetch_realtor(conn, realtor_id):
    with conn.cursor() as cur:
        cur.execute("""
            SELECT *
            FROM blog_realtors
            WHERE id = %s
            LIMIT 1
        """, (int(realtor_id),))
        return cur.fetchone() or {}


def fetch_realtors_for_batch(conn):
    with conn.cursor() as cur:
        cur.execute("""
            SELECT *
            FROM blog_realtors
            WHERE COALESCE(office_address, address, '') <> ''
              AND COALESCE(status, 'active') = 'active'
            ORDER BY id ASC
        """)
        return cur.fetchall() or []


def kakao_geocode(address, rest_api_key):
    address = clean_text(address)

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

    if not rest_api_key:
        raise Exception("KAKAO_REST_API_KEY 값이 없습니다.")

    res = requests.get(
        "https://dapi.kakao.com/v2/local/search/address.json",
        headers={"Authorization": f"KakaoAK {rest_api_key}"},
        params={"query": address},
        timeout=20,
    )

    if res.status_code >= 400:
        raise Exception(f"Kakao geocode HTTP {res.status_code}: {res.text[:500]}")

    data = res.json()
    docs = data.get("documents") or []

    if not docs:
        raise Exception(f"주소 좌표 변환 결과가 없습니다: {address}")

    doc = docs[0]

    return {
        "lat": float(doc.get("y")),
        "lng": float(doc.get("x")),
        "raw": doc,
    }


def build_map_html(js_key, lat, lng, office_name, address, phone_text):
    office_name_json = json.dumps(clean_text(office_name), ensure_ascii=False)
    address_json = json.dumps(clean_text(address), ensure_ascii=False)
    phone_json = json.dumps(clean_text(phone_text), ensure_ascii=False)

    # 카카오맵 JS SDK는 브라우저에서 로드된다.
    return f"""<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>realtor static map</title>
<style>
  html, body {{
    margin: 0;
    padding: 0;
    width: {MAP_WIDTH}px;
    height: {MAP_HEIGHT}px;
    overflow: hidden;
    font-family: Arial, 'Malgun Gothic', sans-serif;
    background: #f8fafc;
  }}
  #map {{
    width: {MAP_WIDTH}px;
    height: {MAP_HEIGHT}px;
  }}
  .marker-label {{
    position: absolute;
    left: 50%;
    top: 45%;
    transform: translate(-50%, -100%);
    display: flex;
    flex-direction: column;
    align-items: center;
    z-index: 20;
    pointer-events: none;
  }}
  .marker-pin {{
    width: 34px;
    height: 34px;
    background: #ef4444;
    border: 4px solid #fff;
    border-radius: 50% 50% 50% 0;
    transform: rotate(-45deg);
    box-shadow: 0 8px 20px rgba(0,0,0,.28);
  }}
  .marker-pin:after {{
    content: '';
    position: absolute;
    width: 10px;
    height: 10px;
    background: #fff;
    border-radius: 50%;
    left: 12px;
    top: 12px;
  }}
  .office-card {{
    margin-bottom: 14px;
    min-width: 360px;
    max-width: 560px;
    padding: 14px 18px;
    border-radius: 18px;
    background: rgba(17, 24, 39, .94);
    color: #fff;
    box-shadow: 0 14px 34px rgba(0,0,0,.28);
    text-align: center;
  }}
  .office-name {{
    font-size: 22px;
    font-weight: 900;
    line-height: 1.35;
    word-break: keep-all;
  }}
  .office-address {{
    margin-top: 6px;
    font-size: 13px;
    line-height: 1.5;
    color: rgba(255,255,255,.82);
    word-break: keep-all;
  }}
  .office-phone {{
    margin-top: 4px;
    font-size: 13px;
    font-weight: 800;
    color: #bbf7d0;
  }}
  .bottom-banner {{
    position: absolute;
    left: 26px;
    right: 26px;
    bottom: 24px;
    z-index: 21;
    padding: 16px 20px;
    border-radius: 20px;
    background: rgba(255,255,255,.96);
    box-shadow: 0 12px 32px rgba(15,23,42,.22);
    border: 1px solid rgba(203,213,225,.9);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
  }}
  .bottom-title {{
    font-size: 18px;
    font-weight: 900;
    color: #111827;
  }}
  .bottom-desc {{
    margin-top: 4px;
    font-size: 13px;
    color: #475569;
    line-height: 1.5;
    word-break: keep-all;
  }}
  .bottom-badge {{
    white-space: nowrap;
    border-radius: 999px;
    padding: 10px 16px;
    background: #03c75a;
    color: #fff;
    font-size: 13px;
    font-weight: 900;
  }}
</style>
<script src="https://dapi.kakao.com/v2/maps/sdk.js?appkey={js_key}&autoload=false"></script>
</head>
<body>
  <div id="map"></div>

  <div class="marker-label">
    <div class="office-card">
      <div class="office-name" id="officeName"></div>
      <div class="office-address" id="officeAddress"></div>
      <div class="office-phone" id="officePhone"></div>
    </div>
    <div class="marker-pin"></div>
  </div>

  <div class="bottom-banner">
    <div>
      <div class="bottom-title">중개사무소 위치 안내</div>
      <div class="bottom-desc">방문 상담 전 매물 가능 여부와 상담 가능 시간을 먼저 확인해 주세요.</div>
    </div>
    <div class="bottom-badge">위치 확인</div>
  </div>

<script>
  const OFFICE_NAME = {office_name_json};
  const OFFICE_ADDRESS = {address_json};
  const OFFICE_PHONE = {phone_json};

  document.getElementById('officeName').textContent = OFFICE_NAME;
  document.getElementById('officeAddress').textContent = OFFICE_ADDRESS;
  document.getElementById('officePhone').textContent = OFFICE_PHONE;

  kakao.maps.load(function() {{
    const center = new kakao.maps.LatLng({lat}, {lng});
    const map = new kakao.maps.Map(document.getElementById('map'), {{
      center: center,
      level: 3
    }});

    map.setCenter(center);

    const marker = new kakao.maps.Marker({{
      position: center,
      map: map
    }});
  }});
</script>
</body>
</html>"""


def render_kakao_map_screenshot(js_key, lat, lng, office_name, address, phone_text, output_path):
    if not js_key:
        raise Exception("KAKAO_JAVASCRIPT_KEY 값이 없습니다.")

    html = build_map_html(
        js_key=js_key,
        lat=lat,
        lng=lng,
        office_name=office_name,
        address=address,
        phone_text=phone_text,
    )

    output_path = Path(output_path)
    output_path.parent.mkdir(parents=True, exist_ok=True)

    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        html_path = tmp_path / "map.html"
        html_path.write_text(html, encoding="utf-8")

        # 카카오 JavaScript 지도는 file:// 환경에서 정상 로드되지 않는 경우가 많다.
        # 임시 localhost 서버로 열어야 Kakao Developers Web 플랫폼 도메인과 맞출 수 있다.
        host = "127.0.0.1"
        port = 8791

        handler = partial(SimpleHTTPRequestHandler, directory=str(tmp_path))
        httpd = ThreadingHTTPServer((host, port), handler)
        server_thread = threading.Thread(target=httpd.serve_forever, daemon=True)
        server_thread.start()

        map_url = f"http://{host}:{port}/map.html"

        try:
            with sync_playwright() as p:
                browser = p.chromium.launch(
                    headless=True,
                    args=[
                        "--disable-gpu",
                        "--no-sandbox",
                        "--disable-dev-shm-usage",
                    ],
                )

                page = browser.new_page(
                    viewport={"width": MAP_WIDTH, "height": MAP_HEIGHT},
                    device_scale_factor=1,
                )

                console_messages = []
                page.on("console", lambda msg: console_messages.append(f"{msg.type}: {msg.text}"))
                page.on("pageerror", lambda err: console_messages.append(f"pageerror: {err}"))

                page.goto(map_url, wait_until="networkidle", timeout=30000)

                # 카카오맵 타일/라벨 로드 여유
                page.wait_for_timeout(2500)

                # 지도 타일이 실제로 로드됐는지 확인한다.
                tile_count = page.locator("img[src*='map']").count()
                map_img_count = page.locator("#map img").count()

                if map_img_count <= 0:
                    msg = "카카오 지도 타일이 로드되지 않았습니다. Kakao Developers > 플랫폼 > Web 사이트 도메인에 http://127.0.0.1:8791 등록이 필요할 수 있습니다."
                    if console_messages:
                        msg += " / console=" + " | ".join(console_messages[-5:])
                    raise Exception(msg)

                page.screenshot(
                    path=str(output_path),
                    full_page=False,
                    type="png",
                )

                browser.close()

        finally:
            try:
                httpd.shutdown()
                httpd.server_close()
            except Exception:
                pass

    return str(output_path)


def update_realtor_map_result(conn, realtor_id, lat=None, lng=None, image_path="", image_url="", error_message=""):
    columns = table_columns(conn, "blog_realtors")
    data = {}

    if "map_lat" in columns:
        data["map_lat"] = lat

    if "map_lng" in columns:
        data["map_lng"] = lng

    if "map_image_path" in columns:
        data["map_image_path"] = image_path

    if "map_image_url" in columns:
        data["map_image_url"] = image_url

    if "map_generated_at" in columns:
        data["map_generated_at"] = datetime.now() if image_url else None

    if "map_error_message" in columns:
        data["map_error_message"] = error_message[:2000] if error_message else None

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

    if not data:
        print("[MAP UPDATE SKIP] blog_realtors map columns not found")
        return

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

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

    conn.commit()


def generate_for_realtor(conn, realtor, rest_api_key, js_key):
    realtor_id = int(realtor.get("id") or 0)
    office_name = clean_text(realtor.get("office_name") or "중개사무소")
    representative_name = clean_text(realtor.get("representative_name") or "")
    office_phone = clean_text(realtor.get("office_phone") or realtor.get("phone") or "")
    mobile_phone = clean_text(realtor.get("mobile_phone") or realtor.get("mobile") or "")
    address = clean_text(realtor.get("office_address") or realtor.get("address") or "")

    if not realtor_id:
        raise Exception("realtor_id가 없습니다.")

    if not address:
        raise Exception(f"주소가 없습니다. realtor_id={realtor_id}")

    phone_text = " / ".join([x for x in [office_phone, mobile_phone] if x])

    print(f"[MAP START] realtor_id={realtor_id}, office={office_name}")

    geo = kakao_geocode(address, rest_api_key)
    lat = geo["lat"]
    lng = geo["lng"]

    filename = f"realtor_static_map_{realtor_id}_{datetime.now().strftime('%Y%m%d%H%M%S')}.png"
    local_path = OUTPUT_DIR / filename

    image_path = render_kakao_map_screenshot(
        js_key=js_key,
        lat=lat,
        lng=lng,
        office_name=office_name,
        address=address,
        phone_text=phone_text,
        output_path=local_path,
    )

    cdn = upload_header_image_to_cafe24(
        conn=conn,
        article_no=f"realtor_{realtor_id}",
        local_path=image_path,
        filename=filename,
    )

    image_url = clean_text((cdn or {}).get("cdn_url") or "")

    if not image_url:
        raise Exception("Cafe24 CDN 업로드 결과 URL이 없습니다.")

    update_realtor_map_result(
        conn=conn,
        realtor_id=realtor_id,
        lat=lat,
        lng=lng,
        image_path=image_path,
        image_url=image_url,
        error_message="",
    )

    print(f"[MAP DONE] realtor_id={realtor_id}, url={image_url}")

    return {
        "realtor_id": realtor_id,
        "lat": lat,
        "lng": lng,
        "image_path": image_path,
        "image_url": image_url,
    }


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--realtor-id", type=int, default=0)
    parser.add_argument("--all", action="store_true")
    parser.add_argument("--kakao-rest-api-key", type=str, default="")
    parser.add_argument("--kakao-javascript-key", type=str, default="")
    args = parser.parse_args()

    rest_api_key = args.kakao_rest_api_key or os.environ.get("KAKAO_REST_API_KEY", "")
    js_key = args.kakao_javascript_key or os.environ.get("KAKAO_JAVASCRIPT_KEY", "")

    conn = get_conn()

    success = 0
    failed = 0

    try:
        if args.all:
            targets = fetch_realtors_for_batch(conn)
        else:
            if args.realtor_id <= 0:
                raise Exception("--realtor-id 또는 --all 옵션이 필요합니다.")
            targets = [fetch_realtor(conn, args.realtor_id)]

        print("=" * 80)
        print(f"[MAP TARGET COUNT] {len(targets)}")
        print("=" * 80)

        for realtor in targets:
            if not realtor:
                continue

            realtor_id = int(realtor.get("id") or 0)

            try:
                generate_for_realtor(
                    conn=conn,
                    realtor=realtor,
                    rest_api_key=rest_api_key,
                    js_key=js_key,
                )
                success += 1

            except Exception as e:
                failed += 1
                print(f"[MAP ERROR] realtor_id={realtor_id} / {e}")

                try:
                    update_realtor_map_result(
                        conn=conn,
                        realtor_id=realtor_id,
                        error_message=str(e),
                    )
                except Exception:
                    pass

        print("=" * 80)
        print(f"[MAP FINISH] success={success}, failed={failed}")
        print("=" * 80)

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


if __name__ == "__main__":
    main()
