"""Banner / hero image generator for README.md and other project files.

Combines LLM prompt engineering with image generation to produce
high-quality, branded banners for developer tools and libraries.

The LLM reads project context (README, title, description) and writes
an optimized image generation prompt automatically — no prompt engineering
knowledge required from the user.

Example:
    from sdkrouter import SDKRouter

    client = SDKRouter(api_key="sk_live_xxx")

    # Generate from README, save to disk
    result = client.banner.generate(
        source="README.md",
        output="assets/banner.png",
    )
    print(f"Saved to: {result.saved_to}")
    print(f"CDN URL:  {result.image_url}")

    # From title + description
    result = client.banner.generate(
        title="My Python Library",
        description="Semantic search for developers",
        preset="github",
        output="banner.webp",
    )

    # Skip LLM, use your own prompt directly
    result = client.banner.generate(
        prompt="Pixar-style 3D robot coding at a futuristic terminal, vivid colors",
        output="banner.png",
    )
"""

from __future__ import annotations

import logging
import time
import urllib.request
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING

from pydantic import BaseModel, Field as PydanticField

from .._api.client import BaseResource, AsyncBaseResource

if TYPE_CHECKING:
    from .._config import SDKConfig

logger = logging.getLogger(__name__)


# ── Size presets ─────────────────────────────────────────────────────────────

PRESETS: dict[str, str] = {
    "github": "1792x1024",   # README.md hero (16:9 widescreen)
    "social": "1200x630",    # Twitter / LinkedIn card
    "og":     "1200x630",    # OpenGraph meta image
    "wide":   "1792x512",    # Wide website banner
    "square": "1024x1024",   # npm / PyPI avatar / icon
}

DEFAULT_PRESET = "github"
DEFAULT_IMAGE_MODEL = "@balanced"
DEFAULT_LLM_MODEL = "@smart"


# ── System prompt ─────────────────────────────────────────────────────────────

_SYSTEM_PROMPT = """\
You are a senior visual designer specializing in developer tool branding and README hero images.

Your job: read a project's context (title, description, README) and write a detailed, vivid
image generation prompt that will produce a stunning banner image.

## Output requirements
- 150–250 words, rich in visual detail
- Describe the scene, lighting, colors, mood, and composition
- Tech-aesthetic: clean, modern, slightly futuristic — Pixar/Disney 3D quality render
- No text, logos, or letters in the image (image gen handles this poorly)
- Avoid clichés: no generic "rocket ships", no hands typing on laptops

## Style guide
- Render quality: Pixar/Disney 3D (subsurface scattering, global illumination, ray tracing)
- Color palette: vivid, saturated; pick 2–3 dominant colors that match the project's personality
- Composition: wide, cinematic — one clear focal object that represents the core concept
- Mood: confident, clever, slightly playful — this is a tool built by engineers for engineers
- Lighting: dramatic studio lighting with colored accent glow

## Negative (what to AVOID)
photorealistic photograph, stock photo, clipart, flat 2D illustration, amateur render,
busy crowded scene, text in image, logos, dark muddy colors, generic office scene

## How to interpret the project context
1. Extract the CORE CONCEPT (what does this tool DO in one word — e.g. "route", "search", "embed")
2. Turn that concept into a VISUAL METAPHOR (e.g. "route" → glowing network of roads at night)
3. Add the MASCOT or FOCAL OBJECT (a character or object representing the tool)
4. Describe the SCENE around it (environment, props, background)
5. Specify LIGHTING and COLORS precisely

Return ONLY the image generation prompt. No preamble, no explanation, no markdown.
"""


# ── Pydantic schema for structured LLM output ────────────────────────────────

class _BannerPrompt(BaseModel):
    prompt: str = PydanticField(
        description="Vivid, detailed image generation prompt, 150–250 words"
    )


# ── Result dataclass ──────────────────────────────────────────────────────────

@dataclass
class BannerResult:
    """Result of a banner generation call."""

    image_url: str
    """CDN URL of the generated image."""

    prompt: str
    """Final image generation prompt used."""

    preset: str
    """Size preset used (github, social, og, wide, square)."""

    size: str
    """Actual image dimensions, e.g. '1792x1024'."""

    model: str
    """Image generation model used."""

    llm_model: str | None
    """LLM model used for prompt enhancement (None if prompt was provided directly)."""

    cost_usd: float
    """Estimated total cost in USD (LLM + image gen)."""

    duration_ms: int
    """Total pipeline duration in milliseconds."""

    saved_to: Path | None = field(default=None)
    """Local file path if output= was specified, otherwise None."""

    @property
    def image_cdn_url(self) -> str:
        """Alias for image_url."""
        return self.image_url


# ── Helper: read context ──────────────────────────────────────────────────────

