"""Talks to whichever AI provider to generate realistic glass UI (glassmorphism) SVGs."""

from __future__ import annotations

import re
import time
import app_config
import color_utils

GLASS_SYSTEM_PROMPT = (
    "You are an elite UI/UX designer and expert SVG vector coder who specializes in "
    "photorealistic 'glassmorphism' (frosted/liquid glass) UI elements for stock asset "
    "packs — the kind sold on Adobe Stock as 'glass UI kit' preview sheets. "
    "Output ONLY raw valid SVG code, nothing else — no markdown code fences, no explanation, "
    "no commentary, no thinking out loud, no text before or after the SVG. "
    'Your entire response MUST start with the exact characters "<svg" and end with "</svg>" — '
    "nothing else on either side. This is machine-parsed; any extra text breaks the app. "
    "Your task is to design a single GLASS UI ELEMENT (button, card, tile, circle, or panel) "
    "centered on a square canvas, styled like a premium stock 'glassmorphism' design. "
    "CRITICAL: you MUST use <linearGradient> and/or <radialGradient> for every glass "
    "surface fill (a soft light-to-transparent ramp), and you MUST use <filter> with "
    "<feGaussianBlur> to soften highlight streaks and give a faint drop shadow beneath "
    "the element so it looks like it is floating over whatever is behind it. Never draw "
    "a glass surface as one single flat opaque fill. "
    "Every glass element needs, layered in this order: (1) a soft blurred drop shadow, "
    "(2) the translucent gradient-filled glass body with rounded/soft geometry appropriate "
    "to its shape, (3) a bright thin semi-transparent rim-light stroke tracing its edge "
    "(strongest along the top edge), (4) one or more diagonal bright highlight streaks or "
    "soft glossy blobs suggesting light reflecting off curved glass. "
    "CRITICAL — NEVER produce an indistinct, amorphous colored blob with no visible edge. "
    "Layer (2), the glass body, MUST be a single crisp closed vector shape (<rect>, <circle>, "
    "<ellipse>, or a rounded-rect <path>) with a clean, describable outline appropriate to its "
    "shape class — someone looking at the result must be able to point to exactly where each "
    "element begins and ends. Apply <feGaussianBlur> ONLY to the drop shadow (layer 1) and to "
    "the glow behind a highlight streak (part of layer 4) — never blur the glass body's own "
    "fill or its outline. If more than one element is on the canvas, give each one clear empty "
    "space around it so they read as distinct separate pieces, never overlapping or merging "
    "into one shapeless mass. Before finishing, double-check every element still has a crisp, "
    "sharp-edged silhouette — if any element looks like a soft blurry smear instead of a "
    "definite shape, redraw it with a solid unblurred outline. "
    "NEVER draw any text, letters, or words (no <text>/<tspan> tags — fonts fail across "
    "machines) unless explicitly told there is a labeled card, in which case represent any "
    "label only as abstract short flat-colored bar/line shapes standing in for text, never "
    "as real letterforms. "
    "The element must be centered on the canvas with generous padding on all sides, "
    "and must look premium, modern, photorealistic, and immediately recognizable as glass, "
    "not flat plastic or matte material."
)

# A hung key must never stall the whole rotation: every call has a hard timeout, and (aside from
# the brief in-place retry for a "model overloaded" 503 above) there are NO retries on a failing
# key — the app moves straight on to the next key instead.
GEN_TIMEOUT_SECONDS = 75

RATE_LIMIT_HINTS = ("rate limit", "429", "quota", "resource_exhausted", "too many requests", "insufficient_quota")
BAD_KEY_HINTS = (
    "api key not valid", "invalid api key", "incorrect api key", "unauthorized", "401", "invalid_api_key",
    "403", "permission_denied", "permission denied", "denied access", "access denied", "forbidden",
)
# Google's own model is overloaded (transient, NOT the key's fault). Google explicitly says
# these are "usually temporary" — so this is the one error class worth retrying in place,
# on the SAME key, with a short backoff, before giving up and rotating to the next key.
OVERLOAD_HINTS = ("503", "unavailable", "overloaded", "high demand", "server error", "internal error", "502", "504")

