# -*- coding: utf-8 -*-
"""
Hero card renderer for Naver real-estate blog drafts.

Purpose
- Naver Blog often strips/changes complex CSS copied from a preview page.
- Important visual sections should be rendered as a single image.
- This module creates a top hero card PNG/JPEG from collected property data and a source image.

Usage
    from services.hero_card_renderer import render_hero_card
    result = render_hero_card(detail, images)
    detail["hero_card_data_uri"] = result.get("data_uri", "")

The returned data_uri is intentionally used for preview-copy paste stability.
A physical JPG is also saved for debugging/backup.
"""

import base64
import io
import os
import re
from datetime import datetime

try:
    import requests
except Exception:  # pragma: no cover
    requests = None

try:
    from PIL import Image, ImageDraw, ImageFont, ImageFilter
except Exception:  # pragma: no cover
    Image = None
    ImageDraw = None
    ImageFont = None
    ImageFilter = None

try:
    from config import STORAGE_DIR
except Exception:  # pragma: no cover
    STORAGE_DIR = os.path.join(os.getcwd(), "storage")


PUBLIC_STORAGE_BASE_URL = os.environ.get(
    "PUBLIC_STORAGE_BASE_URL",
    "http://61.32.69.107:9000/storage",
).rstrip("/")

CARD_WIDTH = 1200
CARD_HEIGHT = 630


BAD_IMAGE_WORDS = [
    "logo", "icon", "sprite", "favicon", "profile", "cropimage",
    "blank", "button", "realtor", "broker",
]


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


def _image_url(img):
    if not img:
        return ""
    return (
        img.get("image_url")
        or img.get("public_url")
        or img.get("local_path")
        or ""
    )


def _is_floorplan(img):
    raw = " ".join([
        str((img or {}).get("image_type") or "").lower(),
        str((img or {}).get("image_category") or "").lower(),
        str((img or {}).get("file_name") or "").lower(),
        _image_url(img).lower(),
    ])
    return "floor" in raw or "plan" in raw or "평면" in raw


def _pick_source_image(images):
    """Pick a visually suitable image for the card background."""
    if not images:
        return ""

    candidates = []

    for img in images:
        url = _image_url(img)
        if not url:
            continue

        category = str(img.get("image_category") or "").lower()
        image_type = str(img.get("image_type") or "").lower()
        raw = " ".join([category, image_type, str(img.get("file_name") or "").lower(), url.lower()])

        if category == "realtor":
            continue
        if _is_floorplan(img):
            continue
        if any(word in raw for word in BAD_IMAGE_WORDS):
            continue

        score = 0
        if category in ["article", "inside"]:
            score += 40
        if category in ["building", "outside", "view", "complex", "exterior"]:
            score += 30
        if any(k in raw for k in ["living", "kitchen", "room", "거실", "주방", "실내"]):
            score += 20
        if any(k in raw for k in ["building", "complex", "외관", "단지", "전경"]):
            score += 10

        candidates.append((score, url))

    if not candidates:
        return ""

    candidates.sort(key=lambda x: x[0], reverse=True)
    return candidates[0][1]


def _download_image(url):
    if not url or Image is None:
        return None

    try:
        # Prefer local file if a Windows/local path is passed.
        local = url
        if local.startswith("file://"):
            local = local[7:]
        if os.path.exists(local):
            return Image.open(local).convert("RGB")
    except Exception:
        pass

    if requests is None:
        return None

    try:
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36",
            "Referer": "https://new.land.naver.com/",
        }
        res = requests.get(url, headers=headers, timeout=12)
        res.raise_for_status()
        return Image.open(io.BytesIO(res.content)).convert("RGB")
    except Exception:
        return None


