"""Compile clip performance into a knowledge-base file Claude reads when
picking shorts. Structured (no LLM): regenerated whenever metrics sync.

The clip-picking skills glob .podcli/knowledge/*.md, so writing the digest
here is what closes the loop — the next suggest_clips run is informed by what
actually performed.
"""
from __future__ import annotations

import os
from collections import defaultdict
from datetime import datetime, timezone
from typing import Any, Callable, Optional

from config.paths import paths
from services.clips_history import load_clips_history

LEARNINGS_FILE = "13-performance-learnings.md"
AI_START = "<!-- AI-ANALYSIS-START -->"
AI_END = "<!-- AI-ANALYSIS-END -->"


def _existing_ai_block(path: str) -> str:
    if not os.path.exists(path):
        return ""
    txt = open(path, encoding="utf-8").read()
    i, j = txt.find(AI_START), txt.find(AI_END)
    return txt[i:j + len(AI_END)] if (i != -1 and j != -1) else ""


def _has_perf(c: dict) -> bool:
    m = c.get("metrics") or {}
    return m.get("views") is not None or m.get("retention") is not None


def _agg(clips: list[dict], key: Callable[[dict], Any]) -> list[dict]:
    groups: dict[str, list[dict]] = defaultdict(list)
    for c in clips:
        groups[key(c) or "—"].append(c)
    out = []
    for k, cs in groups.items():
        vals = lambda f: [c["metrics"][f] for c in cs if (c.get("metrics") or {}).get(f) is not None]
        ret, ctr, views = vals("retention"), vals("ctr"), vals("views")
        out.append({
            "key": k, "n": len(cs),
            "ret": round(sum(ret) / len(ret), 1) if ret else None,
            "ctr": round(sum(ctr) / len(ctr), 1) if ctr else None,
            "views": int(sum(views) / len(views)) if views else None,
        })
    return out


def _length_bucket(c: dict) -> str:
    d = c.get("duration") or 0
    return "<25s" if d < 25 else "25-35s" if d < 35 else "35-45s" if d < 45 else "45s+"


def write_learnings(min_clips: int = 3, ai_block: Optional[str] = None) -> Optional[str]:
    """Regenerate the performance-learnings knowledge file. None if too little data.

    The structured 'what's working' section is rebuilt every call; any AI-analysis
    block is preserved (or replaced when ai_block is passed)."""
    clips = [c for c in load_clips_history() if _has_perf(c)]
    if len(clips) < min_clips:
        return None

    by_type = sorted(_agg(clips, lambda c: c.get("content_type")), key=lambda r: r["ret"] or 0, reverse=True)
    by_caption = sorted(_agg(clips, lambda c: c.get("caption_style")), key=lambda r: r["ctr"] or 0, reverse=True)
    by_length = sorted(_agg(clips, _length_bucket), key=lambda r: r["ret"] or 0, reverse=True)
    top = sorted(clips, key=lambda c: (c.get("metrics") or {}).get("views") or 0, reverse=True)[:5]
    now = datetime.now(timezone.utc).strftime("%Y-%m-%d")

    L: list[str] = [
        "# Performance learnings",
        "",
        "<!-- AUTO-GENERATED by `podcli youtube sync`. The 'What's working now' section is",
        "     regenerated each sync — edits there are overwritten. -->",
        "",
        f"## What's working now (updated {now} · {len(clips)} published clips)",
        "",
        "Retention = content quality (did it hold viewers). CTR = packaging (title/thumbnail). Views = reach.",
        "",
        "**By moment type — retention:**",
    ]
    L += [f"- {r['key']}: {r['ret']}% retention · {r['ctr']}% CTR · ~{r['views']} views (n={r['n']})" for r in by_type]
    L += ["", "**By caption style — CTR:**"]
    L += [f"- {r['key']}: {r['ctr']}% CTR · {r['ret']}% retention (n={r['n']})" for r in by_caption]
    L += ["", "**By length — retention:**"]
    L += [f"- {r['key']}: {r['ret']}% retention (n={r['n']})" for r in by_length]
    L += ["", "**Top performers:**"]
    L += [f"- \"{c['title']}\" — {(c.get('metrics') or {}).get('views')} views, {(c.get('metrics') or {}).get('retention')}% retention [{c.get('content_type', '?')}]" for c in top]
    L += ["", "## Guidance for picking shorts", ""]
    if by_type:
        L.append(f"- Prioritize **{by_type[0]['key']}** moments — highest retention ({by_type[0]['ret']}%).")
    best_ctr = next((r for r in by_caption if r["ctr"]), None)
    if best_ctr:
        L.append(f"- **{best_ctr['key']}** captions earn the most clicks ({best_ctr['ctr']}% CTR).")
    if by_length:
        L.append(f"- Target **{by_length[0]['key']}** clips for retention.")
    L.append("")

    os.makedirs(paths["knowledge"], exist_ok=True)
    path = os.path.join(paths["knowledge"], LEARNINGS_FILE)
    block = ai_block if ai_block is not None else _existing_ai_block(path)
    content = "\n".join(L)
    if block:
        content += "\n\n" + block + "\n"
    with open(path, "w", encoding="utf-8") as f:
        f.write(content)
    return path


