#!/usr/bin/env python3
"""
story-gen: universal video scenario generator.
Supports any platform, audience, content format, and language.
Input can be in any language. Skill code and prompts are English-only.
"""

import sys, os, json, argparse, subprocess
from pathlib import Path
from urllib import request, error

# Fix Windows console encoding (cp1251 can't handle ₽, emoji, etc.)
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ('utf-8', 'utf8'):
    sys.stdout.reconfigure(encoding='utf-8')

API_URL = os.environ.get("OPENAI_BASE_URL", "https://llm.lambda.coredump.ru/v1")
API_KEY = os.environ.get("OPENAI_API_KEY", "")
MODEL   = os.environ.get("STORY_MODEL", "qwen3.5-122b")
DEFAULT_OUTPUT_PATH = Path(__file__).resolve().parents[2] / "assets" / "scenario.json"

SYSTEM_PROMPT = """You are a professional storyboard creator for image-based video production.

This is IMAGE MODE. Every scene must be a STATIC IMAGE generated by an AI image model (gpt-image-1.5).
Do NOT describe motion, camera movement, or video clips. Describe a single frozen frame per scene.

You receive a product description, idea, or topic in ANY language.
You return ONLY valid JSON with no markdown or comments.

Rules for visual_prompt:
- ALWAYS in English regardless of input or output language
- Describe ONE static image: subject, composition, lighting, colors, mood, art style
- No motion words: no "pan", "zoom", "slow", "moving", "transition"
- Optimized for gpt-image-1.5 image generation
- Example: "Woman holding beige leather handbag, marble studio background, soft natural window light, luxury minimal style, 4k sharp"

Rules for visual_type:
- ALWAYS set to "image" — this is image mode, never "video_clip"
- Use "text_only" only for pure text/title cards with no subject

Rules for voiceover and caption:
- Match the language specified in the lang parameter
- If lang is auto: detect language automatically from the input

Rules for storyboard_grid_prompt:
- ALWAYS in English
- Describes ALL scenes as a single image grid for consistent character/subject generation
- Grid size: 2x2 for 2-4 scenes, 3x3 for 5-9 scenes, 4x4 for 10-16 scenes
- Format: "NxN storyboard grid, same [subject] throughout: [top-left] scene1 description [top-right] scene2 description [bottom-left] scene3 description [bottom-right] scene4 description. Consistent [style notes], photorealistic, 8k"
- Purpose: feed directly into gpt-image-1 to generate all frames at once with consistent appearance
- If no recurring character or subject, set to null

Response format:
{
  "title": "video title",
  "format": "wb_ad|reels|viral|long|postcard|educational",
  "platform": "tiktok|instagram|wb|youtube|vk",
  "language": "ru|en|...",
  "duration_sec": 30,
  "hook": "first 3 seconds — grabbing phrase or action",
  "target_audience": "audience description",
  "content_restrictions": "platform restrictions (aspect ratio, age policy, etc.)",
  "scenes": [
    {
      "id": 1,
      "duration_sec": 5,
      "visual_prompt": "static image prompt in English for gpt-image-1.5",
      "visual_type": "image",
      "voiceover": "narration text in target language",
      "caption": "on-screen text in target language"
    }
  ],
  "storyboard_grid_prompt": "NxN storyboard grid prompt or null",
  "music_mood": "upbeat|calm|dramatic|funny|inspirational",
  "style_notes": "overall style and delivery notes",
  "asset_analysis": null
}"""

