"""Pipeline generator — enhanced outline + section generation with rich context."""

from __future__ import annotations

import hashlib
import json
from datetime import datetime, timezone

from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, MofNCompleteColumn

from ..config import Config
from ..db import Database
from .context import ContextBuilder
from .prompts import OUTLINE_PROMPT_V2, SECTION_PROMPT_V2
from .formatter import DraftFormatter

console = Console()


def _prompt_hash(text: str) -> str:
    return hashlib.sha256(text.encode()).hexdigest()[:16]


class PipelineGenerator:
    def __init__(self, config: Config, db: Database, analyzer):
        self.config = config
        self.db = db
        self.analyzer = analyzer
        self.context_builder = ContextBuilder(config, db)

    def _format_ideas_for_prompt(self, ideas: list[dict]) -> str:
        if not ideas:
            return "(none found)"
        lines = []
        for idea in ideas:
            lines.append(
                f"- [{idea.get('type', '?')}] {idea['title']}: "
                f"{idea['description']} (from {idea.get('draft_name', '?')})"
            )
        return "\n".join(lines)

    def _format_rfcs_for_prompt(self, rfcs: list[tuple[str, int]]) -> str:
        if not rfcs:
            return "(none found)"
        return "\n".join(f"- RFC {ref_id} (cited by {count} drafts)" for ref_id, count in rfcs)

    def _format_similar_for_prompt(self, similar: list[tuple[str, float]]) -> str:
        if not similar:
            return "(none found)"
        lines = []
        for name, sim in similar:
            draft = self.db.get_draft(name)
            title = draft.title if draft else name
            lines.append(f"- {name}: {title} (similarity: {sim:.2f})")
        return "\n".join(lines)

    def _format_top_rated_for_prompt(self, top_rated: list[tuple]) -> str:
        if not top_rated:
            return "(none found)"
        return "\n".join(
            f"- {name}: {title} (score: {score:.1f})"
            for name, title, score in top_rated
        )

    def _format_siblings_for_prompt(self, siblings: list[dict]) -> str:
        if not siblings:
            return "(none — this is the first draft in the family)"
        lines = []
        for s in siblings:
            role = s.get("role", "?")
            title = s.get("title", "?")
            abstract = s.get("abstract", "")[:200]
            outline = s.get("outline", {})
            sections = outline.get("sections", [])
            section_titles = [sec.get("title", "") for sec in sections]
            lines.append(
                f"- [{role}] {title}\n"
                f"  Abstract: {abstract}\n"
                f"  Sections: {', '.join(section_titles)}"
            )
        return "\n".join(lines)

    def _format_terminology_for_prompt(self, outline: dict) -> str:
        terms = outline.get("terminology", {})
        if not terms:
            return "(none defined yet)"
        return "\n".join(f"- **{term}**: {defn}" for term, defn in terms.items())

    def generate_outline(self, context: dict, cheap: bool = False) -> dict:
        """Generate outline from assembled context. Returns outline dict."""
        gap = context["gap"]

        prompt = OUTLINE_PROMPT_V2.format(
            gap_topic=gap["topic"],
            gap_description=gap["description"],
            gap_category=gap.get("category", ""),
            gap_evidence=gap.get("evidence", ""),
            gap_severity=gap.get("severity", "medium"),
            convergent_ideas=self._format_ideas_for_prompt(context["convergent_ideas"]),
            rfc_foundations=self._format_rfcs_for_prompt(context["rfc_foundations"]),
            similar_drafts=self._format_similar_for_prompt(context["similar_drafts"]),
            top_rated=self._format_top_rated_for_prompt(context["top_rated"]),
            wg_context=context["wg_context"],
            ecosystem_vision=context["ecosystem_vision"],
            sibling_context=self._format_siblings_for_prompt(context["sibling_context"]),
        )

        phash = _prompt_hash("pipeline-outline-" + prompt)
        cache_key = f"_pipeline_{gap['topic']}_"

        # Check cache
        cached = self.db.get_cached_response(cache_key, phash)
        if cached:
            try:
                return json.loads(cached)
            except (json.JSONDecodeError, KeyError):
                pass

        text, in_tok, out_tok = self.analyzer._call_claude(
            prompt, max_tokens=4096, cheap=cheap
        )
        text = self.analyzer._extract_json(text)
        outline = json.loads(text)

        self.db.cache_response(
            cache_key, phash,
            self.config.claude_model_cheap if cheap else self.config.claude_model,
            prompt, text, in_tok, out_tok,
        )

        return outline

    def generate_section(self, outline: dict, section_idx: int, context: dict, cheap: bool = False) -> str:
        """Generate a single section with relevant ideas and refs."""
        sections = outline["sections"]
        section = sections[section_idx]

        outline_text = "\n".join(
            f"{i+1}. {s['title']}: {s.get('summary', '')}"
            for i, s in enumerate(sections)
        )

        # Find ideas relevant to this section
        key_ideas = section.get("key_ideas", [])
        relevant_ideas = []
        if key_ideas and context["convergent_ideas"]:
            for idea in context["convergent_ideas"]:
                for key in key_ideas:
                    if key.lower() in idea["title"].lower() or key.lower() in idea["description"].lower():
                        relevant_ideas.append(idea)
                        break
        if not relevant_ideas:
            # Use top 3 convergent ideas as fallback
            relevant_ideas = context["convergent_ideas"][:3]

        # Format RFC refs
        rfc_refs = ""
        norm_refs = outline.get("normative_refs", [])
        info_refs = outline.get("informative_refs", [])
        all_refs = norm_refs + info_refs
        if all_refs:
            rfc_refs = "\n".join(f"- {ref}" for ref in all_refs[:10])
        else:
            rfc_refs = self._format_rfcs_for_prompt(context["rfc_foundations"][:5])

        # Format cross-references to siblings
        cross_refs = self._format_siblings_for_prompt(context["sibling_context"])

        prompt = SECTION_PROMPT_V2.format(
            draft_title=outline["title"],
            abstract=outline["abstract"],
            outline_text=outline_text,
            section_num=section_idx + 1,
            section_title=section["title"],
            section_summary=section.get("summary", ""),
            relevant_ideas=self._format_ideas_for_prompt(relevant_ideas),
            rfc_refs=rfc_refs,
            cross_refs=cross_refs,
            terminology=self._format_terminology_for_prompt(outline),
        )

        phash = _prompt_hash("pipeline-section-" + prompt)
        cache_key = f"_pipeline_{outline['title']}_s{section_idx}_"

        # Check cache
        cached = self.db.get_cached_response(cache_key, phash)
        if cached:
            return cached

        text, in_tok, out_tok = self.analyzer._call_claude(
            prompt, max_tokens=2048, cheap=cheap
        )

        self.db.cache_response(
            cache_key, phash,
            self.config.claude_model_cheap if cheap else self.config.claude_model,
            prompt, text, in_tok, out_tok,
        )

        return text

    def generate_full(self, gap_topic: str, cheap: bool = False,
                      family_name: str = "", family_role: str = "") -> dict:
        """Full pipeline: context -> outline -> sections -> assemble -> store in DB."""
        console.print(f"\n[bold]Pipeline: {gap_topic}[/]")

        # Step 1: Build context
        console.print("[dim]Step 1/4:[/] Building context...")
        context = self.context_builder.build_context(gap_topic)
        console.print(
            f"  Ideas: {len(context['convergent_ideas'])}, "
            f"RFCs: {len(context['rfc_foundations'])}, "
            f"Similar: {len(context['similar_drafts'])}, "
            f"Siblings: {len(context['sibling_context'])}"
        )

        # Step 2: Generate outline
        console.print("[dim]Step 2/4:[/] Generating outline...")
        outline = self.generate_outline(context, cheap=cheap)
        console.print(f"  Title: [cyan]{outline['title']}[/]")
        console.print(f"  Sections: {len(outline['sections'])}")
        console.print(f"  Status: {outline.get('intended_status', '?')}")

        # Step 3: Generate sections
        console.print("[dim]Step 3/4:[/] Generating sections...")
        sections = []
        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            BarColumn(),
            MofNCompleteColumn(),
            console=console,
        ) as progress:
            task = progress.add_task("Writing...", total=len(outline["sections"]))
            for i, s in enumerate(outline["sections"]):
                progress.update(task, description=f"Section: {s['title'][:30]}")
                text = self.generate_section(outline, i, context, cheap=cheap)
                sections.append(text)
                progress.advance(task)

        # Step 4: Assemble and store
        console.print("[dim]Step 4/4:[/] Assembling draft...")
        full_text = DraftFormatter.format_draft(outline, sections, family_name=family_name)

        # Generate draft name from title
        words = outline["title"].lower().split()
        slug = "-".join(w for w in words[:4] if w.isalnum())
        draft_name = f"draft-ai-{slug}-00"

        data = {
            "gap_topic": gap_topic,
            "draft_name": draft_name,
            "title": outline["title"],
            "abstract": outline.get("abstract", ""),
            "outline": outline,
            "sections": sections,
            "full_text": full_text,
            "family_name": family_name,
            "family_role": family_role,
            "version": 0,
            "status": "draft",
        }

        draft_id = self.db.upsert_generated_draft(data)
        console.print(f"  Stored as generated_draft id={draft_id}, name={draft_name}")

        data["id"] = draft_id
        return data
