﻿"""
Two-pass AI Description Engine for Canadian used car dealership.
Writer -> QA Editor -> Compact HTML output.
"""
import json, logging, re
from datetime import datetime

logger = logging.getLogger(__name__)

WORD_LIMIT = 180

def _to_int(val):
    """Safely convert a DB value to int, handling strings."""
    if val is None or val == "":
        return 0
    try:
        return int(float(str(val)))
    except (ValueError, TypeError):
        return 0


def _fmt_km(val):
    """Format kilometers with commas, handling string values."""
    try:
        return f"{int(float(str(val))):,}"
    except (ValueError, TypeError):
        return str(val)


def _fmt_price(val):
    """Format price as CAD string."""
    try:
        return f"${int(float(str(val))):,}"
    except (ValueError, TypeError):
        return "Contact for price"


def _diverse_feature_sample(features, target=30):
    """Return a deduped, strided subset of features so the model sees variety
    (safety, drivetrain, comfort, audio, wheels, ...) instead of the first N
    of one category. Deterministic — no random."""
    if not features:
        return []
    cleaned = []
    seen = set()
    for f in features:
        if f is None:
            continue
        s = str(f).strip()
        if not s:
            continue
        key = s.lower()
        if key in seen:
            continue
        seen.add(key)
        cleaned.append(s)
    if len(cleaned) <= target:
        return cleaned
    step = len(cleaned) / target
    out = []
    i = 0.0
    while len(out) < target and int(i) < len(cleaned):
        out.append(cleaned[int(i)])
        i += step
    return out


def build_seo_description_prompt(vehicle, features_list=None, fix_notes=""):
    """Build the SEO-optimized compact-HTML description writer prompt for a vehicle."""
    year = vehicle.get("year") or ""
    make = vehicle.get("make") or ""
    model = vehicle.get("model") or ""
    trim = vehicle.get("trim") or ""
    body_style = vehicle.get("body_style") or ""
    transmission = vehicle.get("transmission") or ""
    fuel_type = vehicle.get("fuel_type") or ""
    drive_line = vehicle.get("drive_line") or ""
    exterior_color = vehicle.get("exterior_color") or ""
    engine = vehicle.get("engine") or ""
    doors = vehicle.get("doors") or ""
    price = vehicle.get("price") or 0
    certified = vehicle.get("certified")
    dealer_city = vehicle.get("dealer_city", "Toronto")
    dealer_province = vehicle.get("dealer_province", "ON")
    features_text = "\n".join(f"- {f}" for f in (features_list or [])) or "None listed"
    price_str = _fmt_price(price)
    certified_note = "This vehicle is certified (CPO)." if certified else ""
    fix_section = f"\nFIX THESE ISSUES: {fix_notes}\n" if fix_notes else ""
    # Location is OPTIONAL — only inject it when a real dealer_city is supplied. Do NOT
    # default to Toronto (that invented a city on every listing). When absent, the AI is
    # told explicitly not to mention any location.
    has_location = bool(dealer_city)
    location_intro = f" for a Canadian used car dealership in {dealer_city}, {dealer_province}" if has_location else " for a Canadian used car dealership"
    location_line = f"Location: {dealer_city}, {dealer_province}, Canada\n" if has_location else ""
    location_rule = "" if has_location else "- Do NOT mention any city, province, or location — none is provided, so do not invent one.\n"
    return (
        f"You are an expert automotive SEO copywriter{location_intro}.\n\n"
        f"VEHICLE: {year} {make} {model} {trim} | {body_style} | {doors}dr | "
        f"{transmission} | {fuel_type} | {drive_line} | Ext: {exterior_color} | Engine: {engine} | "
        f"Price: {price_str} CAD | {certified_note}\n"
        f"{location_line}\n"
        f"FEATURES:\n{features_text}\n{fix_section}\n"
        f"Write an SEO-optimized vehicle listing description in compact HTML.\n\n"
        f"SEO REQUIREMENTS:\n"
        f"- Naturally integrate the searchable keywords a Canadian buyer would query: the year, make, model, trim, and body style MUST appear in the opening sentence.\n"
        f"- Write for BOTH human buyers and search engines — informative and factual, keyword-rich without keyword stuffing.\n"
        f"- The intro should describe the vehicle accurately and, where it fits and is supported by the specs, its Canadian relevance (winters, highway comfort, etc.). Do not over-sell or use brochure/marketing language.\n\n"
        f"OUTPUT REQUIREMENTS:\n"
        f"- Return JSON: {{\"description_html\": \"...\"}}\n"
        f"- description_html must be well-formed HTML with NO markdown fences or backticks.\n"
        f"- Structure: one intro <p> (2-3 sentences, keyword-rich hook) + a <ul> with 3-5 <li> bullets + an optional closing <p> (1-sentence value/CTA).\n"
        f"- Each bullet must highlight a DISTINCT standout feature drawn from the FEATURES list or the specs above (key options, trim highlights, drivetrain, design, value). DO NOT repeat the same fact in multiple bullets or restate something already in the intro.\n"
        f"- TOTAL word count across intro + bullets + closing MUST NOT exceed {WORD_LIMIT} words.\n"
        f"- Use CAD pricing references naturally where appropriate (e.g. \"strong value at this price point\").\n\n"
        f"STRICT RULES:\n"
        f"- NEVER mention mileage, kilometres, km, or the odometer reading — anywhere in the description.\n"
        f"- NEVER mention the VIN or internal stock numbers.\n"
        f"- NO fabricated specs, options, or features not listed above.\n"
        f"- NO warranty, finance, or delivery promises unless stated.\n"
        f"- NO marketing superlatives (\"stunning\", \"iconic\", \"rare blend\", \"ultimate\", \"unleash\", \"elevate\"). Factual, professional tone.\n"
        f"- NO markdown, NO code fences, NO JSON fences in the HTML.\n"
        f"- NO USA/UK/Europe references. Canada only.\n"
        f"- Use ONLY the facts listed under VEHICLE and FEATURES. If a spec is empty or a feature is not listed, do NOT mention it and do NOT invent one.\n"
        f"- If FEATURES is 'None listed', write only from the VEHICLE specs — do NOT invent features.\n"
        f"- Only reference Canadian conditions (winters, AWD/4WD traction, highway comfort) when directly supported by the specs above (e.g. drive_line is AWD/4WD). Do not assume features the specs do not state.\n"
        f"{location_rule}"
        f"- Do NOT write a novel — tight, scannable, professional."
    )