VIDEO_SYSTEM_PROMPT = """You are a professional scriptwriter for real video production (not image generation).

You receive a topic, idea, or product description in ANY language.
You return ONLY valid JSON with no markdown or comments.

Your job: write a full shooting script with voiceover and on-screen action descriptions, timed to the second.

Rules:
- timecode: cumulative start time in format HH:MM:SS
- voiceover: what the narrator says out loud — exact words, in the input language (or --lang if given)
- action: what happens on screen — camera movement, subject action, scene change — detailed, in English
- voiceover and action run in parallel (same time block)
- duration_sec: how long this scene lasts
- No scene shorter than 3 seconds

Response format:
{
  "title": "video title",
  "platform": "tiktok|instagram|wb|youtube|vk",
  "language": "ru|en|...",
  "duration_sec": 60,
  "hook": "first 3 seconds — what grabs attention",
  "target_audience": "who watches this",
  "scenes": [
    {
      "id": 1,
      "timecode": "00:00:00",
      "duration_sec": 5,
      "voiceover": "exact words spoken by narrator in target language",
      "action": "detailed English description of what is on screen: camera, subject, movement, lighting"
    }
  ],
  "music_mood": "upbeat|calm|dramatic|funny|inspirational",
  "style_notes": "overall visual style, pacing, tone"
}"""


WB_PRODUCT_VIDEO_PROMPT = """You are a senior commercial director specializing in e-commerce video ads for Wildberries.

You receive product information in ANY form: URL, text description, asset list, or any mix.
You return ONLY valid JSON with no markdown or comments.

STYLE: Professional, serious, high-energy commercial. Every second sells.
FORMAT: Wildberries product card video (9:16 vertical, 15-30 sec) or Reels/TikTok.

Asset types the user may provide:
- photo: product photo paths/URLs
- video: existing clip paths/URLs
- text: product name, description, specs, USPs
- url: Wildberries product URL

VISUAL STYLE for all scenes (mandatory):
- Background: deep dark (near-black, dark charcoal, very dark navy or deep gradient) OR studio white — always solid, never busy
- Product: ultra-bright, hyper-saturated, sharp, glowing with light — MUST pop against background
- Text overlays: bold modern sans-serif, large, high contrast (white or bright accent color on dark, or dark on white)
- Lighting: dramatic product lighting — rim light, spotlight, glossy highlights
- Feel: premium e-commerce, clean, aspirational — like Apple or luxury brand ad

Rules for scenes:
- visual_prompt: ALWAYS in English. Include: background color, product description and placement, lighting style, text overlay content and style, overall mood
- visual_type: "image" (generated or from photo), "video_clip" (motion), "text_only" (title/CTA card)
- asset_ref: "photo_1", "video_1", null (to generate)
- voiceover: confident, punchy Russian — short sentences, benefit-driven
- caption: exact Russian text to show on screen, large and bold

Scene structure:
1. HERO (3 sec): full product shot, ultra-bright, dramatic dark background, product name in bold
2. PROBLEM (3-4 sec): relatable pain point, minimal visual, strong text
3. FEATURE 1 (3-4 sec): close-up of key feature, spec text overlay
4. FEATURE 2 (3-4 sec): second key feature or benefit
5. LIFESTYLE (3-4 sec): product in aspirational real-world use
6. CTA (2-3 sec): dark bg, product + price text + "Wildberries" branding

Response format:
{
  "title": "video title",
  "format": "wb_ad",
  "platform": "wb",
  "language": "ru",
  "duration_sec": 22,
  "hook": "opening hook line",
  "target_audience": "...",
  "scenes": [
    {
      "id": 1,
      "duration_sec": 3,
      "scene_purpose": "hero|problem|feature|lifestyle|cta",
      "visual_prompt": "English image generation prompt with full visual details",
      "visual_type": "image|video_clip|text_only",
      "asset_ref": "photo_1|null",
      "voiceover": "Russian spoken text",
      "caption": "Russian on-screen text — bold, short"
    }
  ],
  "storyboard_grid_prompt": "NxN storyboard grid prompt or null",
  "music_mood": "upbeat|dramatic|inspirational",
  "style_notes": "dark bg, hyper-bright product, bold typography",
  "asset_plan": {
    "photos_needed": ["list of product shots needed if no assets provided"],
    "videos_needed": ["list of video clips needed"],
    "wb_url": "extracted WB URL or null"
  }
}"""


