from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

from src.services.creative import LAYOUTS, CreativeBrief
from src.services.llm import LLMService

PLATFORMS = ("linkedin", "instagram", "x", "threads", "facebook")


@dataclass
class PlatformPost:
    post: str
    hashtags: list[str]

    @classmethod
    def from_dict(cls, data: dict[str, Any] | None) -> "PlatformPost":
        data = data or {}
        return cls(
            post=str(data.get("post", "")).strip(),
            hashtags=[str(h).lstrip("#") for h in (data.get("hashtags") or [])][:15],
        )

    def as_publish_text(self) -> str:
        body = self.post.rstrip()
        lines = [ln for ln in body.splitlines() if not ln.strip().startswith("#")]
        body = "\n".join(lines).strip()
        tags = " ".join(f"#{h}" for h in self.hashtags if h)
        return f"{body}\n\n{tags}".strip() if tags else body


@dataclass
class ContentIdea:
    id: str
    format: str
    topic: str
    hook: str
    news_url: str
    news_title: str
    visual_brief: str
    creative_direction: str = ""
    layout: str = "editorial_overlay"
    palette: list[str] = field(default_factory=list)
    palette_name: str = ""
    typography: str = "modern_startup"
    background_style: str = "dark_premium"
    illustration_style: str = "editorial_3d"
    image_prompt: str = ""
    video_style: str = "cinematic"
    aspect: str = "square"
    why_it_matters: str = ""
    key_fact: str = ""
    news_category: str = "ai"
    slides: list[dict[str, str]] = field(default_factory=list)
    video_script: list[dict[str, str]] = field(default_factory=list)
    source_refs: list[str] = field(default_factory=list)
    platforms: dict[str, PlatformPost] = field(default_factory=dict)

    def platform(self, name: str) -> PlatformPost:
        return self.platforms.get(name) or PlatformPost(post=self.hook, hashtags=["AI", "Tech"])

    def news_source_label(self) -> str:
        return (self.news_title or "Tech News")[:60]

    @classmethod
    def from_dict(cls, data: dict[str, Any], idx: int, brief: CreativeBrief | None = None) -> "ContentIdea":
        fmt = str(data.get("format", "image")).lower().strip()
        if fmt not in {"image", "carousel", "video"}:
            fmt = "image"

        slides = data.get("slides") or []
        if fmt == "carousel" and len(slides) < 5:
            slides = [
                {"title": data.get("hook", "Hook"), "body": "The moment everything shifted.", "role": "hook"},
                {"title": "What happened", "body": str(data.get("topic", "")), "role": "what"},
                {"title": "Why it matters", "body": str(data.get("why_it_matters", "")), "role": "why"},
                {"title": "The numbers", "body": str(data.get("key_fact", "")), "role": "stats"},
                {"title": "The insight", "body": "Here's what most people are missing.", "role": "insight"},
                {"title": "What's next", "body": "This changes the next 12 months.", "role": "future"},
                {"title": "Your move", "body": "Save + share with someone who needs this.", "role": "cta"},
            ]

        layout = str(data.get("layout") or (brief.layout if brief else "editorial_overlay")).strip()
        if layout not in LAYOUTS:
            layout = brief.layout if brief else LAYOUTS[idx % len(LAYOUTS)]

        palette = [str(c) for c in (data.get("palette") or (brief.palette if brief else []))][:4]
        if brief and not palette:
            palette = list(brief.palette)

        raw_platforms = data.get("platforms") or {}
        platforms = {p: PlatformPost.from_dict(raw_platforms.get(p)) for p in PLATFORMS}

        video_script = [
            {
                "beat": str(s.get("beat", "")),
                "on_screen": str(s.get("on_screen", "")),
                "voiceover": str(s.get("voiceover", "")),
                "visual": str(s.get("visual", "")),
                "motion": str(s.get("motion", "zoom_in")),
                "seconds": float(s.get("seconds", 4) or 4),
            }
            for s in (data.get("video_script") or [])
        ][:8]

        if fmt == "video" and len(video_script) < 4:
            video_script = [
                {"beat": "hook", "on_screen": str(data.get("hook", "")), "voiceover": "This just dropped.", "visual": "", "motion": "punch_in", "seconds": 2.5},
                {"beat": "problem", "on_screen": "What changed", "voiceover": str(data.get("topic", "")), "visual": "", "motion": "pan_right", "seconds": 5},
                {"beat": "details", "on_screen": str(data.get("key_fact", "The details")), "voiceover": str(data.get("why_it_matters", "")), "visual": "", "motion": "zoom_in", "seconds": 7},
                {"beat": "impact", "on_screen": "Why you should care", "voiceover": str(data.get("why_it_matters", "")), "visual": "", "motion": "parallax", "seconds": 6},
                {"beat": "cta", "on_screen": "Follow for daily AI drops", "voiceover": "Save this.", "visual": "", "motion": "zoom_out", "seconds": 3},
            ]

        return cls(
            id=str(data.get("id") or f"idea-{idx:02d}"),
            format=fmt,
            topic=str(data.get("topic", "")).strip(),
            hook=str(data.get("hook", "")).strip(),
            news_url=str(data.get("news_url", "")).strip(),
            news_title=str(data.get("news_title", "")).strip(),
            visual_brief=str(data.get("visual_brief", "")).strip(),
            creative_direction=str(data.get("creative_direction", "")).strip(),
            layout=layout,
            palette=palette,
            palette_name=str(data.get("palette_name") or (brief.palette_name if brief else "")),
            typography=str(data.get("typography") or (brief.typography if brief else "modern_startup")),
            background_style=str(data.get("background_style") or (brief.background if brief else "dark_premium")),
            illustration_style=str(data.get("illustration_style") or (brief.illustration_style if brief else "editorial_3d")),
            image_prompt=str(data.get("image_prompt", "")).strip(),
            video_style=str(data.get("video_style") or (brief.video_style if brief else "cinematic")),
            aspect=str(data.get("aspect") or (brief.aspect if brief else "square")),
            why_it_matters=str(data.get("why_it_matters", "")).strip(),
            key_fact=str(data.get("key_fact", "")).strip(),
            news_category=str(data.get("news_category", "ai")).strip().lower(),
            slides=[{"title": str(s.get("title", "")), "body": str(s.get("body", "")), "role": str(s.get("role", "")), "image_prompt": str(s.get("image_prompt", ""))} for s in slides][:8],
            video_script=video_script,
            source_refs=[str(x) for x in (data.get("source_refs") or [])][:6],
            platforms=platforms,
        )


