#!/usr/bin/env python3
"""
Full Video Generation Pipeline with RunPod Proxy.

Automates the complete workflow:
1. Fetch product data from Wildberries
2. Download product images
3. Process with NanoBanana (cleanup + resize to 16:9)
4. Generate video with Veo3
5. Download and save all results

All Google API calls are routed through a US-based RunPod instance
via SSH tunnel to bypass geo-restrictions.

Usage:
    python full_pipeline.py \\
        --url "https://www.wildberries.ru/catalog/202372804/detail.aspx" \\
        --ssh-host PROXY_HOST \\
        --ssh-port 14492 \\
        --api-key "AIzaSy..."
"""

import argparse
import base64
import json
import os
import re
import subprocess
import sys
import time
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional

import requests
from dotenv import load_dotenv

# Load .env from project root (parent of scripts/)
load_dotenv(Path(__file__).parent.parent / ".env")

# Import local modules
from gemini_proxy import (
    start_tunnel,
    stop_tunnel,
    call_nanobanana,
    call_veo3,
    poll_veo3,
    download_video,
    test_connection
)
from gptunnel_proxy import (
    call_veo3_gptunnel,
    poll_veo3_gptunnel,
    download_video_gptunnel
)
from pollo_proxy import (
    call_veo3_pollo,
    poll_veo3_pollo,
    download_video_pollo
)
from laozhang_proxy import (
    call_nanobanana_laozhang,
    call_veo3_laozhang,
    download_video_laozhang
)
from wb_to_video_prompts import (
    extract_product_id,
    construct_image_urls
)

# Try to import Playwright fetcher
try:
    from wb_fetcher import fetch_wb_product_playwright
    HAS_PLAYWRIGHT = True
except ImportError:
    HAS_PLAYWRIGHT = False