def build_qa_editor_prompt(vehicle, generated_output):
    """Build the QA editor review prompt for compact-HTML output."""
    year = vehicle.get("year") or ""
    make = vehicle.get("make") or ""
    model = vehicle.get("model") or ""
    trim = vehicle.get("trim") or ""
    transmission = vehicle.get("transmission") or ""
    fuel_type = vehicle.get("fuel_type") or ""
    drive_line = vehicle.get("drive_line") or ""
    engine = vehicle.get("engine") or ""
    exterior = vehicle.get("exterior_color") or ""
    body = vehicle.get("body_style") or ""
    doors = vehicle.get("doors") or ""
    price = vehicle.get("price") or 0
    certified = vehicle.get("certified")
    html = (generated_output.get("description_html", "") or "")[:1000]
    return (
        f"You are a QA Editor for a Canadian used car dealership. Score this vehicle description.\n\n"
        f"GROUND TRUTH: {year} {make} {model} {trim} | {transmission} | {fuel_type} | {drive_line} | "
        f"Engine: {engine} | Ext: {exterior} | Body: {body} | Doors: {doors} | "
        f"{_fmt_price(price)} CAD | Certified: {'Yes' if certified else 'No'}\n\n"
        f"GENERATED HTML:\n{html}\n\n"
        f"Score 0-10 on each criterion:\n"
        f"- accuracy: all facts match ground truth (no invented options, specs, or claims)\n"
        f"- seo: year/make/model/trim/body style naturally integrated; keyword-rich without stuffing; compelling to buyers\n"
        f"- compactness: word count is tight (target ~{WORD_LIMIT} words max), no filler, no novel\n"
        f"- no_forbidden: NO mileage/km/odometer, NO VIN, NO stock numbers anywhere (score 0 if any appear)\n"
        f"- no_repetition: each bullet states a DISTINCT fact; no fact repeated across intro/bullets (score 0 if a fact is repeated)\n"
        f"- html_quality: valid <p> + <ul><li> structure, no markdown fences, no raw JSON\n"
        f"- canada_context: references CAD / Canada where relevant, no USA/UK/Europe/VAT\n\n"
        f"Return JSON: {{\"scores\":{{...}},\"total_score\":N,\"passed\":true/false,"
        f"\"issues\":[...],\"fix_instructions\":\"...\"}}\n"
        f"PASS: total_score >= 80 AND accuracy >= 8 AND no_forbidden >= 9 AND no_repetition >= 8 AND html_quality >= 8"
    )