def _read_source(source: str | Path) -> str:
    """Read a local file or fetch from a GitHub URL."""
    path = Path(source)
    if path.exists():
        return path.read_text(encoding="utf-8", errors="replace")

    # Try as a GitHub URL → convert to raw URL
    url = str(source)
    if "github.com" in url and "raw.githubusercontent.com" not in url:
        url = (
            url.replace("github.com", "raw.githubusercontent.com")
               .replace("/blob/", "/")
        )
    try:
        with urllib.request.urlopen(url, timeout=10) as r:
            return r.read().decode("utf-8", errors="replace")
    except Exception as exc:
        logger.warning("Could not fetch source %s: %s", source, exc)
        return ""


def _build_user_context(
    *,
    source: str | Path | None,
    title: str | None,
    description: str | None,
    github_url: str | None,
) -> str:
    parts: list[str] = []

    if title:
        parts.append(f"Project name: {title}")
    if description:
        parts.append(f"Description: {description}")

    readme_text = ""
    if source:
        readme_text = _read_source(source)
    elif github_url:
        # Try to fetch README.md from the repo
        base = github_url.rstrip("/")
        for branch in ("main", "master"):
            url = f"{base}/raw/{branch}/README.md"
            readme_text = _read_source(url)
            if readme_text:
                break

    if readme_text:
        # Trim to ~3000 chars to stay within token limits
        parts.append(f"README content (excerpt):\n\n{readme_text[:3000]}")

    if not parts:
        parts.append("A developer tool or Python library.")

    return "\n\n".join(parts)


