"""Draft-related data access functions."""
from __future__ import annotations

import json
import re
from collections import Counter, defaultdict
from pathlib import Path
from typing import TypedDict

from ietf_analyzer.db import Database
from ietf_analyzer.readiness import compute_readiness, compute_readiness_batch
from webui.data._shared import _project_root


class OverviewStats(TypedDict):
    """High-level dashboard statistics from :func:`get_overview_stats`."""
    total_drafts: int
    rated_count: int
    author_count: int
    idea_count: int
    gap_count: int
    input_tokens: int
    output_tokens: int
    false_positive_count: int

class DraftListItem(TypedDict):
    """Single draft in the paginated listing from :func:`get_drafts_page`."""
    name: str
    title: str
    date: str | None
    url: str
    pages: int
    group: str
    source: str
    score: float
    novelty: float
    maturity: float
    overlap: float
    momentum: float
    relevance: float
    categories: list[str]
    summary: str
    readiness: float

class DraftsPage(TypedDict):
    """Paginated draft listing from :func:`get_drafts_page`."""
    drafts: list[DraftListItem]
    total: int
    page: int
    per_page: int
    pages: int

def get_overview_stats(db: Database) -> OverviewStats:
    """Return high-level stats for the dashboard home page.

    Excludes drafts flagged as false positives from rated counts.
    """
    total_drafts = db.count_drafts(include_false_positives=False)
    rated_pairs = db.drafts_with_ratings(limit=1000)  # already excludes FPs
    rated_count = len(rated_pairs)
    author_count = db.author_count()
    idea_count = db.idea_count()
    gaps = db.all_gaps()
    input_tok, output_tok = db.total_tokens_used()

    # Count false positives separately for transparency
    total_all = db.count_drafts(include_false_positives=True)
    false_positive_count = total_all - total_drafts

    return {
        "total_drafts": total_drafts,
        "rated_count": rated_count,
        "author_count": author_count,
        "idea_count": idea_count,
        "gap_count": len(gaps),
        "input_tokens": input_tok,
        "output_tokens": output_tok,
        "false_positive_count": false_positive_count,
    }

def get_category_counts(db: Database) -> dict[str, int]:
    """Return {category: draft_count} for all categories."""
    return db.category_counts()