WB_CARD_PROMPT = """You are a professional e-commerce graphic designer specializing in Wildberries product card infographics.

You receive product information and generate a plan for a series of product card images (infographics).
You return ONLY valid JSON with no markdown or comments.

WILDBERRIES CARD VISUAL STYLE (strict):
- Format: vertical 3:4 (900x1200px equivalent)
- Background: very dark (near-black #0a0a0a, dark charcoal #1a1a1a, deep navy #0d1b2a, or rich dark gradient) — creates maximum contrast
- Product: ultra-bright, hyper-saturated, sharp studio shot — GLOWING against dark background, as if lit from within
- Typography: bold modern sans-serif (like Montserrat Bold, Bebas Neue). Large, commanding. White or bright accent (electric blue, neon green, vivid orange, hot pink) on dark background
- Text hierarchy: HUGE headline (product name / main benefit), medium subtitle, small spec details
- Layout: product takes 50-70% of frame, text occupies remaining space in clear zones
- Infographic elements: icons, dividers, spec badges, numbered lists — all clean and minimal
- Feel: premium, powerful, high-converting — like top Wildberries sellers' cards

Card count: generate exactly as many cards as requested. Default is 1 (HERO card only).
If count=1: generate only the HERO card.
If count>1: generate that many cards following this sequence:
1. HERO card: full product shot center, product name huge at top, main tagline at bottom. Most dramatic lighting.
2. FEATURES card: product + 3-4 key specs as icon+text blocks around it
3. BENEFIT 1 card: close-up detail, ONE big benefit headline, short explanation
4. BENEFIT 2 card: different angle or use case, another key benefit
5. COMPARISON/PROOF card: "почему мы" — size chart, material callout, or social proof numbers
6. CTA card: product + price area (leave price blank if unknown) + "Купить на Wildberries"

For each card generate:
- image_prompt: ENGLISH, full detailed prompt for gpt-image-1 image generation
  Must include: exact background color (e.g. "very dark charcoal background #1a1a2e"), product description and position,
  lighting style (studio rim light, spotlight from above, etc.), text overlay description (content, size, placement, color),
  visual style tags
- headline_ru: main Russian text for this card (large, bold)
- subtext_ru: secondary Russian text (smaller)
- design_notes: specific layout or design instructions in Russian

Response format:
{
  "product_name": "extracted product name",
  "product_category": "category",
  "color_scheme": "describe the dark bg color and accent color chosen",
  "typography_style": "font style description",
  "cards": [
    {
      "id": 1,
      "card_type": "hero|features|benefit|comparison|cta",
      "image_prompt": "full English prompt for AI image generation",
      "headline_ru": "ГЛАВНЫЙ ЗАГОЛОВОК",
      "subtext_ru": "пояснительный текст",
      "design_notes": "инструкции по верстке"
    }
  ],
  "brand_notes": "overall visual brand guidance for consistency"
}"""


def generate_wb_product_video(input_text: str, platform: str = "wb", lang: str = "ru",
                               duration: int = None, assets: dict = None) -> dict:
    """Generate a professional WB product commercial video scenario."""
    if not API_KEY:
        print("Error: OPENAI_API_KEY not set", file=sys.stderr)
        sys.exit(1)

    parts = [f"Product input: {input_text}"]
    if platform != "auto": parts.append(f"Platform: {platform}")
    if lang != "auto":     parts.append(f"Output language: {lang}")
    if duration:           parts.append(f"Target duration: {duration} seconds")
    if assets:             parts.append(f"Available assets: {json.dumps(assets, ensure_ascii=False)}")

    payload = json.dumps({
        "model": MODEL,
        "messages": [
            {"role": "system", "content": WB_PRODUCT_VIDEO_PROMPT},
            {"role": "user",   "content": "\n".join(parts)}
        ],
        "temperature": 0.6
    }).encode()

    req = request.Request(
        f"{API_URL}/chat/completions",
        data=payload,
        headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
    )
    try:
        with request.urlopen(req, timeout=90) as resp:
            data = json.loads(resp.read())
    except error.URLError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

    content = data["choices"][0]["message"]["content"].strip()
    if content.startswith("```"):
        content = content.split("```")[1]
        if content.startswith("json"):
            content = content[4:]
    return json.loads(content.strip())