def generate_creative_prompts(image_paths: List[str], product_name: str, user_feedback: Optional[str] = None, activity_log_path: Optional[str] = None) -> Optional[Dict]:
    """
    Run Claude CLI to analyze images and generate creative prompts.

    Uses the same prompt system as auto_select_image.py - creates two-act
    storytelling prompts that show the product being USED with positive outcomes.

    Args:
        image_paths: List of paths to product images
        product_name: Name of the product
        user_feedback: Optional feedback from previous generations to improve prompts
        activity_log_path: Optional path to activity.log for Claude to read directly
    """

    # Build image list for prompt
    image_list = "\n".join([f"- {path}" for path in image_paths])

    # Build feedback section if available
    feedback_section = ""
    if user_feedback:
        activity_instruction = ""
        if activity_log_path:
            activity_instruction = f"""
FIRST, read the activity log to understand the full history:
Activity log path: {activity_log_path}

The activity log contains timestamps, ratings, and user comments.
"""

        feedback_section = f"""
## USER FEEDBACK FROM PREVIOUS GENERATIONS

The user has generated videos for this product before. Here is their feedback:
{activity_instruction}
{user_feedback}

IMPORTANT: Generate a FRESH, DIFFERENT creative concept!
- Don't repeat similar ideas from before
- If feedback mentions specific issues (e.g., "three slippers"), avoid that specific problem
- Otherwise, create something completely new and surprising
- Be creative and diverse in your approach

"""

    prompt = f'''You are a creative director making product videos that CREATE DESIRE through storytelling.
{feedback_section}

Product: {product_name}

Image files to analyze:
{image_list}

READ each image, then create a TWO-ACT story:

## THE RULE: Every video must show the product being USED with a POSITIVE OUTCOME.

**ACT 1** (first 3 seconds): The product - beautiful, desirable, waiting
**ACT 2** (last 5 seconds): The product IN USE - a person enjoying it, a positive moment, the BENEFIT

Examples of good Act 2 ideas:
- Suitcase → Person rolling it through airport, arriving at destination, hand grabbing handle
- Jewelry → Woman smiling in mirror, receiving compliment, confident posture
- Cookware → Family gathered around table with FINISHED meal, person holding pan proudly, gift-giving/unboxing moment (AVOID: cooking action, cracking eggs, sliding food, pouring)
- Clothing → Person walking confidently, turning heads, feeling great
- Tech → Person relaxing with music, productive moment, satisfied expression

## VEO3 LIMITATIONS - AVOID THESE ACTIONS:
- Sliding/pouring/cracking (eggs sliding off pan, pouring liquid)
- Complex hand manipulation of small objects
- Rapid scene transitions (prefer single continuous scene or simple fade)
- Objects changing shape or state
- NEVER include babies or toddlers in scenes (content policy restriction)

## NANOBANANA PHYSICS:
- Products must REST FIRMLY ON surfaces with proper shadows
- Say "resting firmly on the wooden counter" not just "on counter"
- Ensure visible contact point with surface

## CRITICAL: PRESERVE PRODUCT AUTHENTICITY (DO NOT INVENT LABELS)
The generated image must show the ACTUAL product as the buyer will receive it.

**RULE 1 - NEVER ADD FAKE LABELS TO THE PRODUCT:**
- DO NOT add text, logos, or labels that don't exist on the original product
- The physical product must look EXACTLY like the source image
- Example: A Gigabyte motherboard has AMD socket - do NOT write "Gigabyte" on the CPU socket area
- Example: Don't add brand names to parts of the product where they don't exist

**RULE 2 - PRESERVE EXISTING LABELS:**
- Keep all original labels, stickers, logos that ARE on the product
- These are part of what the buyer receives

**RULE 3 - BRAND OVERLAY IS OK (on the IMAGE, not the product):**
- You CAN add brand name as a subtle text overlay in corner of the IMAGE
- This is like a watermark - separate from the product itself
- Example: "Gigabyte" text in bottom-right corner of the frame (not ON the motherboard)

**RULE 4 - ONLY REMOVE:**
- Marketing overlays, promotional badges, price tags, "SALE" stickers
- Website watermarks, URLs - things NOT part of the physical product

The buyer must recognize the product they will receive.

## 1. SELECT IMAGES:

**detail_image**: Where product is LARGEST (NanoBanana needs pixels!)
**context_image**: Any with lifestyle/human element, or same if none

## 2. CREATIVE CONCEPT:
What positive emotion or outcome does this product enable? Joy? Confidence? Adventure? Comfort? Success?
If it's a known brand, USE THE BRAND NAME to increase desire!

## 3. SCENE CONSISTENCY DECISION:

Decide if ACT 2 should maintain visual elements from ACT 1:

**USE CONSISTENCY (scene_consistency.needed = true)** when:
- Same room/location in both acts (e.g., sofa shown, then person sitting on same sofa)
- Same person appears in both acts
- Continuous story in one space (product on table → person picks it up)

**NO CONSISTENCY NEEDED (scene_consistency.needed = false)** when:
- Different locations (garage → car, store → home, kitchen → dining room)
- Different time of day or context
- Act 1 is just product, Act 2 is completely different scene

If consistency needed, specify what to maintain: "environment", "person", "furniture", "lighting"

## 4. NANOBANANA PROMPTS:

**frame_act1**: The product hero shot. Beautiful, inviting, desirable.
- Keep the product EXACTLY as shown in source - no invented labels!
- Describe: setting, lighting, mood, atmosphere. Make it aspirational.
- Optional: add brand name as subtle text overlay in corner of frame

**frame_act2**: The LIFESTYLE payoff. Product being USED by a PERSON with POSITIVE emotion.
- The product should look exactly like the real item (no fake labels)
- Describe: who is using it, where, what are they feeling, what's the positive outcome.
- This is NOT just the product again - it's someone ENJOYING the product!
- If scene_consistency.needed=true, describe SAME environment/person from ACT 1

End each with: "16:9 aspect ratio, photorealistic, keep product labels exactly as original"

## 5. VEO3 PROMPT (structured as three parts):

**veo3_act1**: Describe ACT 1 scene (product hero shot, 3 seconds)
- Camera movement, lighting, mood
- Product is the star

**veo3_transition**: How to move from ACT 1 to ACT 2. Choose ONE:
- "fade to black then fade in" (safe, works always)
- "gentle cross-dissolve" (smooth blend)
- "hand reaches into frame and picks up product" (dynamic)
- "camera pans right to reveal" (continuous)
- "match cut on product shape" (creative)

**veo3_act2**: Describe ACT 2 scene (lifestyle payoff, 5 seconds)
- Person using/enjoying the product
- Positive emotion visible

The viewer should WANT this product after watching.

Output ONLY valid JSON:
{{
  "detail_image": "filename",
  "context_image": "filename",
  "brand_name": "Detected brand name or 'generic' if no brand visible",
  "story_title": "Short catchy title for this video (3-7 words, in Russian if product is Russian market)",
  "positive_outcome": "What benefit/emotion does this product provide?",
  "scene_consistency": {{
    "needed": true or false,
    "maintain": ["environment", "person", "lighting"] or [] if not needed
  }},
  "frame_act1": "NanoBanana prompt - product hero shot WITH brand identity preserved",
  "frame_act2": "NanoBanana prompt - lifestyle with person USING the branded product",
  "veo3_act1": "ACT 1 scene description for video",
  "veo3_transition": "transition method (fade/dissolve/pan/etc)",
  "veo3_act2": "ACT 2 scene description for video"
}}'''

    # Run Claude CLI
    cmd = ["claude", "-p", "--allowedTools", "Read"]

    try:
        result = subprocess.run(
            cmd,
            input=prompt,
            capture_output=True,
            text=True,
            timeout=180
        )

        if result.returncode != 0:
            print(f"  Claude error: {result.stderr[:200]}")
            return None

        # Parse JSON from output
        output = result.stdout.strip()

        try:
            start = output.find('{')
            end = output.rfind('}') + 1
            if start >= 0 and end > start:
                json_str = output[start:end]
                return json.loads(json_str)
        except json.JSONDecodeError:
            print(f"  Could not parse JSON from Claude output")
            return None

    except subprocess.TimeoutExpired:
        print("  Claude timeout")
        return None
    except FileNotFoundError:
        print("  Claude CLI not found")
        return None


def download_image(url: str, output_path: str, timeout: int = 30) -> bool:
    """Download image from URL to local file."""
    try:
        response = requests.get(url, timeout=timeout, headers={
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })
        if response.status_code == 200:
            with open(output_path, 'wb') as f:
                f.write(response.content)
            return True
    except Exception as e:
        print(f"  Failed to download {url}: {e}")
    return False


def generate_task_id() -> str:
    """Generate unique task ID, sortable by time."""
    import random
    import string
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    suffix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4))
    return f"{timestamp}_{suffix}"


