"""
거절된 씬 이미지에 대한 다양한 프레이밍 전략 테스트.

Gemini content moderation에 의해 거절된 씬 프롬프트를 다양한 전략으로
재작성하여 생성 성공률을 높이는 방법을 실험한다.

실행: cd backend && .venv/bin/python tests/test_moderation_retry_strategies.py
"""

import json
import os
import sys
import time
from pathlib import Path

# 프로젝트 루트 설정
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
# 2026-04-27 hotfix: prod URL setdefault 제거. 단독 실행 시 prod DB가 의도하지 않은
# 변경에 노출될 위험이 있어 sqlite default로 변경. prod DB가 필요하면 명시적으로
# export DATABASE_URL=postgresql://... 후 실행할 것.
import tempfile as _tmp_default
os.environ.setdefault("DATABASE_URL", f"sqlite:///{_tmp_default.gettempdir()}/theroad_standalone.sqlite")

from app.core.config import settings
from app.modules.llm.openai_client import OpenAIClient
from app.modules.llm.gemini_image_client import GeminiImageClient, ModerationError


# ── 재프레이밍 전략 정의 ──────────────────────────────────────

STRATEGIES = {
    "film_previs": {
        "name": "영화 프리비즈 (Pre-visualization)",
        "prefix": (
            "This is a pre-visualization concept art for a Korean sci-fi film production. "
            "The image is a storyboard-quality still frame for the director's shot planning reference. "
            "Cinematic composition, dramatic lighting, professional film production context. "
            "NO gore, NO explicit violence, NO blood — focus on dramatic tension and emotion.\n\n"
        ),
    },
    "graphic_novel": {
        "name": "그래픽 노블 일러스트",
        "prefix": (
            "A graphic novel illustration panel in the style of Korean manhwa. "
            "Stylized, dramatic, with bold lines and cinematic framing. "
            "Focus on character expressions, body language, and atmospheric tension. "
            "Imply action through motion lines and dramatic angles rather than explicit depiction.\n\n"
        ),
    },
    "movie_poster": {
        "name": "영화 포스터 / 키 비주얼",
        "prefix": (
            "A cinematic movie poster key visual for a Korean sci-fi thriller. "
            "Focus on the emotional core of the scene — characters' faces, dramatic poses, "
            "atmospheric lighting. Professional film marketing quality. "
            "Show the mood and tension, not the violence.\n\n"
        ),
    },
    "aftermath": {
        "name": "직후 정적 장면",
        "prefix": (
            "The moment AFTER the action — a quiet, contemplative still frame. "
            "Characters process what just happened. Focus on facial expressions, "
            "body posture, environmental details that tell the story. "
            "Cinematic, thoughtful, no active violence.\n\n"
        ),
    },
    "symbolic": {
        "name": "상징적 표현",
        "prefix": (
            "A symbolic, metaphorical representation of this scene. "
            "Use visual metaphors, silhouettes, shadows, reflections, and abstract framing "
            "to convey the emotional weight without literal depiction. "
            "Art house cinema aesthetic.\n\n"
        ),
    },
}


def sanitize_prompt_with_gpt(original_prompt: str, strategy_name: str, strategy_prefix: str) -> str:
    """GPT-5.4로 프롬프트를 전략에 맞게 재작성."""
    client = OpenAIClient()

    system = (
        "You are a film production concept artist who rewrites scene descriptions "
        "for text-to-image generation. The original description was rejected by the "
        "image generation API's safety filter. Rewrite it to be visually compelling "
        "while avoiding content that triggers safety filters.\n\n"
        "Rules:\n"
        "- Keep the scene's emotional core and narrative meaning\n"
        "- Remove explicit violence, blood, gore, sexual content\n"
        "- Replace with dramatic tension, facial expressions, body language\n"
        "- Maintain cinematic quality and Korean sci-fi setting\n"
        "- Output ONLY the rewritten prompt, no explanation"
    )

    user = (
        f"Strategy: {strategy_name}\n"
        f"Prefix to include: {strategy_prefix}\n\n"
        f"Original rejected prompt:\n{original_prompt}\n\n"
        f"Rewrite this prompt following the strategy above."
    )

    result = client.generate_structured(
        system_prompt=system,
        user_prompt=user,
        response_schema={
            "type": "object",
            "additionalProperties": False,
            "properties": {
                "rewritten_prompt": {"type": "string"},
                "changes_made": {"type": "string"},
            },
            "required": ["rewritten_prompt", "changes_made"],
        },
        schema_name="prompt_rewrite",
        max_tokens=2000,
    )

    return result["rewritten_prompt"]


def try_generate_image(prompt: str, aspect_ratio: str = "16:9") -> dict:
    """Gemini로 이미지 생성 시도. 결과 반환."""
    client = GeminiImageClient(
        api_key=settings.gemini_api_key,
        model=settings.gemini_image_model,
    )

    start = time.time()
    try:
        image_bytes, response_time = client.generate_image(
            prompt=prompt,
            aspect_ratio=aspect_ratio,
        )
        return {
            "status": "success",
            "size_bytes": len(image_bytes),
            "response_time_ms": int((time.time() - start) * 1000),
            "image_bytes": image_bytes,
        }
    except ModerationError as e:
        return {
            "status": "moderation_blocked",
            "block_reason": e.block_reason,
            "block_categories": e.block_categories,
            "response_time_ms": int((time.time() - start) * 1000),
        }
    except Exception as e:
        return {
            "status": "error",
            "error": str(e)[:300],
            "response_time_ms": int((time.time() - start) * 1000),
        }