def generate_wb_product_cards(input_text: str, assets: dict = None, count: int = 1) -> dict:
    """Generate a WB product card infographic series plan."""
    if not API_KEY:
        print("Error: OPENAI_API_KEY not set", file=sys.stderr)
        sys.exit(1)

    parts = [f"Product input: {input_text}"]
    parts.append(f"Number of cards to generate: {count}")
    if assets:
        parts.append(f"Available assets: {json.dumps(assets, ensure_ascii=False)}")

    payload = json.dumps({
        "model": MODEL,
        "messages": [
            {"role": "system", "content": WB_CARD_PROMPT},
            {"role": "user",   "content": "\n".join(parts)}
        ],
        "temperature": 0.5
    }).encode()

    req = request.Request(
        f"{API_URL}/chat/completions",
        data=payload,
        headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
    )
    try:
        with request.urlopen(req, timeout=90) as resp:
            data = json.loads(resp.read())
    except error.URLError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

    content = data["choices"][0]["message"]["content"].strip()
    if content.startswith("```"):
        content = content.split("```")[1]
        if content.startswith("json"):
            content = content[4:]
    return json.loads(content.strip())


# Keep old name as alias for backwards compatibility
def generate_wb_product(input_text: str, platform: str = "wb", lang: str = "ru",
                        duration: int = None, assets: dict = None) -> dict:
    return generate_wb_product_video(input_text, platform, lang, duration, assets)


def generate_wb_product(input_text: str, platform: str = "wb", lang: str = "ru",
                        duration: int = None, assets: dict = None) -> dict:
    """Generate a professional WB product commercial scenario."""
    if not API_KEY:
        print("Error: OPENAI_API_KEY not set", file=sys.stderr)
        sys.exit(1)

    parts = [f"Product input: {input_text}"]
    if platform != "auto": parts.append(f"Platform: {platform}")
    if lang != "auto":     parts.append(f"Output language: {lang}")
    if duration:           parts.append(f"Target duration: {duration} seconds")
    if assets:             parts.append(f"Available assets: {json.dumps(assets, ensure_ascii=False)}")

    payload = json.dumps({
        "model": MODEL,
        "messages": [
            {"role": "system", "content": WB_PRODUCT_PROMPT},
            {"role": "user",   "content": "\n".join(parts)}
        ],
        "temperature": 0.6
    }).encode()

    req = request.Request(
        f"{API_URL}/chat/completions",
        data=payload,
        headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
    )

    try:
        with request.urlopen(req, timeout=90) as resp:
            data = json.loads(resp.read())
    except error.URLError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

    content = data["choices"][0]["message"]["content"].strip()
    if content.startswith("```"):
        content = content.split("```")[1]
        if content.startswith("json"):
            content = content[4:]

    return json.loads(content.strip())


ASSET_PROMPT = """You analyze input data for video production.
Your task is to extract maximum information from what is given.

Return ONLY valid JSON:
{
  "asset_type": "product_description|url|image_description|idea|mixed",
  "extracted_info": {
    "subject": "what or who the video is about",
    "key_features": ["feature 1", "feature 2"],
    "tone": "serious|funny|inspirational|sales",
    "existing_visuals": "description of visuals if present",
    "gaps": ["what is missing for a complete scenario"]
  },
  "recommended_format": "wb_ad|reels|viral|long|postcard|educational",
  "recommended_platform": "tiktok|instagram|wb|youtube|vk",
  "confidence": "high|medium|low"
}"""