def _cover_resize(img, size):
    target_w, target_h = size
    w, h = img.size

    if w <= 0 or h <= 0:
        return Image.new("RGB", size, (38, 42, 52))

    scale = max(target_w / w, target_h / h)
    new_w = int(w * scale)
    new_h = int(h * scale)

    img = img.resize((new_w, new_h), Image.LANCZOS)

    left = max(0, (new_w - target_w) // 2)
    top = max(0, (new_h - target_h) // 2)

    return img.crop((left, top, left + target_w, top + target_h))


def _font_candidates():
    return [
        "C:/Windows/Fonts/malgunbd.ttf",
        "C:/Windows/Fonts/malgun.ttf",
        "/usr/share/fonts/truetype/nanum/NanumGothicBold.ttf",
        "/usr/share/fonts/truetype/nanum/NanumGothic.ttf",
        "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
        "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
    ]


def _load_font(size, bold=False):
    if ImageFont is None:
        return None

    candidates = _font_candidates()
    if bold:
        candidates = [p for p in candidates if "bd" in p.lower() or "bold" in p.lower()] + candidates

    for path in candidates:
        try:
            if os.path.exists(path):
                return ImageFont.truetype(path, size=size)
        except Exception:
            pass

    try:
        return ImageFont.load_default()
    except Exception:
        return None


def _text_width(draw, text, font):
    try:
        bbox = draw.textbbox((0, 0), text, font=font)
        return bbox[2] - bbox[0]
    except Exception:
        return len(text) * 16


def _wrap_text(draw, text, font, max_width, max_lines=2):
    text = re.sub(r"\s+", " ", _safe_text(text))
    if not text:
        return []

    words = list(text) if not re.search(r"\s", text) else text.split()
    lines = []
    current = ""

    joiner = "" if words and len(words[0]) == 1 else " "

    for token in words:
        candidate = token if not current else current + joiner + token
        if _text_width(draw, candidate, font) <= max_width:
            current = candidate
        else:
            if current:
                lines.append(current)
            current = token
            if len(lines) >= max_lines:
                break

    if current and len(lines) < max_lines:
        lines.append(current)

    if len(lines) > max_lines:
        lines = lines[:max_lines]

    return lines


def _draw_round_rect(draw, xy, radius, fill, outline=None, width=1):
    try:
        draw.rounded_rectangle(xy, radius=radius, fill=fill, outline=outline, width=width)
    except Exception:
        draw.rectangle(xy, fill=fill, outline=outline)


def _format_title(detail):
    name = _safe_text(detail.get("article_title") or detail.get("article_name") or detail.get("apt_name") or "부동산 매물")
    trade = _safe_text(detail.get("trade_type") or detail.get("trade_type_name"))
    price = _safe_text(detail.get("price_text") or detail.get("deal_or_warrant_prc") or detail.get("price"))

    parts = [name]
    if trade:
        parts.append(trade)
    if price:
        parts.append(price)
    return " · ".join(parts)


def _detail_value(detail, *keys):
    for key in keys:
        value = _safe_text(detail.get(key))
        if value:
            return value
    return ""


def _build_badges(detail):
    badges = []

    trade = _detail_value(detail, "trade_type", "trade_type_name")
    price = _detail_value(detail, "price_text", "deal_or_warrant_prc", "price")
    area = _detail_value(detail, "area_text", "supply_area_text", "exclusive_area_text")
    floor = _detail_value(detail, "floor_text", "floor_info")
    direction = _detail_value(detail, "direction", "direction_name")

    if trade:
        badges.append(("거래", trade))
    if price:
        badges.append(("가격", price))
    if area:
        badges.append(("면적", area))
    if floor:
        badges.append(("층수", floor))
    if direction:
        badges.append(("방향", direction))

    return badges[:5]


def _save_paths(article_no):
    article_no = _safe_text(article_no) or "unknown"
    rel_dir = os.path.join("realestate", article_no, "hero_card")
    abs_dir = os.path.join(STORAGE_DIR, rel_dir)
    os.makedirs(abs_dir, exist_ok=True)

    filename = "hero_card.jpg"
    abs_path = os.path.join(abs_dir, filename)
    public_url = f"{PUBLIC_STORAGE_BASE_URL}/realestate/{article_no}/hero_card/{filename}"
    return abs_path, public_url


def render_hero_card(detail, images=None):
    """Render a top visual card.

    Returns dict:
        {
            "data_uri": "data:image/jpeg;base64,...",
            "file_path": "...",
            "public_url": "...",
            "source_url": "...",
        }
    If rendering fails, values are empty and draft generation should continue.
    """
    result = {
        "data_uri": "",
        "file_path": "",
        "public_url": "",
        "source_url": "",
    }

    if Image is None or ImageDraw is None:
        print("[HERO CARD SKIP] pillow not available")
        return result

    try:
        article_no = _safe_text(detail.get("article_no"))
        title = _format_title(detail)
        source_url = _pick_source_image(images or [])
        result["source_url"] = source_url

        bg = _download_image(source_url) if source_url else None

        if bg is None:
            bg = Image.new("RGB", (CARD_WIDTH, CARD_HEIGHT), (44, 50, 62))
        else:
            bg = _cover_resize(bg, (CARD_WIDTH, CARD_HEIGHT))
            try:
                bg = bg.filter(ImageFilter.GaussianBlur(radius=1.0))
            except Exception:
                pass

        overlay = Image.new("RGBA", (CARD_WIDTH, CARD_HEIGHT), (0, 0, 0, 0))
        od = ImageDraw.Draw(overlay)
        # left dark gradient
        for x in range(CARD_WIDTH):
            alpha = int(178 * (1 - min(x / CARD_WIDTH, 1)) + 72)
            od.line([(x, 0), (x, CARD_HEIGHT)], fill=(0, 0, 0, max(72, min(210, alpha))))
        bg = Image.alpha_composite(bg.convert("RGBA"), overlay)

        draw = ImageDraw.Draw(bg)

        title_font = _load_font(54, bold=True)
        sub_font = _load_font(28, bold=False)
        badge_label_font = _load_font(21, bold=False)
        badge_value_font = _load_font(30, bold=True)
        small_font = _load_font(22, bold=False)

        # top label
        label = "NAVER REAL ESTATE PICK"
        _draw_round_rect(draw, (70, 62, 360, 108), 22, fill=(255, 255, 255, 46), outline=(255, 255, 255, 80), width=1)
        draw.text((92, 73), label, fill=(255, 255, 255, 230), font=small_font)

        # title
        y = 150
        for line in _wrap_text(draw, title, title_font, 960, max_lines=2):
            draw.text((70, y), line, fill=(255, 255, 255, 255), font=title_font)
            y += 70

        intro = _safe_text(detail.get("article_feature_description") or detail.get("feature_description") or "")
        if not intro:
            intro = "사진과 조건을 함께 확인해볼 수 있는 부동산 매물입니다."
        for line in _wrap_text(draw, intro, sub_font, 860, max_lines=2):
            draw.text((72, y + 12), line, fill=(235, 239, 245, 235), font=sub_font)
            y += 40

        # badges
        badges = _build_badges(detail)
        bx = 70
        by = 430
        for label, value in badges[:4]:
            w = 250
            h = 92
            _draw_round_rect(draw, (bx, by, bx + w, by + h), 22, fill=(255, 255, 255, 235))
            draw.text((bx + 24, by + 16), label, fill=(89, 99, 115, 255), font=badge_label_font)
            short_value = value[:16]
            draw.text((bx + 24, by + 46), short_value, fill=(17, 24, 39, 255), font=badge_value_font)
            bx += w + 18
            if bx + w > CARD_WIDTH - 70:
                break

        office = ""
        realtor = detail.get("realtor_info") or {}
        if isinstance(realtor, dict):
            office = _safe_text(realtor.get("office_name"))
        if not office:
            office = _safe_text(detail.get("office_name"))

        footer = office or "부동산 매물 안내"
        draw.text((74, CARD_HEIGHT - 62), footer[:36], fill=(255, 255, 255, 225), font=small_font)

        abs_path, public_url = _save_paths(article_no)
        rgb = bg.convert("RGB")
        rgb.save(abs_path, "JPEG", quality=88, optimize=True)

        buffer = io.BytesIO()
        rgb.save(buffer, "JPEG", quality=86, optimize=True)
        data_uri = "data:image/jpeg;base64," + base64.b64encode(buffer.getvalue()).decode("ascii")

        result.update({
            "data_uri": data_uri,
            "file_path": abs_path,
            "public_url": public_url,
        })

        print(f"[HERO CARD CREATED] article_no={article_no} path={abs_path}")
        return result

    except Exception as e:
        print("[HERO CARD ERROR]", str(e))
        return result