IDEATION_SYSTEM = """You are the Creative Director + Social Strategist for a premium AI/Tech media brand
(quality bar: OpenAI, Nvidia, Apple, Stripe, Linear, Figma, Arc — NEVER Canva-like templates).

MISSION: Turn breaking tech news into scroll-stopping, publish-ready social assets with agency-level polish.

VOICE:
- Natural fluent English, modern Gen-Z energy, professional credibility.
- Sharp, concrete, specific. Avoid empty hype, buzzword soup, and generic phrasing.
- Forbidden cliches: "game-changer", "revolutionary", "changing everything" unless backed by evidence.

FACTUALITY (non-negotiable):
- Every idea maps to ONE real item from the provided feed.
- Do not invent data, quotes, timelines, valuations, benchmarks, or product claims.
- If a number is uncertain, speak in implications, not fake precision.
- key_fact must be specific and verifiable from the source context.

CONTENT RULES:
- Prioritize: breaking launches, funding rounds, model updates, cyber incidents, research papers, major big-tech moves.
- Hook in first 1–2 lines. Make "why it matters" practical for operators/builders/decision-makers.
- Unique copy per platform. Never paraphrase the same caption 5 times.
- Emojis: 0–3 tasteful max.
- Hashtags ONLY in hashtags arrays (not in post body).

PLATFORM WRITING STANDARDS:
- linkedin: 900–1300 chars, insight-led narrative, crisp line breaks, 3–5 tags.
- instagram: high-energy storytelling, skimmable rhythm, 8–12 tags, social CTA.
- x: <=260 chars, punchy and opinionated, one clear takeaway.
- threads: 350–500 chars, conversational perspective + debate hook.
- facebook: 400–800 chars, clear context + discussion starter.

IMAGE / VISUAL RULES:
- image_prompt describes a full cinematic scene that EXPLAINS the story visually.
- No text-in-image instructions, no poster language, no blank backgrounds, no generic "AI brain" wallpaper.
- Include tangible objects/environments: devices, UI, labs, chips, dashboards, robotics, people-in-context, data metaphors.
- Match subject language to news_category (ai/hardware/startup/cyber/research/programming/robotics/healthcare/finance/cloud/opensource/bigtech).
- Follow assigned creative brief EXACTLY (layout, palette, typography, background, illustration_style).

CAROUSEL RULES:
- 6–7 slides, narrative arc: hook -> what happened -> why it matters -> stats/fact -> expert insight -> future impact -> CTA.
- Each slide must add NEW information (no repetitive rewording).
- Slide titles <=8 words. Body concise and high-signal.

VIDEO RULES:
- Cinematic reel storyboard, not slideshow.
- Beats: hook (0–2s) -> problem (2–6s) -> details (6–15s) -> impact (15–25s) -> CTA (25–35s).
- Each beat needs: on_screen (<=6 words), voiceover, visual scene, motion.
- motion one of: zoom_in|zoom_out|pan_left|pan_right|punch_in|parallax|glitch.

QUALITY GATE (self-check before output):
Reject and regenerate internally if any idea is generic, repetitive, or weak.
Do not output ideas that:
- look like templates,
- repeat prior layout/palette/style,
- have weak hooks,
- lack a concrete key_fact,
- or have vague visual prompts.
"""