def analyze_assets(input_text: str) -> dict:
    """Analyze input assets before generating the scenario."""
    payload = json.dumps({
        "model": MODEL,
        "messages": [
            {"role": "system", "content": ASSET_PROMPT},
            {"role": "user", "content": input_text}
        ],
        "temperature": 0.3
    }).encode()

    req = request.Request(
        f"{API_URL}/chat/completions",
        data=payload,
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json"
        }
    )
    with request.urlopen(req, timeout=60) as resp:
        data = json.loads(resp.read())

    content = data["choices"][0]["message"]["content"].strip()
    if content.startswith("```"):
        content = content.split("```")[1]
        if content.startswith("json"):
            content = content[4:]
    return json.loads(content.strip())


def generate(input_text, format_hint="auto", platform="auto",
             audience="", duration=None, lang="auto", assets=None):
    if not API_KEY:
        print("Error: OPENAI_API_KEY not set", file=sys.stderr)
        sys.exit(1)

    parts = [f"Input: {input_text}"]
    if format_hint != "auto": parts.append(f"Format: {format_hint}")
    if platform != "auto":    parts.append(f"Platform: {platform}")
    if audience:              parts.append(f"Target audience: {audience}")
    if duration:              parts.append(f"Duration: {duration} seconds")
    if lang != "auto":        parts.append(f"Output language: {lang}")
    if assets:                parts.append(f"Asset analysis: {json.dumps(assets, ensure_ascii=False)}")

    payload = json.dumps({
        "model": MODEL,
        "messages": [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user",   "content": "\n".join(parts)}
        ],
        "temperature": 0.8
    }).encode()

    req = request.Request(
        f"{API_URL}/chat/completions",
        data=payload,
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type":  "application/json"
        }
    )

    try:
        with request.urlopen(req, timeout=60) as resp:
            data = json.loads(resp.read())
    except error.URLError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

    content = data["choices"][0]["message"]["content"].strip()
    if content.startswith("```"):
        content = content.split("```")[1]
        if content.startswith("json"):
            content = content[4:]

    return json.loads(content.strip())


def generate_video(input_text, platform="auto", audience="", duration=None, lang="auto"):
    """Generate a full video shooting script with timecoded voiceover and action descriptions."""
    if not API_KEY:
        print("Error: OPENAI_API_KEY not set", file=sys.stderr)
        sys.exit(1)

    parts = [f"Input: {input_text}"]
    if platform != "auto": parts.append(f"Platform: {platform}")
    if audience:           parts.append(f"Target audience: {audience}")
    if duration:           parts.append(f"Duration: {duration} seconds")
    if lang != "auto":     parts.append(f"Output language: {lang}")

    payload = json.dumps({
        "model": MODEL,
        "messages": [
            {"role": "system", "content": VIDEO_SYSTEM_PROMPT},
            {"role": "user",   "content": "\n".join(parts)}
        ],
        "temperature": 0.8
    }).encode()

    req = request.Request(
        f"{API_URL}/chat/completions",
        data=payload,
        headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
    )

    try:
        with request.urlopen(req, timeout=60) as resp:
            data = json.loads(resp.read())
    except error.URLError as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

    content = data["choices"][0]["message"]["content"].strip()
    if content.startswith("```"):
        content = content.split("```")[1]
        if content.startswith("json"):
            content = content[4:]

    return json.loads(content.strip())