def create_output_directory(base_path: str, product_id: str, task_id: str = None) -> Path:
    """Create output directory structure.

    Args:
        base_path: Base output path
        product_id: Wildberries product ID
        task_id: If provided, uses new format: wb_{product_id}/{task_id}/
                 Otherwise uses old format: {product_id}_{timestamp}/
    """
    if task_id:
        # New format for bot: wb_<id>/<task_id>/
        output_dir = Path(base_path) / f"wb_{product_id}" / task_id
    else:
        # Old format for CLI: <id>_<timestamp>/
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        output_dir = Path(base_path) / f"{product_id}_{timestamp}"

    # Create subdirectories
    (output_dir / "images").mkdir(parents=True, exist_ok=True)
    (output_dir / "processed").mkdir(parents=True, exist_ok=True)
    (output_dir / "videos").mkdir(parents=True, exist_ok=True)

    return output_dir


def get_mime_type(filepath: str) -> str:
    """Get MIME type based on file extension."""
    ext = Path(filepath).suffix.lower()
    mime_types = {
        ".webp": "image/webp",
        ".jpg": "image/jpeg",
        ".jpeg": "image/jpeg",
        ".png": "image/png",
        ".gif": "image/gif"
    }
    return mime_types.get(ext, "image/webp")


def generate_report_md(output_dir: Path, metadata: Dict) -> str:
    """Generate markdown report with links and prompts."""
    product_url = metadata.get("product_url", "")
    product_name = metadata.get("product_name", f"Product {metadata.get('product_id', 'Unknown')}")
    product_id = metadata.get("product_id", "")

    # Get prompts
    prompts = metadata.get("prompts", {})
    creative = prompts.get("creative", {})

    # Extract creative prompts or fallback
    if creative:
        positive_outcome = creative.get("positive_outcome", "")
        frame_act1 = creative.get("frame_act1", "")
        frame_act2 = creative.get("frame_act2", "")
        detail_image = creative.get("detail_image", "")
        context_image = creative.get("context_image", "")

        # Build veo3_prompt from structured parts or use legacy single field
        veo3_act1 = creative.get("veo3_act1", "")
        veo3_transition = creative.get("veo3_transition", "")
        veo3_act2 = creative.get("veo3_act2", "")
        if veo3_act1 and veo3_act2:
            veo3_prompt = f"ACT 1: {veo3_act1}\nTRANSITION: {veo3_transition}\nACT 2: {veo3_act2}"
        else:
            veo3_prompt = creative.get("veo3_prompt", "")
    else:
        positive_outcome = ""
        frame_act1 = prompts.get("frame_act1", "")
        frame_act2 = prompts.get("frame_act2", "")
        veo3_prompt = prompts.get("veo3", "")
        detail_image = ""
        context_image = ""

    md_content = f"""# {product_name}

## Wildberries

- **URL**: [{product_url}]({product_url})
- **Product ID**: {product_id}

## Generated Files

### Original Images (from WB)
- `images/original_*.webp`

### Processed Frames (NanoBanana, 16:9 cinematic)
- `processed/frame_act1.png` - Product hero shot
- `processed/frame_act2.png` - Lifestyle shot (person using product)

### Video (Veo3)
- `videos/video.mp4` - Two-act video

---

## Creative Concept

**Positive Outcome**: {positive_outcome}

**Source Image**: {detail_image}

---

## Prompts Used

### ACT 1 - Product Hero Shot (NanoBanana)

```
{frame_act1}
```

### ACT 2 - Lifestyle Shot (NanoBanana)

```
{frame_act2}
```

### Two-Act Video (Veo3)

```
{veo3_prompt}
```

---

## Pipeline Info

- **Started**: {metadata.get("started_at", "")}
- **Completed**: {metadata.get("completed_at", "")}
- **Images Generated**: {len(metadata.get("images_processed", []))}
- **Videos Generated**: {len(metadata.get("videos_generated", []))}

### Command Line

```bash
{metadata.get("command_line", "N/A")}
```
"""

    if metadata.get("errors"):
        md_content += "\n## Errors\n\n"
        for err in metadata["errors"]:
            md_content += f"- {err}\n"

    return md_content