def generate_vehicle_description(listing_id, db, ai_call, max_attempts=3):
    """Two-pass AI description generation with QA verification.

    Args:
        listing_id: DB row ID of the listing.
        db: SQLite database connection (must support execute + fetchone).
        ai_call: Callable ai_call(prompt) -> str (raw JSON response text).
        max_attempts: Max writer->QA retry loops.

    Returns:
        dict with keys: status, html, score, attempts
    """
    vehicle = db.execute("SELECT * FROM listings WHERE id = ?", (listing_id,)).fetchone()
    if not vehicle:
        return {"status": "error", "error": f"Listing {listing_id} not found"}

    vehicle_dict = dict(vehicle)
    vehicle_dict.setdefault("dealer_city", "Toronto")
    vehicle_dict.setdefault("dealer_province", "ON")

    # Identity guard: refuse to generate without the essentials — prevents the
    # model from inventing a vehicle's identity. Live cars have 100% coverage.
    if not (vehicle_dict.get("make") and vehicle_dict.get("model") and vehicle_dict.get("year")):
        return {
            "status": "error",
            "attempts": 0,
            "best_score": 0,
            "html": "",
            "error": "Missing make/model/year — cannot generate a grounded description.",
        }

    features_list = []
    try:
        meta = json.loads(vehicle["source_metadata_json"] or "{}")
        if isinstance(meta, dict):
            features_list = meta.get("features", [])
    except Exception:
        pass
    # Diverse, deduped, strided subset so bullets span categories, not cluster.
    features_list = _diverse_feature_sample(features_list, target=30)

    # Single-pass writer (fast: one model call per listing). The QA editor loop was
    # causing ~6 calls/listing and 10-minute runtimes; the writer prompt is already
    # strong (SEO, no km/VIN, no repetition), so we skip the QA pass for speed.
    try:
        raw = ai_call(build_seo_description_prompt(vehicle_dict, features_list, ""))
        output = json.loads(raw) if isinstance(raw, str) else raw
        best_html = (output.get("description_html", "") or "").strip()
    except Exception as e:
        logger.error(f"Writer failed: {e}")
        return {
            "status": "error",
            "attempts": 1,
            "best_score": 0,
            "html": "",
            "error": f"AI writer call failed: {e}",
        }

    if not best_html:
        return {
            "status": "error",
            "attempts": 1,
            "best_score": 0,
            "html": "",
            "error": "AI returned an empty description. Check the model response format.",
        }

    _save_desc(db, listing_id, best_html, 100, "published")
    return {"status": "published", "attempts": 1, "score": 100, "html": best_html}

def _save_desc(db, listing_id, html, score, status="published"):
    """Save the generated description HTML to the listing."""
    safe_html = (html or "").strip()
    if not safe_html:
        return
    # Strip markdown code fences that might have leaked through
    safe_html = re.sub(r"^```(?:html|json)?\s*", "", safe_html)
    safe_html = re.sub(r"\s*```$", "", safe_html)
    # Generate a plain-text short description from the HTML
    text = re.sub(r"<[^>]+>", "", safe_html)
    short_desc = text[:200].rsplit(" ", 1)[0] + "..." if len(text) > 200 else text
    db.execute(
        "UPDATE listings SET description_html_en=?, short_description_en=?, "
        "ai_description_score=?, ai_description_status=?, updated_at=? WHERE id=?",
        (safe_html, short_desc, score, status, datetime.utcnow().isoformat(), listing_id),
    )
    db.commit()