def scenario_to_video_txt(scenario: dict) -> str:
    """Convert video scenario JSON to video.txt format: [HH:MM:SS] voiceover text."""
    lines = []
    for scene in scenario.get("scenes", []):
        tc = scene.get("timecode", "00:00:00")
        voiceover = scene.get("voiceover", "").strip()
        if voiceover:
            lines.append(f"[{tc}] {voiceover}")
    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser(description="Universal video scenario generator")
    parser.add_argument("input", help="Product description, idea, topic, or joke in any language")
    parser.add_argument("--mode",     default="image",
                        choices=["image", "video"],
                        help="image: storyboard for image/visual generation; video: full shooting script with voiceover")
    parser.add_argument("--format",   default="auto",
                        choices=["auto","wb_ad","reels","viral","long","postcard","educational"])
    parser.add_argument("--platform", default="auto",
                        choices=["auto","tiktok","instagram","wb","youtube","vk"])
    parser.add_argument("--audience", default="", help="Target audience description")
    parser.add_argument("--duration", type=int, default=None, help="Target duration in seconds")
    parser.add_argument("--lang",     default="auto", help="Output language: ru, en, de, auto")
    parser.add_argument("--analyze",  action="store_true", help="Analyze assets before generating (image mode only)")
    parser.add_argument("--out",      default=None, help="Save JSON output to file")
    parser.add_argument("--voice",    action="store_true",
                        help="After generating video script, immediately run voice synthesis (video mode only). Requires --out.")
    parser.add_argument("--voice-out", default=None,
                        help="Output directory for voice segments (default: voice/segments next to --out)")
    args = parser.parse_args()

    if args.mode == "video":
        print("Generating video shooting script...", file=sys.stderr)
        result = generate_video(args.input, args.platform, args.audience, args.duration, args.lang)
        print(f"  Title: {result.get('title')}", file=sys.stderr)
        print(f"  Scenes: {len(result.get('scenes', []))}", file=sys.stderr)
        print(f"  Duration: {result.get('duration_sec')}s", file=sys.stderr)

        output = json.dumps(result, ensure_ascii=False, indent=2)

        # Always need --out for video mode to save voiceover txt
        if not args.out:
            print("Warning: use --out to save scenario and voiceover files", file=sys.stderr)
            print(output)
            return

        os.makedirs(os.path.dirname(os.path.abspath(args.out)), exist_ok=True)
        with open(args.out, "w", encoding="utf-8") as f:
            f.write(output)
        print(f"Saved scenario to {args.out}", file=sys.stderr)

        # Save voiceover txt for voice_acting.py
        base = args.out[:-5] if args.out.endswith(".json") else args.out
        video_txt_path = base + "_voiceover.txt"
        video_txt = scenario_to_video_txt(result)
        with open(video_txt_path, "w", encoding="utf-8") as f:
            f.write(video_txt)
        print(f"Saved voiceover script to {video_txt_path}", file=sys.stderr)

        print(output)

        # Run voice synthesis if requested
        if args.voice:
            voice_script = os.path.join(
                os.path.dirname(__file__), "..", "..", "voice", "voice_acting.py"
            )
            voice_script = os.path.normpath(voice_script)
            if not os.path.exists(voice_script):
                print(f"Error: voice_acting.py not found at {voice_script}", file=sys.stderr)
                sys.exit(1)

            segments_dir = args.voice_out or os.path.join(
                os.path.dirname(os.path.abspath(args.out)), "voice_segments"
            )
            print(f"\nStarting voice synthesis → {segments_dir}", file=sys.stderr)
            cmd = [sys.executable, voice_script, video_txt_path, "-o", segments_dir]
            result_proc = subprocess.run(cmd)
            if result_proc.returncode != 0:
                print("Voice synthesis failed", file=sys.stderr)
                sys.exit(result_proc.returncode)
            print(f"Voice segments saved to {segments_dir}", file=sys.stderr)
            print(f"Manifest: {segments_dir}/segments.txt", file=sys.stderr)

        return

    assets = None
    if args.analyze:
        print("Analyzing assets...", file=sys.stderr)
        assets = analyze_assets(args.input)
        print(f"Asset type: {assets.get('asset_type')} / confidence: {assets.get('confidence')}", file=sys.stderr)
        if args.format == "auto" and assets.get("recommended_format"):
            args.format = assets["recommended_format"]
        if args.platform == "auto" and assets.get("recommended_platform"):
            args.platform = assets["recommended_platform"]

    result = generate(args.input, args.format, args.platform,
                      args.audience, args.duration, args.lang, assets)

    if assets:
        result["asset_analysis"] = assets

    output = json.dumps(result, ensure_ascii=False, indent=2)

    output_path = Path(args.out) if args.out else DEFAULT_OUTPUT_PATH
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(output, encoding="utf-8")
    print(f"Saved to {output_path}", file=sys.stderr)

    print(output)


if __name__ == "__main__":
    main()