def run_pipeline(
    product_url: str,
    ssh_host: str,
    ssh_port: int,
    api_key: str,
    output_base: str = "./output",
    ssh_user: str = "root",
    image_nums: List[int] = None,
    proxy_port: int = 1080,
    skip_video: bool = False,
    use_gptunnel: bool = False,
    gptunnel_key: Optional[str] = None,
    use_pollo: bool = False,
    pollo_key: Optional[str] = None,
    use_laozhang: bool = False,
    laozhang_key: Optional[str] = None,
    command_line: str = "",
    task_id: str = None,
    user_feedback: Optional[str] = None
) -> Dict:
    """
    Run the full video generation pipeline.

    Uses Claude CLI to generate creative two-act storytelling prompts,
    then processes images with NanoBanana and generates videos with Veo3.

    Args:
        product_url: Wildberries product URL
        ssh_host: RunPod SSH host
        ssh_port: RunPod SSH port
        api_key: Gemini API key
        output_base: Base output directory
        ssh_user: SSH username
        image_nums: List of image numbers to process (default: [1,2,3,4,5,6,7])
        proxy_port: Local SOCKS5 proxy port
        skip_video: Skip video generation (only process images)
        use_gptunnel: Use GPTunnel for Veo3 instead of direct Google API
        gptunnel_key: GPTunnel API key (required if use_gptunnel=True)
        command_line: Command line used to run the pipeline (for README)
        task_id: If provided, uses bot directory format: wb_{id}/{task_id}/
        user_feedback: Optional feedback from previous generations to guide creative prompts

    Returns:
        Dict with pipeline results and metadata
    """
    if image_nums is None:
        image_nums = [1, 2, 3, 4, 5, 6, 7]

    metadata = {
        "product_url": product_url,
        "started_at": datetime.now().isoformat(),
        "ssh_host": ssh_host,
        "command_line": command_line,
        "images_processed": [],
        "videos_generated": [],
        "errors": []
    }

    # Extract product ID
    product_id = extract_product_id(product_url)
    if not product_id:
        metadata["errors"].append("Could not extract product ID from URL")
        return metadata

    metadata["product_id"] = product_id
    if task_id:
        metadata["task_id"] = task_id
    print(f"\n{'='*60}")
    print(f"Product ID: {product_id}")
    print(f"{'='*60}")

    # Create output directory
    output_dir = create_output_directory(output_base, product_id, task_id)
    metadata["output_dir"] = str(output_dir)
    print(f"Output directory: {output_dir}")

    # Start SSH tunnel (only needed if not using LaoZhang for everything)
    tunnel_started = False
    if use_laozhang and laozhang_key:
        print(f"\nUsing LaoZhang.ai for all API calls (no SSH tunnel needed)")
        print("  NanoBanana: via LaoZhang.ai")
        print("  Veo3: via LaoZhang.ai")
    else:
        print(f"\nStarting SSH tunnel to {ssh_host}:{ssh_port}...")
        if not start_tunnel(ssh_host, ssh_port, ssh_user, proxy_port):
            metadata["errors"].append("Failed to start SSH tunnel")
            return metadata
        tunnel_started = True

        # Test connection
        print("Testing API connection...")
        test_results = test_connection(api_key, proxy_port)
        if test_results.get("nanobanana", {}).get("status") != "ok":
            metadata["errors"].append(f"NanoBanana not accessible: {test_results.get('nanobanana')}")
            return metadata
        print("  NanoBanana: OK")
        print("  Veo3: OK" if test_results.get("veo3", {}).get("status") == "ok" else f"  Veo3: {test_results.get('veo3')}")

    try:
        # Fetch product data
        print("\nFetching product data...")
        product_data = None
        if HAS_PLAYWRIGHT:
            try:
                product_data = fetch_wb_product_playwright(product_url, headless=True)
                if product_data:
                    metadata["product_name"] = product_data.get("name", "Unknown")
                    print(f"  Product: {metadata['product_name']}")
            except Exception as e:
                print(f"  Playwright fetch failed: {e}")

        # Get image URLs
        if product_data and product_data.get("images"):
            image_urls = product_data["images"]
        else:
            image_urls = construct_image_urls(product_id)
        print(f"  Found {len(image_urls)} image URLs")

        # Step 1: Download images for analysis
        print(f"\n{'─'*60}")
        print("Step 1: Downloading images for analysis...")
        downloaded_paths = []
        for img_num in image_nums:
            if img_num > len(image_urls):
                continue
            img_url = image_urls[img_num - 1]
            original_path = output_dir / "images" / f"original_{img_num}.webp"
            if download_image(img_url, str(original_path)):
                downloaded_paths.append(str(original_path))
                print(f"  Downloaded: {original_path.name}")
            else:
                print(f"  Failed: image {img_num}")

        if not downloaded_paths:
            metadata["errors"].append("No images downloaded")
            return metadata

        # Step 2: Generate creative prompts with Claude
        print(f"\n{'─'*60}")
        print("Step 2: Generating creative prompts with Claude...")
        if user_feedback:
            print(f"  Using feedback from previous generations")
        product_name = metadata.get("product_name", f"Product {product_id}")

        # Pass activity log path for Claude to read directly
        activity_log_path = None
        if user_feedback:
            activity_log_path = str(Path(output_base) / f"wb_{product_id}" / "activity.log")

        creative_prompts = generate_creative_prompts(
            downloaded_paths,
            product_name,
            user_feedback,
            activity_log_path
        )

        if creative_prompts:
            print(f"  Creative concept: {creative_prompts.get('positive_outcome', 'N/A')}")
            print(f"  Selected detail image: {creative_prompts.get('detail_image', 'N/A')}")
            metadata["prompts"] = {"creative": creative_prompts}

            frame_act1_prompt = creative_prompts.get("frame_act1", "")
            frame_act2_prompt = creative_prompts.get("frame_act2", "")
            detail_image = creative_prompts.get("detail_image", "")

            # Build Veo3 prompt from structured parts
            veo3_act1 = creative_prompts.get("veo3_act1", "")
            veo3_transition = creative_prompts.get("veo3_transition", "fade to black then fade in")
            veo3_act2 = creative_prompts.get("veo3_act2", "")

            if veo3_act1 and veo3_act2:
                veo3_prompt = f"ACT 1 (3 sec): {veo3_act1} TRANSITION: {veo3_transition}. ACT 2 (5 sec): {veo3_act2} --no promotional text, watermarks, morphing, distortion, artifacts"
            else:
                # Fallback to old single-field format if structured not provided
                veo3_prompt = creative_prompts.get("veo3_prompt", "")
        else:
            # Fallback to simple English prompts
            print("  Claude failed, using fallback prompts...")
            frame_act1_prompt = "A beautiful product hero shot resting firmly on elegant marble surface. Soft warm studio lighting from the left creates gentle shadows. The product is centered, taking up 60% of frame. Cinematic composition, aspirational mood. 16:9 aspect ratio, photorealistic, no text or watermarks"
            frame_act2_prompt = "A stylish person enjoying this product in a beautiful modern setting. Natural warm lighting, genuine smile of satisfaction. The product is visible and being appreciated. Lifestyle moment capturing the joy of ownership. 16:9 aspect ratio, photorealistic, no text or watermarks"
            veo3_prompt = "ACT 1 (3 sec): Close-up of the product in beautiful studio lighting, camera slowly pushes in revealing quality and craftsmanship. TRANSITION: fade to black then fade in. ACT 2 (5 sec): A person enjoying the product, genuine smile of satisfaction, warm natural lighting, the viewer feels desire. --no text, watermarks, morphing, distortion, artifacts"
            detail_image = "original_1"
            metadata["prompts"] = {
                "frame_act1": frame_act1_prompt,
                "frame_act2": frame_act2_prompt,
                "veo3": veo3_prompt,
                "fallback": True
            }

        # Find the detail_image source file
        detail_image_path = None
        for img_path in downloaded_paths:
            # Match by filename (e.g., "original_1" matches "original_1.webp")
            if detail_image in img_path or Path(img_path).stem in detail_image:
                detail_image_path = img_path
                break

        # Fallback to first image if not found
        if not detail_image_path:
            detail_image_path = downloaded_paths[0]
            print(f"  Warning: Could not find detail_image '{detail_image}', using first image")

        print(f"\n{'─'*60}")
        print("Step 3: Generating TWO cinematic frames with NanoBanana...")

        # === ACT 1: Product Hero Shot ===
        print(f"\n  [3a] ACT 1 - Product Hero Shot")
        print(f"      Source: {Path(detail_image_path).name}")
        act1_path = None
        act1_data = None
        act1_mime = None
        try:
            with open(detail_image_path, 'rb') as f:
                source_image_data = f.read()

            if use_laozhang and laozhang_key:
                # Use LaoZhang.ai for NanoBanana
                act1_data, act1_mime = call_nanobanana_laozhang(
                    image_data=source_image_data,
                    prompt=frame_act1_prompt,
                    api_key=laozhang_key,
                    mime_type=get_mime_type(detail_image_path),
                    aspect_ratio="16:9"
                )
            else:
                # Use direct Google API via RunPod tunnel
                act1_data, act1_mime = call_nanobanana(
                    image_data=source_image_data,
                    prompt=frame_act1_prompt,
                    api_key=api_key,
                    mime_type=get_mime_type(detail_image_path),
                    proxy_port=proxy_port,
                    aspect_ratio="16:9"
                )

            # Get proper extension from mime type
            ext = ".jpg" if "jpeg" in act1_mime else ".png"
            act1_path = output_dir / "processed" / f"frame_act1{ext}"
            with open(act1_path, 'wb') as f:
                f.write(act1_data)

            print(f"      Saved: {act1_path.name}")
            metadata["images_processed"].append({
                "type": "act1",
                "source": detail_image_path,
                "output": str(act1_path),
                "prompt": frame_act1_prompt
            })

        except Exception as e:
            metadata["errors"].append(f"NanoBanana ACT 1 failed: {e}")
            print(f"      Error: {e}")

        # === ACT 2: Lifestyle Shot ===
        print(f"\n  [3b] ACT 2 - Lifestyle Shot (person using product)")
        act2_path = None
        act2_data = None
        act2_mime = None

        # Check if scene consistency is needed
        scene_consistency = creative_prompts.get("scene_consistency", {}) if creative_prompts else {}
        use_reference = (
            scene_consistency.get("needed", False)
            and act1_data is not None
        )
        maintain_elements = scene_consistency.get("maintain") or []

        if use_reference:
            print(f"      Scene consistency: maintaining {maintain_elements}")
            # Enhance prompt to reference the first image
            consistency_note = f" Use the second image as reference - maintain the same {', '.join(maintain_elements)}."
            frame_act2_prompt_enhanced = frame_act2_prompt + consistency_note
        else:
            frame_act2_prompt_enhanced = frame_act2_prompt

        try:
            # For ACT 2, we use the same source image but with lifestyle prompt
            # NanoBanana will generate a lifestyle scene based on the product
            if use_laozhang and laozhang_key:
                # Use LaoZhang.ai for NanoBanana
                act2_data, act2_mime = call_nanobanana_laozhang(
                    image_data=source_image_data,
                    prompt=frame_act2_prompt_enhanced,
                    api_key=laozhang_key,
                    mime_type=get_mime_type(detail_image_path),
                    aspect_ratio="16:9",
                    reference_image_data=act1_data if use_reference else None,
                    reference_mime_type=act1_mime if use_reference else None
                )
            else:
                # Use direct Google API via RunPod tunnel
                act2_data, act2_mime = call_nanobanana(
                    image_data=source_image_data,
                    prompt=frame_act2_prompt_enhanced,
                    api_key=api_key,
                    mime_type=get_mime_type(detail_image_path),
                    proxy_port=proxy_port,
                    aspect_ratio="16:9"
                )

            # Get proper extension from mime type
            ext = ".jpg" if "jpeg" in act2_mime else ".png"
            act2_path = output_dir / "processed" / f"frame_act2{ext}"
            with open(act2_path, 'wb') as f:
                f.write(act2_data)

            print(f"      Saved: {act2_path.name}")
            metadata["images_processed"].append({
                "type": "act2",
                "source": detail_image_path,
                "output": str(act2_path),
                "prompt": frame_act2_prompt
            })

        except Exception as e:
            metadata["errors"].append(f"NanoBanana ACT 2 failed: {e}")
            print(f"      Error: {e}")

        if skip_video:
            print(f"\n{'─'*60}")
            print("Skipping video generation (--skip-video)")
        elif act1_path:
            # === VEO3: Generate Two-Act Video ===
            print(f"\n{'─'*60}")

            if use_laozhang and laozhang_key:
                # === LaoZhang.ai Mode ===
                print("Step 4: Generating video with Veo3 via LaoZhang.ai...")
                print(f"  First frame (ACT 1): {act1_path.name}")
                if act2_path:
                    print(f"  Last frame (ACT 2):  {act2_path.name}")

                # Retry logic with image regeneration for IP_INPUT_IMAGE errors
                max_retries = 3
                video_result = None
                last_error = None

                for attempt in range(max_retries):
                    try:
                        if attempt > 0:
                            print(f"\n  Retry {attempt + 1}/{max_retries} (regenerating images)...")
                            # Regenerate NanoBanana images with variation
                            # Note: explicitly mention adult to avoid content policy issues
                            variation_suffix = f" Add subtle variation #{attempt + 1}. Only show adults (25-40 years old)."

                            print(f"  [Regen] ACT 1 - Product Hero Shot")
                            act1_data, act1_mime = call_nanobanana_laozhang(
                                image_data=source_image_data,
                                prompt=frame_act1_prompt + variation_suffix,
                                api_key=laozhang_key,
                                mime_type=get_mime_type(detail_image_path),
                                aspect_ratio="16:9"
                            )
                            ext = ".jpg" if "jpeg" in act1_mime else ".png"
                            act1_path = output_dir / "processed" / f"frame_act1_v{attempt + 1}{ext}"
                            with open(act1_path, 'wb') as f:
                                f.write(act1_data)
                            print(f"      Saved: {act1_path.name}")

                            print(f"  [Regen] ACT 2 - Lifestyle Shot")
                            act2_data, act2_mime = call_nanobanana_laozhang(
                                image_data=source_image_data,
                                prompt=frame_act2_prompt_enhanced + variation_suffix,
                                api_key=laozhang_key,
                                mime_type=get_mime_type(detail_image_path),
                                aspect_ratio="16:9"
                            )
                            ext = ".jpg" if "jpeg" in act2_mime else ".png"
                            act2_path = output_dir / "processed" / f"frame_act2_v{attempt + 1}{ext}"
                            with open(act2_path, 'wb') as f:
                                f.write(act2_data)
                            print(f"      Saved: {act2_path.name}")

                        video_result = call_veo3_laozhang(
                            prompt=veo3_prompt,
                            api_key=laozhang_key,
                            first_frame_path=str(act1_path),
                            last_frame_path=str(act2_path) if act2_path else None,
                            model="veo-3.1-landscape-fast-fl",
                            n=1
                        )
                        break  # Success

                    except Exception as e:
                        last_error = e
                        error_str = str(e)
                        if "IP_INPUT_IMAGE" in error_str and attempt < max_retries - 1:
                            print(f"  Image rejected by API, will regenerate...")
                            time.sleep(3)
                            continue
                        elif "未成年人" in error_str and attempt < max_retries - 1:
                            print(f"  Minor detected in image, will regenerate with adult...")
                            time.sleep(3)
                            continue
                        elif "AUDIO_FILTERED" in error_str and attempt < max_retries - 1:
                            print(f"  Audio filter error, retrying...")
                            time.sleep(3)
                            continue
                        else:
                            # Non-retryable error or final attempt
                            break

                if video_result:
                    print(f"  Video ready!")
                    metadata["video_uri"] = video_result["uri"]

                    # Download video
                    print(f"\n{'─'*60}")
                    print("Step 5: Downloading video...")
                    video_path = output_dir / "videos" / "video.mp4"

                    download_video_laozhang(
                        video_url=video_result["uri"],
                        output_path=str(video_path)
                    )

                    print(f"  Saved: {video_path.name}")
                    metadata["videos_generated"].append({
                        "path": str(video_path),
                        "prompt": veo3_prompt,
                        "first_frame": str(act1_path),
                        "last_frame": str(act2_path) if act2_path else None,
                        "provider": "laozhang"
                    })
                else:
                    metadata["errors"].append(f"LaoZhang Veo3 failed: {last_error}")
                    print(f"  Error: {last_error}")

            elif use_pollo and pollo_key:
                # === Pollo.ai Mode ===
                print("Step 4: Generating video with Veo3 via Pollo.ai...")
                print(f"  First frame (ACT 1): {act1_path.name}")
                if act2_path:
                    print(f"  Last frame (ACT 2):  {act2_path.name}")

                try:
                    pollo_result = call_veo3_pollo(
                        prompt=veo3_prompt,
                        api_key=pollo_key,
                        first_frame_path=str(act1_path),
                        last_frame_path=str(act2_path) if act2_path else None,
                        aspect_ratio="16:9",
                        length=8,
                        resolution="720p"
                    )

                    task_id = pollo_result["task_id"]
                    print(f"  Task ID: {task_id}")
                    metadata["veo3_task_id"] = task_id

                    # Poll for completion
                    video_result = poll_veo3_pollo(
                        task_id=task_id,
                        api_key=pollo_key,
                        poll_interval=5,
                        timeout=600
                    )

                    print(f"  Video ready!")
                    metadata["video_uri"] = video_result["uri"]

                    # Download video
                    print(f"\n{'─'*60}")
                    print("Step 5: Downloading video...")
                    video_path = output_dir / "videos" / "video.mp4"

                    download_video_pollo(
                        video_url=video_result["uri"],
                        output_path=str(video_path)
                    )

                    print(f"  Saved: {video_path.name}")
                    metadata["videos_generated"].append({
                        "path": str(video_path),
                        "prompt": veo3_prompt,
                        "first_frame": str(act1_path),
                        "last_frame": str(act2_path) if act2_path else None,
                        "provider": "pollo"
                    })

                except Exception as e:
                    metadata["errors"].append(f"Pollo Veo3 failed: {e}")
                    print(f"  Error: {e}")

            elif use_gptunnel and gptunnel_key:
                # === GPTunnel Mode ===
                print("Step 4: Generating video with Veo3 via GPTunnel...")
                print(f"  First frame (ACT 1): {act1_path.name}")
                if act2_path:
                    print(f"  Last frame (ACT 2):  {act2_path.name}")

                try:
                    task_id = call_veo3_gptunnel(
                        prompt=veo3_prompt,
                        api_key=gptunnel_key,
                        first_frame_path=str(act1_path),
                        last_frame_path=str(act2_path) if act2_path else None,
                        aspect_ratio="16:9"
                    )

                    print(f"  Task ID: {task_id}")
                    metadata["veo3_task_id"] = task_id

                    # Poll for completion
                    video_result = poll_veo3_gptunnel(
                        task_id=task_id,
                        api_key=gptunnel_key,
                        poll_interval=10,
                        timeout=600
                    )

                    print(f"  Video ready!")
                    metadata["video_uri"] = video_result["uri"]

                    # Download video
                    print(f"\n{'─'*60}")
                    print("Step 5: Downloading video...")
                    video_path = output_dir / "videos" / "video.mp4"

                    download_video_gptunnel(
                        video_url=video_result["uri"],
                        output_path=str(video_path)
                    )

                    print(f"  Saved: {video_path.name}")
                    metadata["videos_generated"].append({
                        "path": str(video_path),
                        "prompt": veo3_prompt,
                        "first_frame": str(act1_path),
                        "last_frame": str(act2_path) if act2_path else None,
                        "provider": "gptunnel"
                    })

                except Exception as e:
                    metadata["errors"].append(f"GPTunnel Veo3 failed: {e}")
                    print(f"  Error: {e}")

            else:
                # === Direct Google API via RunPod tunnel ===
                print("Step 4: Generating video with Veo3 (frame interpolation)...")
                print(f"  First frame (ACT 1): {act1_path.name}")
                if act2_path:
                    print(f"  Last frame (ACT 2):  {act2_path.name}")

                try:
                    # Read ACT 1 image as starting frame
                    with open(act1_path, 'rb') as f:
                        act1_image_data = f.read()
                    act1_mime = "image/jpeg" if act1_path.suffix == ".jpg" else "image/png"

                    # Read ACT 2 image as ending frame (for interpolation)
                    act2_image_data = None
                    act2_mime = "image/png"
                    if act2_path:
                        with open(act2_path, 'rb') as f:
                            act2_image_data = f.read()
                        act2_mime = "image/jpeg" if act2_path.suffix == ".jpg" else "image/png"

                    operation_name = call_veo3(
                        prompt=veo3_prompt,
                        api_key=api_key,
                        image_data=act1_image_data,
                        mime_type=act1_mime,
                        last_frame_data=act2_image_data,
                        last_frame_mime_type=act2_mime,
                        aspect_ratio="16:9",
                        proxy_port=proxy_port
                    )

                    print(f"  Operation: {operation_name}")
                    metadata["veo3_operation"] = operation_name

                    # Poll for completion
                    video_result = poll_veo3(
                        operation_name=operation_name,
                        api_key=api_key,
                        proxy_port=proxy_port,
                        poll_interval=10,
                        timeout=600
                    )

                    print(f"  Video ready!")
                    metadata["video_uri"] = video_result["uri"]

                    # Download video
                    print(f"\n{'─'*60}")
                    print("Step 5: Downloading video...")
                    video_path = output_dir / "videos" / "video.mp4"

                    download_video(
                        video_uri=video_result["uri"],
                        api_key=api_key,
                        output_path=str(video_path),
                        proxy_port=proxy_port
                    )

                    print(f"  Saved: {video_path.name}")
                    metadata["videos_generated"].append({
                        "path": str(video_path),
                        "prompt": veo3_prompt,
                        "first_frame": str(act1_path),
                        "last_frame": str(act2_path) if act2_path else None,
                        "provider": "google_direct"
                    })

                except Exception as e:
                    metadata["errors"].append(f"Veo3 failed: {e}")
                    print(f"  Error: {e}")

    finally:
        # Stop tunnel if it was started
        if tunnel_started:
            print("\nStopping SSH tunnel...")
            stop_tunnel()

    # Save metadata
    metadata["completed_at"] = datetime.now().isoformat()
    metadata_path = output_dir / "metadata.json"
    with open(metadata_path, 'w', encoding='utf-8') as f:
        json.dump(metadata, f, indent=2, ensure_ascii=False)
    print(f"\nMetadata saved: {metadata_path}")

    # Save report MD
    report_md = generate_report_md(output_dir, metadata)
    report_path = output_dir / "README.md"
    with open(report_path, 'w', encoding='utf-8') as f:
        f.write(report_md)
    print(f"Report saved: {report_path}")

    # Summary
    print(f"\n{'='*60}")
    print("PIPELINE COMPLETE")
    print(f"{'='*60}")
    print(f"Output directory: {output_dir}")
    print(f"Images processed: {len(metadata['images_processed'])}")
    print(f"Videos generated: {len(metadata['videos_generated'])}")
    if metadata["errors"]:
        print(f"Errors: {len(metadata['errors'])}")
        for err in metadata["errors"]:
            print(f"  - {err}")

    return metadata