def run_test(rejected_scenes: list[dict], output_dir: Path, max_scenes: int = 3):
    """거절된 씬들에 대해 모든 전략을 시도하고 결과 기록."""

    output_dir.mkdir(parents=True, exist_ok=True)
    results = []

    for scene in rejected_scenes[:max_scenes]:
        scene_id = scene["id"]
        beat_title = scene["beat_title"]
        original_prompt = scene["still_frame_prompt"]

        print(f"\n{'='*60}")
        print(f"씬: {beat_title}")
        print(f"원본: {original_prompt[:100]}...")
        print(f"{'='*60}")

        scene_results = {
            "scene_id": scene_id,
            "beat_title": beat_title,
            "original_prompt": original_prompt,
            "strategies": {},
        }

        for strategy_key, strategy in STRATEGIES.items():
            print(f"\n  전략: {strategy['name']}")

            # 1. GPT로 프롬프트 재작성
            try:
                rewritten = sanitize_prompt_with_gpt(
                    original_prompt, strategy["name"], strategy["prefix"]
                )
                full_prompt = strategy["prefix"] + rewritten
                print(f"  재작성: {rewritten[:80]}...")
            except Exception as e:
                print(f"  GPT 재작성 실패: {e}")
                scene_results["strategies"][strategy_key] = {
                    "status": "rewrite_failed",
                    "error": str(e)[:200],
                }
                continue

            # 2. Gemini로 이미지 생성 시도
            result = try_generate_image(full_prompt)
            result["rewritten_prompt"] = rewritten
            result["strategy_name"] = strategy["name"]

            if result["status"] == "success":
                # 이미지 저장
                img_path = output_dir / f"{scene_id}_{strategy_key}.png"
                img_path.write_bytes(result.pop("image_bytes"))
                result["saved_to"] = str(img_path)
                print(f"  ✅ 성공! ({result['size_bytes']//1024}KB, {result['response_time_ms']}ms)")
            elif result["status"] == "moderation_blocked":
                print(f"  ❌ 거절: {result['block_reason']}")
            else:
                print(f"  ⚠️ 에러: {result.get('error', '?')[:80]}")

            scene_results["strategies"][strategy_key] = result

            # API rate limit 방지
            time.sleep(3)

        results.append(scene_results)

    # 결과 요약
    print(f"\n{'='*60}")
    print("=== 결과 요약 ===")
    print(f"{'='*60}")

    total_attempts = 0
    total_success = 0
    strategy_stats = {k: {"attempts": 0, "success": 0} for k in STRATEGIES}

    for scene_result in results:
        print(f"\n씬: {scene_result['beat_title']}")
        for strategy_key, result in scene_result["strategies"].items():
            total_attempts += 1
            strategy_stats[strategy_key]["attempts"] += 1
            status = result.get("status", "?")
            if status == "success":
                total_success += 1
                strategy_stats[strategy_key]["success"] += 1
                print(f"  ✅ {STRATEGIES[strategy_key]['name']}")
            else:
                print(f"  ❌ {STRATEGIES[strategy_key]['name']} — {status}")

    print(f"\n전체: {total_success}/{total_attempts} 성공 ({total_success/max(total_attempts,1)*100:.0f}%)")
    print("\n전략별 성공률:")
    for key, stats in strategy_stats.items():
        rate = stats["success"] / max(stats["attempts"], 1) * 100
        print(f"  {STRATEGIES[key]['name']}: {stats['success']}/{stats['attempts']} ({rate:.0f}%)")

    # JSON 저장
    report_path = output_dir / "retry_report.json"
    # image_bytes 제거 (JSON 직렬화 불가)
    report_path.write_text(
        json.dumps(results, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    print(f"\n리포트 저장: {report_path}")


def load_rejected_scenes() -> list[dict]:
    """PostgreSQL에서 이미지 미생성 씬 로드."""
    from app.core.database import SessionLocal
    from app.models.project import SceneStill, ImageAsset

    db = SessionLocal()
    try:
        # 이미지가 없는 씬 스틸 조회
        subquery = db.query(ImageAsset.still_id).filter(
            ImageAsset.still_id.isnot(None),
            ImageAsset.asset_type == "scene",
        ).subquery()

        missing = (
            db.query(SceneStill)
            .filter(SceneStill.id.notin_(db.query(subquery)))
            .order_by(SceneStill.still_index)
            .all()
        )

        return [
            {
                "id": s.id,
                "beat_title": s.beat_title or "",
                "still_frame_prompt": s.still_frame_prompt or "",
                "still_index": s.still_index,
            }
            for s in missing
        ]
    finally:
        db.close()


if __name__ == "__main__":
    print("=== Moderation 거절 씬 재생성 전략 테스트 ===")
    print(f"OpenAI: {settings.openai_model}")
    print(f"Gemini: {settings.gemini_image_model}")
    print()

    rejected = load_rejected_scenes()
    print(f"미생성 씬: {len(rejected)}개")

    if not rejected:
        print("모든 씬 이미지가 생성되어 있습니다.")
        sys.exit(0)

    # 처음 3개만 테스트 (5전략 × 3씬 = 15회 API 호출)
    output_dir = Path(settings.projects_dir) / "moderation_retry_test"
    run_test(rejected, output_dir, max_scenes=3)