# How many extra in-place attempts a single key gets when the model itself reports overload,
# and how long to wait between them. Kept short so GEN_TIMEOUT_SECONDS / GENERATION_TIME_BUDGET
# in app.py still hold — this does not turn into an unbounded retry loop.
OVERLOAD_RETRY_ATTEMPTS = 2
OVERLOAD_RETRY_DELAY_SECONDS = 3

def looks_like_rate_limit(message: str) -> bool:
    return any(h in (message or "").lower() for h in RATE_LIMIT_HINTS)

def looks_like_bad_key(message: str) -> bool:
    return any(h in (message or "").lower() for h in BAD_KEY_HINTS)

def looks_like_overloaded(message: str) -> bool:
    return any(h in (message or "").lower() for h in OVERLOAD_HINTS)

class GenerationError(Exception):
    pass

def _extract_svg(text: str):
    text = (text or "").strip()
    text = re.sub(r"^```(svg|xml|html)?", "", text, flags=re.IGNORECASE).strip()
    text = re.sub(r"```$", "", text).strip()
    match = re.search(r"<svg[\s\S]*</svg>", text, flags=re.IGNORECASE)
    return match.group(0) if match else None

class InvalidSVGError(GenerationError):
    """The model answered but not with a usable SVG. Not the key's fault — just move on."""


def _gemini_call(api_key, model, system, user_text, max_tokens=4500, timeout=GEN_TIMEOUT_SECONDS):
    from google import genai
    from google.genai import types
    client = genai.Client(
        api_key=api_key,
        http_options=types.HttpOptions(timeout=int(timeout * 1000), retry_options=types.HttpRetryOptions(attempts=1)),
    )
    response = client.models.generate_content(
        model=model, contents=[user_text],
        config=types.GenerateContentConfig(system_instruction=system, max_output_tokens=max_tokens),
    )
    return response.text or ""


def _call(provider: dict, system: str, user_text: str, max_tokens=4500, timeout=GEN_TIMEOUT_SECONDS) -> str:
    if not provider or not provider.get("api_key"):
        raise GenerationError("This API key entry is empty.")
    model = (provider.get("model") or app_config.DEFAULT_GEMINI_MODEL).strip()

    last_exc = None
    for attempt in range(1 + OVERLOAD_RETRY_ATTEMPTS):
        try:
            return _gemini_call(provider["api_key"], model, system, user_text, max_tokens, timeout)
        except Exception as exc:
            last_exc = exc
            # Only "model overloaded / 503" is worth retrying on the SAME key — it is Google's
            # capacity issue, not this key's fault, and Google itself says it's usually brief.
            # Bad keys / real rate-limit-quota errors are NOT retried here; they fall straight
            # through so the caller in app.py can rotate to the next key immediately.
            if attempt < OVERLOAD_RETRY_ATTEMPTS and looks_like_overloaded(str(exc)):
                time.sleep(OVERLOAD_RETRY_DELAY_SECONDS * (attempt + 1))
                continue
            break
    raise GenerationError(str(last_exc)) from last_exc


def _call_and_extract(provider: dict, system: str, user_text: str, max_tokens: int) -> str:
    """One call, one attempt. If the answer isn't a usable SVG the caller simply moves on to the
    next key — no in-place retry, so users never wait twice on the same key."""
    text = _call(provider, system, user_text, max_tokens)
    svg = _extract_svg(text)
    if not svg:
        raise InvalidSVGError("The model did not return a valid SVG.")
    return svg

def _glass_bg_line(bg_choice: str | None) -> str:
    choice = (bg_choice or "random").strip().lower()
    if choice in ("transparent", "none"):
        return "Background: Leave the canvas fully transparent — draw ONLY the glass element and its shadows/highlights, nothing else."
    
    # Get the actual background color
    actual_color = color_utils.get_bg_color(bg_choice)
    return f"Background: The glass element will be shown floating over a {actual_color} background. Keep the canvas itself transparent (no background rect) — the {actual_color} is just context for how translucent/tinted to make the glass."

def _custom_prompt_line(extra_prompt: str | None) -> str:
    text = (extra_prompt or "").strip()
    if not text:
        return ""
    return (
        "\n\nAdditional Client Instructions (apply these on top of everything above, "
        "without breaking the hard rules — no real text/letters, still a single glass "
        f"element, still centered):\n{text}\n"
    )