def _save_image(cdn_url: str, output: str | Path) -> Path:
    """Download image from CDN URL and save to output path."""
    out_path = Path(output)
    out_path.parent.mkdir(parents=True, exist_ok=True)

    with urllib.request.urlopen(cdn_url, timeout=60) as r:
        data = r.read()

    out_path.write_bytes(data)
    logger.info("Banner saved: %s (%d KB)", out_path, len(data) // 1024)
    return out_path


# ── Sync resource ─────────────────────────────────────────────────────────────

class BannerResource(BaseResource):
    """Banner / hero image generator.

    Combines LLM prompt engineering with image generation.
    Saves the result directly to disk when output= is specified.
    """

    def __init__(self, config: "SDKConfig") -> None:
        super().__init__(config)

    def generate(
        self,
        *,
        # Content source — at least one required (or prompt=)
        source: str | Path | None = None,
        title: str | None = None,
        description: str | None = None,
        github_url: str | None = None,

        # Skip LLM — use this prompt directly
        prompt: str | None = None,

        # Output
        output: str | Path | None = None,

        # Presets and image options
        preset: str = DEFAULT_PRESET,
        model: str = DEFAULT_IMAGE_MODEL,
        quality: str = "hd",
        style: str = "vivid",

        # LLM options
        llm_model: str = DEFAULT_LLM_MODEL,
        enhance_prompt: bool = True,

        # Dev helpers
        dry_run: bool = False,
    ) -> BannerResult:
        """Generate a banner image for a project.

        Args:
            source: Path to a local README.md or other markdown file.
                    Can also be a GitHub file URL.
            title: Project title (used as context for LLM prompt).
            description: Short project description.
            github_url: GitHub repository URL — README.md is fetched automatically.
            prompt: Skip LLM enhancement and use this image generation prompt directly.
            output: Local file path to save the image to (e.g. "assets/banner.png").
                    If not set, the image is generated but not saved locally.
            preset: Size preset: "github" (1792x1024), "social" (1200x630),
                    "og" (1200x630), "wide" (1792x512), "square" (1024x1024).
            model: Image generation model. Aliases: @cheap, @balanced, @smart, @fast.
            quality: Image quality: "standard" or "hd".
            style: Image style: "natural" or "vivid".
            llm_model: LLM model for prompt enhancement. Aliases: @smart, @balanced.
                       Ignored when prompt= is provided.
            enhance_prompt: If True (default), use LLM to enhance the prompt from context.
                            Set False + provide prompt= to skip LLM entirely.
            dry_run: If True, generate and return the prompt without calling image gen API.

        Returns:
            BannerResult with image_url, prompt, saved_to path, cost, and duration.

        Raises:
            ValueError: If neither source/title/description/github_url nor prompt is provided.
        """
        t0 = time.monotonic()
        size = PRESETS.get(preset, PRESETS[DEFAULT_PRESET])
        total_cost = 0.0
        used_llm_model: str | None = None

        # ── Step 1: Build or enhance prompt ──────────────────────────────────
        if prompt:
            final_prompt = prompt
            logger.info("Using provided prompt directly (LLM skipped)")
        elif enhance_prompt:
            if not any([source, title, description, github_url]):
                raise ValueError(
                    "Provide at least one of: source, title, description, github_url, or prompt="
                )
            user_ctx = _build_user_context(
                source=source,
                title=title,
                description=description,
                github_url=github_url,
            )
            final_prompt, llm_cost = self._enhance_prompt(user_ctx, llm_model)
            total_cost += llm_cost
            used_llm_model = llm_model
        else:
            raise ValueError(
                "Either provide prompt= or set enhance_prompt=True with source/title/description."
            )

        logger.info("Banner prompt (%d chars): %s…", len(final_prompt), final_prompt[:80])

        if dry_run:
            return BannerResult(
                image_url="",
                prompt=final_prompt,
                preset=preset,
                size=size,
                model=model,
                llm_model=used_llm_model,
                cost_usd=total_cost,
                duration_ms=int((time.monotonic() - t0) * 1000),
                saved_to=None,
            )

        # ── Step 2: Generate image ────────────────────────────────────────────
        image_result = self._get_sdk().image_gen.generate(
            final_prompt,
            model=model,
            size=size,
            quality=quality,
            style=style,
        )
        image_url = str(image_result.image_cdn_url or image_result.image_url or "")
        total_cost += float(image_result.cost_usd or 0)
        logger.info("Banner generated: url=%s cost=$%.4f", image_url, total_cost)

        # ── Step 3: Save to disk (optional) ──────────────────────────────────
        saved_to: Path | None = None
        if output and image_url:
            saved_to = _save_image(image_url, output)

        return BannerResult(
            image_url=image_url,
            prompt=final_prompt,
            preset=preset,
            size=size,
            model=model,
            llm_model=used_llm_model,
            cost_usd=total_cost,
            duration_ms=int((time.monotonic() - t0) * 1000),
            saved_to=saved_to,
        )

    def _get_sdk(self):
        """Create an SDKRouter instance from stored config."""
        from .._client import SDKRouter
        return SDKRouter(
            api_key=self._config.api_key,
            llm_url=self._config.llm_url,
            api_url=self._config.api_url,
            audio_url=self._config.audio_url,
            use_self_hosted=self._config.use_self_hosted,
            timeout=self._config.timeout,
            max_retries=self._config.max_retries,
        )

    def _enhance_prompt(self, user_context: str, llm_model: str) -> tuple[str, float]:
        """Use LLM to generate an image prompt from project context.

        Returns: (prompt_text, cost_usd)
        """
        sdk = self._get_sdk()

        for model in (llm_model, "@balanced"):
            try:
                result = sdk.parse(
                    model=model,
                    messages=[
                        {"role": "system", "content": _SYSTEM_PROMPT},
                        {"role": "user", "content": user_context},
                    ],
                    response_format=_BannerPrompt,
                    max_tokens=600,
                )
                parsed = result.choices[0].message.parsed
                if parsed:
                    prompt_text = parsed.prompt.strip()
                    cost = float(result.usage.total_tokens * 0.000001) if result.usage else 0.0
                    logger.info("LLM prompt enhanced via %s (%d chars)", model, len(prompt_text))
                    return prompt_text, cost
            except Exception as exc:
                if "402" in str(exc) and model == llm_model:
                    logger.warning("LLM %s out of credits, retrying @balanced", model)
                    continue
                logger.error("LLM prompt enhancement failed (%s): %s", model, exc)
                break

        # Fallback: minimal generic prompt
        fallback = (
            "Pixar-quality 3D render of a sleek futuristic developer tool interface. "
            "Electric blue and dark navy color palette. Cinematic lighting with neon accent glow. "
            "Clean, modern, professional tech aesthetic. Wide 16:9 composition."
        )
        logger.warning("Using fallback prompt")
        return fallback, 0.0


# ── Async resource ────────────────────────────────────────────────────────────

class AsyncBannerResource(AsyncBaseResource):
    """Async banner / hero image generator."""

    def __init__(self, config: "SDKConfig") -> None:
        super().__init__(config)

    async def generate(
        self,
        *,
        source: str | Path | None = None,
        title: str | None = None,
        description: str | None = None,
        github_url: str | None = None,
        prompt: str | None = None,
        output: str | Path | None = None,
        preset: str = DEFAULT_PRESET,
        model: str = DEFAULT_IMAGE_MODEL,
        quality: str = "hd",
        style: str = "vivid",
        llm_model: str = DEFAULT_LLM_MODEL,
        enhance_prompt: bool = True,
        dry_run: bool = False,
    ) -> BannerResult:
        """Async version of BannerResource.generate(). Same parameters and behavior."""
        import asyncio

        loop = asyncio.get_event_loop()
        sync_resource = BannerResource(self._config)
        return await loop.run_in_executor(
            None,
            lambda: sync_resource.generate(
                source=source,
                title=title,
                description=description,
                github_url=github_url,
                prompt=prompt,
                output=output,
                preset=preset,
                model=model,
                quality=quality,
                style=style,
                llm_model=llm_model,
                enhance_prompt=enhance_prompt,
                dry_run=dry_run,
            ),
        )


__all__ = [
    "BannerResource",
    "AsyncBannerResource",
    "BannerResult",
    "PRESETS",
]