def get_category_summary(db: Database, category: str) -> dict | None:
    """Build a data-driven summary for a category. Returns None if category not found."""
    pairs = db.drafts_with_ratings(limit=2000)
    all_authors = db.top_authors(limit=500)

    # Filter to drafts in this category
    cat_pairs = [(d, r) for d, r in pairs if category in r.categories]
    if not cat_pairs:
        return None

    # Author lookup: draft_name -> [author names]
    author_drafts_map: dict[str, list[str]] = defaultdict(list)
    for name, aff, cnt, drafts, *_ in all_authors:
        for dn in drafts:
            author_drafts_map[dn].append(name)

    # Dimension averages
    n = len(cat_pairs)
    avg = lambda vals: round(sum(vals) / len(vals), 1) if vals else 0
    novelty_vals = [r.novelty for _, r in cat_pairs]
    maturity_vals = [r.maturity for _, r in cat_pairs]
    overlap_vals = [r.overlap for _, r in cat_pairs]
    momentum_vals = [r.momentum for _, r in cat_pairs]
    relevance_vals = [r.relevance for _, r in cat_pairs]
    scores = [r.composite_score for _, r in cat_pairs]

    # Top drafts
    sorted_pairs = sorted(cat_pairs, key=lambda p: p[1].composite_score, reverse=True)
    top_3 = [(d.name, d.title, round(r.composite_score, 1)) for d, r in sorted_pairs[:3]]

    # Top authors in this category
    author_counter: Counter = Counter()
    org_counter: Counter = Counter()
    author_aff: dict[str, str] = {}
    for name, aff, cnt, drafts, *_ in all_authors:
        author_aff[name] = aff or ""
    for d, r in cat_pairs:
        for a in author_drafts_map.get(d.name, []):
            author_counter[a] += 1
            if author_aff.get(a):
                org_counter[author_aff[a]] += 1
    top_authors = author_counter.most_common(5)
    top_orgs = org_counter.most_common(5)

    # Strongest and weakest dimensions
    dim_avgs = {
        "Novelty": avg(novelty_vals),
        "Maturity": avg(maturity_vals),
        "Overlap": avg(overlap_vals),
        "Momentum": avg(momentum_vals),
        "Relevance": avg(relevance_vals),
    }
    strongest = max(dim_avgs, key=dim_avgs.get)
    weakest = min(dim_avgs, key=dim_avgs.get)

    # Activity trend: how many are recent (last 6 months)?
    recent = sum(1 for d, _ in cat_pairs if d.time and d.time >= "2025-09")
    total_all = len(pairs)

    # Build text summary
    lines = []
    lines.append(f"**{n} drafts** ({n * 100 // total_all}% of all rated drafts) "
                 f"with an average composite score of **{avg(scores):.1f}/5.0**.")

    # Dimension profile
    lines.append(f"Strongest dimension: **{strongest}** ({dim_avgs[strongest]}), "
                 f"weakest: **{weakest}** ({dim_avgs[weakest]}).")

    # Maturity vs novelty insight
    if dim_avgs["Maturity"] < 2.5 and dim_avgs["Novelty"] >= 3.0:
        lines.append("This category has **high novelty but low maturity** — many early-stage proposals with fresh ideas that haven't been fully developed yet.")
    elif dim_avgs["Maturity"] >= 3.0 and dim_avgs["Novelty"] < 2.5:
        lines.append("This category is **mature but less novel** — established approaches being refined rather than introducing fundamentally new concepts.")
    elif dim_avgs["Maturity"] >= 3.0 and dim_avgs["Novelty"] >= 3.0:
        lines.append("This category shows **both high novelty and maturity** — well-developed proposals with genuinely new contributions.")

    # Overlap insight
    if dim_avgs["Overlap"] >= 3.5:
        lines.append(f"High overlap ({dim_avgs['Overlap']}) suggests **significant duplication** — multiple drafts cover similar ground, which may indicate convergence or fragmentation.")
    elif dim_avgs["Overlap"] <= 2.0:
        lines.append(f"Low overlap ({dim_avgs['Overlap']}) indicates **diverse approaches** — drafts in this category tackle distinct problems with little redundancy.")

    # Activity
    if recent > 0:
        lines.append(f"**{recent} draft{'s' if recent != 1 else ''}** submitted in the last 6 months, "
                     f"suggesting {'active' if recent >= 3 else 'moderate'} development.")

    return {
        "text": " ".join(lines),
        "count": n,
        "avg_score": avg(scores),
        "dimensions": dim_avgs,
        "top_drafts": top_3,
        "top_authors": top_authors,
        "top_orgs": top_orgs,
        "strongest": strongest,
        "weakest": weakest,
    }