def generate_ideas(
    llm: LLMService,
    *,
    brand_name: str,
    profiles: str,
    news_block: str,
    batch_size: int,
    enable_video: bool,
    creative_briefs: list[CreativeBrief],
    recent_styles: str,
) -> list[ContentIdea]:
    formats = "image, carousel, video" if enable_video else "image, carousel"
    # Force format diversity when possible
    format_plan = []
    cycle = ["image", "carousel", "video"] if enable_video else ["image", "carousel"]
    for i in range(batch_size):
        format_plan.append(cycle[i % len(cycle)])

    user = f"""Brand: {brand_name}

## Recent styles ALREADY USED (do NOT repeat these combinations)
{recent_styles}

## LOCKED creative briefs (assign idea-01 to brief 1, etc. — mandatory)
{chr(10).join(f'{i+1}. {b.layout} | {b.palette_name} | {b.typography} | {b.illustration_style} | {b.video_style}' for i, b in enumerate(creative_briefs))}

Full briefs:
{_briefs_json(creative_briefs)}

## Creator style notes
{profiles}

## Latest news (ONLY source of truth — pick the highest-impact stories)
{news_block}

Create exactly {batch_size} packages from DISTINCT high-impact news stories.
Suggested format order: {format_plan}
Mix of formats available: {formats}.

Each idea MUST:
1. Use its locked creative brief fields (layout, palette, typography, background_style, illustration_style, video_style, aspect, palette_name)
2. Include a rich image_prompt (cinematic scene, no text-in-image, agency quality)
3. Include news_category one of: ai|hardware|startup|cyber|research|programming|robotics|healthcare|finance|cloud|opensource|bigtech
4. Have UNIQUE platform copy for linkedin, instagram, x, threads, facebook
5. Have a strong key_fact tied to the source (no fabricated numbers)
6. Avoid repeating sentence structure across platform posts for the same idea

JSON schema:
{{
  "ideas": [
    {{
      "id": "idea-01",
      "format": "image|carousel|video",
      "topic": "...",
      "hook": "scroll-stopping ≤8 words",
      "news_url": "https://...",
      "news_title": "exact title",
      "news_category": "ai",
      "why_it_matters": "...",
      "key_fact": "one concrete fact",
      "creative_direction": "1-2 sentence art direction",
      "layout": "...",
      "palette_name": "...",
      "palette": ["#...", "#..."],
      "typography": "...",
      "background_style": "...",
      "illustration_style": "...",
      "aspect": "square|portrait",
      "image_prompt": "detailed cinematic scene WITHOUT any text overlays...",
      "visual_brief": "short overlay caption for design system (1 sentence)",
      "video_style": "...",
      "source_refs": ["news_title"],
      "slides": [
        {{"title": "...", "body": "...", "role": "hook|what|why|stats|insight|future|cta", "image_prompt": "optional"}}
      ],
      "video_script": [
        {{"beat": "hook", "on_screen": "...", "voiceover": "...", "visual": "...", "motion": "punch_in", "seconds": 2.5}}
      ],
      "platforms": {{
        "linkedin": {{"post": "...", "hashtags": ["AI"]}},
        "instagram": {{"post": "...", "hashtags": ["AI"]}},
        "x": {{"post": "...", "hashtags": ["AI"]}},
        "threads": {{"post": "...", "hashtags": ["Tech"]}},
        "facebook": {{"post": "...", "hashtags": ["AI"]}}
      }},
      "quality_checks": {{
        "hook_strength_1to10": 9,
        "specificity_1to10": 9,
        "visual_uniqueness_1to10": 9,
        "factual_confidence_1to10": 9
      }}
    }}
  ]
}}
"""
    data = llm.complete_json(IDEATION_SYSTEM, user, max_tokens=12000)
    raw_ideas = data.get("ideas") if isinstance(data, dict) else data
    if not isinstance(raw_ideas, list):
        raise RuntimeError("LLM ideation did not return an ideas list")

    ideas: list[ContentIdea] = []
    for i, item in enumerate(raw_ideas[:batch_size]):
        brief = creative_briefs[i] if i < len(creative_briefs) else None
        idea = ContentIdea.from_dict(item, i + 1, brief=brief)
        # Enforce locked brief (LLM sometimes drifts)
        if brief:
            idea.layout = brief.layout
            idea.palette = list(brief.palette)
            idea.palette_name = brief.palette_name
            idea.typography = brief.typography
            idea.background_style = brief.background
            idea.illustration_style = brief.illustration_style
            idea.video_style = brief.video_style
            idea.aspect = brief.aspect
        ideas.append(idea)
    return ideas


def _briefs_json(briefs: list[CreativeBrief]) -> str:
    lines = []
    for i, b in enumerate(briefs, 1):
        lines.append(
            f"idea-{i:02d}: layout={b.layout}; typography={b.typography}; "
            f"background={b.background}; palette_name={b.palette_name}; palette={b.palette}; "
            f"illustration_style={b.illustration_style}; video_style={b.video_style}; aspect={b.aspect}"
        )
    return "\n".join(lines)