def _clip_line(c: dict) -> str:
    m = c.get("metrics") or {}
    return (f"- [{c.get('content_type', '?')} · {c.get('caption_style', '?')} · {round(c.get('duration', 0))}s · "
            f"{m.get('retention')}% retention · {m.get('ctr')}% CTR] \"{c.get('title')}\"\n"
            f"  transcript: {c.get('transcript_slice', '')[:400]}")


def write_semantic_learnings(top_n: int = 4, min_total: int = 6) -> Optional[str]:
    """Token-frugal LLM pass: contrast top vs bottom clips by retention and write
    a qualitative 'why winners win' block. Returns the file path, or None if there
    isn't enough data or no AI CLI is available."""
    clips = [c for c in load_clips_history()
             if _has_perf(c) and c.get("transcript_slice") and (c.get("metrics") or {}).get("retention") is not None]
    if len(clips) < min_total:
        return None
    ranked = sorted(clips, key=lambda c: c["metrics"]["retention"], reverse=True)
    top_performers, underperformers = ranked[:top_n], ranked[-top_n:]

    try:
        from services.claude_suggest import _find_ai_cli_candidates, _run_ai_command
    except Exception:
        return None
    candidates = _find_ai_cli_candidates()
    if not candidates:
        return None
    cli_path, engine = candidates[0]

    prompt = (
        "You analyze short-form video performance to guide future clip selection.\n"
        "Below are the highest- and lowest-retention clips from one podcast channel.\n\n"
        "TOP PERFORMERS:\n" + "\n".join(_clip_line(c) for c in top_performers) +
        "\n\nUNDERPERFORMERS:\n" + "\n".join(_clip_line(c) for c in underperformers) +
        "\n\nIn 4-6 concise markdown bullets, state the concrete content patterns that distinguish "
        "the top performers from the underperformers (hooks, topic, emotional beat, structure) and give "
        "actionable guidance for picking future shorts. No preamble, just the bullets."
    )
    os.makedirs(paths["working"], exist_ok=True)
    prompt_file = os.path.join(paths["working"], "_perf_analysis_prompt.txt")
    with open(prompt_file, "w", encoding="utf-8") as f:
        f.write(prompt)
    try:
        res = _run_ai_command(cli_path, engine, prompt, prompt_file, paths["project_root"], timeout=180)
    except Exception:
        return None
    finally:
        try:
            os.unlink(prompt_file)
        except Exception:
            pass
    text = (res.stdout or "").strip()
    if not text:
        return None
    now = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    block = f"{AI_START}\n## What separates top performers (AI analysis · {now})\n\n{text}\n{AI_END}"
    return write_learnings(ai_block=block)