def main():
    parser = argparse.ArgumentParser(
        description="Full Video Generation Pipeline with RunPod Proxy",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
    # Basic usage
    python full_pipeline.py \\
        --url "https://www.wildberries.ru/catalog/202372804/detail.aspx" \\
        --ssh-host PROXY_HOST --ssh-port 14492 \\
        --api-key "AIzaSy..."

    # Process multiple images
    python full_pipeline.py \\
        --url "..." --ssh-host ... --ssh-port ... \\
        --images 1,2,3

Uses Claude CLI to generate creative two-act storytelling prompts.
        """
    )

    parser.add_argument("--url", required=True, help="Wildberries product URL")
    parser.add_argument("--ssh-host", required=True, help="RunPod SSH host")
    parser.add_argument("--ssh-port", required=True, type=int, help="RunPod SSH port")
    parser.add_argument("--ssh-user", default="root", help="SSH username (default: root)")
    parser.add_argument("--api-key", help="Gemini API key (or set GEMINI_API_KEY env)")
    parser.add_argument("--output", default="../output", help="Output directory (default: ../output)")
    parser.add_argument("--images", default="1,2,3,4,5,6,7", help="Image numbers to download for analysis, comma-separated (default: 1,2,3,4,5,6,7)")
    parser.add_argument("--proxy-port", type=int, default=1080, help="Local SOCKS5 proxy port (default: 1080)")
    parser.add_argument("--skip-video", action="store_true", help="Skip video generation (only process images)")
    parser.add_argument("--use-gptunnel", action="store_true", help="Use GPTunnel for Veo3 (bypasses Google rate limits)")
    parser.add_argument("--gptunnel-key", help="GPTunnel API key (or set GPTTUNNEL_KEY env)")
    parser.add_argument("--use-pollo", action="store_true", help="Use Pollo.ai for Veo3 (supports frame interpolation)")
    parser.add_argument("--pollo-key", help="Pollo API key (or set POLLO_KEY env)")
    parser.add_argument("--use-laozhang", action="store_true", help="Use LaoZhang.ai for Veo3 (supports frame interpolation, recommended)")
    parser.add_argument("--laozhang-key", help="LaoZhang API key (or set LAOZHANG_KEY env)")

    args = parser.parse_args()

    # Get API key
    api_key = args.api_key or os.environ.get("GEMINI_API_KEY")
    if not api_key:
        print("Error: API key required. Use --api-key or set GEMINI_API_KEY environment variable.")
        sys.exit(1)

    # Get GPTunnel key if using GPTunnel
    gptunnel_key = None
    if args.use_gptunnel:
        gptunnel_key = args.gptunnel_key or os.environ.get("GPTTUNNEL_KEY")
        if not gptunnel_key:
            print("Error: GPTunnel key required when using --use-gptunnel. Use --gptunnel-key or set GPTTUNNEL_KEY environment variable.")
            sys.exit(1)
        print(f"Using GPTunnel for Veo3 video generation")

    # Get Pollo key if using Pollo
    pollo_key = None
    if args.use_pollo:
        pollo_key = args.pollo_key or os.environ.get("POLLO_KEY")
        if not pollo_key:
            print("Error: Pollo key required when using --use-pollo. Use --pollo-key or set POLLO_KEY environment variable.")
            sys.exit(1)
        print(f"Using Pollo.ai for Veo3 video generation")

    # Get LaoZhang key if using LaoZhang
    laozhang_key = None
    if args.use_laozhang:
        laozhang_key = args.laozhang_key or os.environ.get("LAOZHANG_KEY")
        if not laozhang_key:
            print("Error: LaoZhang key required when using --use-laozhang. Use --laozhang-key or set LAOZHANG_KEY environment variable.")
            sys.exit(1)
        print(f"Using LaoZhang.ai for Veo3 video generation")

    # Parse image numbers
    image_nums = [int(x.strip()) for x in args.images.split(",")]

    # Build command line string (without API key for security)
    cmd_parts = [
        "python full_pipeline.py",
        f'--url "{args.url}"',
        f"--ssh-host {args.ssh_host}",
        f"--ssh-port {args.ssh_port}",
    ]
    if args.ssh_user != "root":
        cmd_parts.append(f"--ssh-user {args.ssh_user}")
    if args.output != "../output":
        cmd_parts.append(f"--output {args.output}")
    if args.images != "1,2,3,4,5,6,7":
        cmd_parts.append(f"--images {args.images}")
    if args.proxy_port != 1080:
        cmd_parts.append(f"--proxy-port {args.proxy_port}")
    if args.skip_video:
        cmd_parts.append("--skip-video")
    if args.use_gptunnel:
        cmd_parts.append("--use-gptunnel")
    if args.use_pollo:
        cmd_parts.append("--use-pollo")
    if args.use_laozhang:
        cmd_parts.append("--use-laozhang")
    command_line = " \\\n    ".join(cmd_parts)

    # Run pipeline
    result = run_pipeline(
        product_url=args.url,
        ssh_host=args.ssh_host,
        ssh_port=args.ssh_port,
        api_key=api_key,
        output_base=args.output,
        ssh_user=args.ssh_user,
        image_nums=image_nums,
        proxy_port=args.proxy_port,
        skip_video=args.skip_video,
        use_gptunnel=args.use_gptunnel,
        gptunnel_key=gptunnel_key,
        use_pollo=args.use_pollo,
        pollo_key=pollo_key,
        use_laozhang=args.use_laozhang,
        laozhang_key=laozhang_key,
        command_line=command_line
    )

    # Exit with error code if there were errors
    sys.exit(1 if result.get("errors") else 0)


if __name__ == "__main__":
    main()