def generate_glass_svg(provider: dict, glass_type: str, style_label: str, canvas_size_label: str, tint_color: str | None = None, bg_choice: str | None = None, cleanup: bool = True, extra_prompt: str | None = None, intensity_label: str | None = None) -> str:
    size = app_config.get_canvas_size(canvas_size_label)
    width, height = size, size
    shape_desc = app_config.GLASS_TYPES.get(glass_type, "")
    style_desc = app_config.GLASS_STYLES.get(style_label, "")
    intensity_desc = app_config.HIGHLIGHT_INTENSITIES.get(
        intensity_label, app_config.HIGHLIGHT_INTENSITIES[app_config.DEFAULT_HIGHLIGHT_INTENSITY]
    )

    color_line = "Accent Tint: No specific tint requested — keep the glass neutral/clear (very light white gradient)."
    if tint_color and str(tint_color).strip().lower() != "auto":
        color_line = f"Accent Tint: Use {tint_color} as the low-opacity tint color for the glass gradient (see style rules for opacity range)."

    user_text = (
        f"Task: Generate a single glass UI element centered on a square canvas.\n\n"
        f"Canvas: viewBox=\"0 0 {width} {height}\" (square {width}×{height}).\n\n"
        f"Shape Guidelines (Type — follow exactly):\n{shape_desc}\n\n"
        f"Rendering Guidelines (Glass Style — follow exactly):\n{style_desc}\n\n"
        f"{intensity_desc}\n\n"
        f"{color_line}\n"
        f"{_glass_bg_line(bg_choice)}"
        f"{_custom_prompt_line(extra_prompt)}\n\n"
        f"Output ONLY the raw SVG code, starting with <svg and ending with </svg>."
    )

    svg = _call_and_extract(provider, GLASS_SYSTEM_PROMPT, user_text, max_tokens=6000)

    import svg_cleanup
    if cleanup: 
        svg = svg_cleanup.clean_svg(svg, width=width, height=height, auto_fit=True)
    
    # Apply background
    bg_color = color_utils.get_bg_color(bg_choice)
    return svg_cleanup.apply_background(svg, bg_color, width=width, height=height)

def refine_glass_svg(provider: dict, previous_svg: str, instruction: str, glass_type: str, style_label: str, canvas_size_label: str, tint_color: str | None = None, bg_choice: str | None = None, cleanup: bool = True, extra_prompt: str | None = None, intensity_label: str | None = None) -> str:
    size = app_config.get_canvas_size(canvas_size_label)
    width, height = size, size
    shape_desc = app_config.GLASS_TYPES.get(glass_type, "")
    style_desc = app_config.GLASS_STYLES.get(style_label, "")
    intensity_desc = app_config.HIGHLIGHT_INTENSITIES.get(
        intensity_label, app_config.HIGHLIGHT_INTENSITIES[app_config.DEFAULT_HIGHLIGHT_INTENSITY]
    )

    original_brief_line = f"\nOriginal Custom Instructions (still apply unless the change request overrides them):\n{extra_prompt.strip()}\n" if (extra_prompt or "").strip() else ""

    user_text = (
        "Task: Below is a glass UI element you previously designed, followed by a "
        "requested change. Apply ONLY that requested change and return the COMPLETE "
        "revised SVG, keeping the same canvas size and centered on the canvas.\n\n"
        f"Original Design Rules:\n{shape_desc}\n{style_desc}\n{intensity_desc}\n"
        f"{original_brief_line}\n"
        "Previous SVG:\n"
        f"{previous_svg}\n\n"
        "Client's Change Request:\n"
        f"{instruction}\n\n"
        "Output ONLY the raw, complete, updated SVG code."
    )
    svg = _call_and_extract(provider, GLASS_SYSTEM_PROMPT, user_text, max_tokens=6000)

    import svg_cleanup
    if cleanup: 
        svg = svg_cleanup.clean_svg(svg, width=width, height=height, auto_fit=True)
    
    # Apply background
    bg_color = color_utils.get_bg_color(bg_choice)
    return svg_cleanup.apply_background(svg, bg_color, width=width, height=height)