def get_drafts_page(
    db: Database,
    page: int = 1,
    per_page: int = 50,
    search: str = "",
    category: str = "",
    min_score: float = 0.0,
    sort: str = "score",
    sort_dir: str = "desc",
    source: str = "",
) -> DraftsPage:
    """Return a paginated, filtered list of drafts with ratings.

    Returns dict with keys: drafts, total, page, per_page, pages.
    """
    pairs = db.drafts_with_ratings(limit=1000)

    # Build author lookup for search (draft_name -> "author1 author2 ...")
    author_text_by_draft: dict[str, str] = {}
    if search:
        rows = db.conn.execute(
            """SELECT da.draft_name, GROUP_CONCAT(a.name, ' ') as names
               FROM draft_authors da JOIN authors a ON da.person_id = a.person_id
               GROUP BY da.draft_name"""
        ).fetchall()
        for r in rows:
            author_text_by_draft[r[0]] = r[1] or ""

    # Filter
    filtered = []
    for draft, rating in pairs:
        if min_score > 0 and rating.composite_score < min_score:
            continue
        if category and category not in rating.categories:
            continue
        if source and draft.source != source:
            continue
        if search:
            author_names = author_text_by_draft.get(draft.name, "")
            haystack = f"{draft.name} {draft.title} {rating.summary} {author_names}".lower()
            if not all(w in haystack for w in search.lower().split()):
                continue
        filtered.append((draft, rating))

    # Sort
    sort_keys = {
        "score": lambda p: p[1].composite_score,
        "name": lambda p: p[0].name,
        "date": lambda p: p[0].time or "",
        "novelty": lambda p: p[1].novelty,
        "maturity": lambda p: p[1].maturity,
        "relevance": lambda p: p[1].relevance,
        "overlap": lambda p: p[1].overlap,
        "momentum": lambda p: p[1].momentum,
        "readiness": lambda p: (1.0 if p[0].name.startswith("draft-ietf-") else 0.0) * 0.25 +
                                min(int(p[0].rev or "0") / 5.0, 1.0) * 0.15 +
                                ((p[1].momentum - 1) / 4.0) * 0.15,
    }
    key_fn = sort_keys.get(sort, sort_keys["score"])
    reverse = sort_dir == "desc"
    filtered.sort(key=key_fn, reverse=reverse)

    total = len(filtered)
    pages = max(1, (total + per_page - 1) // per_page)
    page = max(1, min(page, pages))
    start = (page - 1) * per_page
    page_items = filtered[start : start + per_page]

    # Pre-compute readiness in batch (~6 queries total instead of ~200)

    readiness_cache = compute_readiness_batch(db, [d.name for d, _ in page_items])

    drafts = []
    for draft, rating in page_items:
        r_score = readiness_cache.get(draft.name, {}).get("score", 0)
        drafts.append({
            "name": draft.name,
            "title": draft.title,
            "date": draft.date,
            "url": draft.source_url if draft.source != "ietf" else draft.datatracker_url,
            "pages": draft.pages or 0,
            "group": draft.group or "individual",
            "source": draft.source or "ietf",
            "score": round(rating.composite_score, 2),
            "novelty": rating.novelty,
            "maturity": rating.maturity,
            "overlap": rating.overlap,
            "momentum": rating.momentum,
            "relevance": rating.relevance,
            "categories": rating.categories,
            "summary": rating.summary,
            "readiness": r_score,
        })

    return {
        "drafts": drafts,
        "total": total,
        "page": page,
        "per_page": per_page,
        "pages": pages,
    }

def get_draft_detail(db: Database, name: str) -> dict | None:
    """Return full detail for a single draft."""
    draft = db.get_draft(name)
    if not draft:
        return None

    rating = db.get_rating(name)
    authors = db.get_authors_for_draft(name)
    ideas = db.get_ideas_for_draft(name)
    refs = db.get_refs_for_draft(name)

    result = {
        "name": draft.name,
        "title": draft.title,
        "rev": draft.rev,
        "abstract": draft.abstract,
        "date": draft.date,
        "time": draft.time,
        "url": draft.datatracker_url,
        "text_url": draft.text_url,
        "pages": draft.pages,
        "words": draft.words,
        "group": draft.group or "individual",
        "categories": draft.categories,
        "tags": draft.tags,
        "authors": [
            {"name": a.name, "affiliation": a.affiliation, "person_id": a.person_id}
            for a in authors
        ],
        "ideas": ideas,
        "refs": [{"type": t, "id": rid} for t, rid in refs],
    }

    if rating:
        result["rating"] = {
            "score": round(rating.composite_score, 2),
            "novelty": rating.novelty,
            "maturity": rating.maturity,
            "overlap": rating.overlap,
            "momentum": rating.momentum,
            "relevance": rating.relevance,
            "summary": rating.summary,
            "novelty_note": rating.novelty_note,
            "maturity_note": rating.maturity_note,
            "overlap_note": rating.overlap_note,
            "momentum_note": rating.momentum_note,
            "relevance_note": rating.relevance_note,
            "categories": rating.categories,
        }

    # Readiness score

    result["readiness"] = compute_readiness(db, name)

    # Annotation
    annotation = db.get_annotation(name)
    result["annotation"] = annotation

    return result

def get_generated_drafts() -> list[dict]:
    """Return list of pre-generated draft files in data/reports/generated-drafts/."""
    drafts_dir = _project_root / "data" / "reports" / "generated-drafts"
    if not drafts_dir.exists():
        return []
    results = []
    for f in sorted(drafts_dir.glob("draft-*.txt")):
        # Extract title from first non-empty content line after header
        title = f.stem
        text = f.read_text(errors="replace")
        for line in text.splitlines():
            stripped = line.strip()
            if stripped and not stripped.startswith("Internet-Draft") and \
               not stripped.startswith("Intended status") and \
               not stripped.startswith("Expires:") and stripped != "":
                title = stripped
                break
        results.append({
            "filename": f.name,
            "stem": f.stem,
            "title": title,
            "size": f.stat().st_size,
            "path": str(f),
        })
    return results

def read_generated_draft(filename: str) -> str | None:
    """Read a generated draft file by filename. Returns text or None."""
    drafts_dir = _project_root / "data" / "reports" / "generated-drafts"
    path = drafts_dir / filename
    if not path.exists() or not path.is_file():
        return None
    # Safety: ensure we're not reading outside the directory
    if not str(path.resolve()).startswith(str(drafts_dir.resolve())):
        return None
    return path.read_text(errors="replace")
