"""Admin-only routes (require @admin_required)."""
from __future__ import annotations

import functools
import time
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path

from flask import Blueprint, render_template, request, jsonify, abort, g, Response, redirect, url_for

from webui.auth import admin_required
from webui.analytics import get_analytics_data
from webui.obsidian_export import build_obsidian_vault
from webui.data import (
    get_db,
    get_overview_stats,
    get_rating_distributions,
    get_all_gaps,
    get_gap_detail,
    get_drafts_for_gap,
    get_generated_drafts,
    read_generated_draft,
    get_monitor_status,
    get_landscape_tsne,
    get_similarity_graph,
    get_comparison_data,
    get_ask_search,
    get_ask_synthesize,
    get_source_comparison,
    get_false_positive_profile,
    get_citation_influence,
    get_bcp_analysis,
    get_idea_analysis,
    get_idea_detail,
    get_trends_data,
    get_complexity_data,
    get_all_proposals,
    get_proposal_detail,
    get_proposals_for_gap,
    get_all_blog_drafts,
    get_blog_draft_detail,
)

admin_bp = Blueprint("admin", __name__)

_project_root = Path(__file__).resolve().parent.parent.parent.parent

# --- Rate limiting for Claude-calling endpoints ---

_rate_limit_store: dict[str, list[float]] = defaultdict(list)
_RATE_LIMIT_MAX = 10  # max requests
_RATE_LIMIT_WINDOW = 60  # per 60 seconds


def rate_limit(f):
    """Simple in-memory rate limiter: max 10 requests per minute per IP."""
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        ip = request.remote_addr or "unknown"
        now = time.time()
        timestamps = _rate_limit_store[ip]
        _rate_limit_store[ip] = [t for t in timestamps if now - t < _RATE_LIMIT_WINDOW]
        if len(_rate_limit_store[ip]) >= _RATE_LIMIT_MAX:
            return jsonify({"error": "Rate limit exceeded. Try again later."}), 429
        _rate_limit_store[ip].append(now)
        return f(*args, **kwargs)
    return wrapper


def db():
    if "db" not in g:
        g.db = get_db()
    return g.db


# ── Gap pages ────────────────────────────────────────────────────────────

@admin_bp.route("/gaps")
@admin_required
def gaps():
    gap_list = get_all_gaps(db())
    generated = get_generated_drafts()
    return render_template("gaps.html", gaps=gap_list, generated_drafts=generated)


@admin_bp.route("/gaps/demo")
@admin_required
def gaps_demo():
    """Show a pre-generated example draft so users can see output without API calls."""
    generated = get_generated_drafts()
    selected = request.args.get("file", "")
    draft_text = None
    draft_info = None
    if selected:
        draft_text = read_generated_draft(selected)
        for gd in generated:
            if gd["filename"] == selected:
                draft_info = gd
                break
    elif generated:
        draft_info = generated[0]
        draft_text = read_generated_draft(draft_info["filename"])
    return render_template(
        "gap_demo.html",
        generated_drafts=generated,
        draft_text=draft_text,
        draft_info=draft_info,
        selected=selected,
    )


@admin_bp.route("/gaps/<int:gap_id>")
@admin_required
def gap_detail(gap_id: int):
    gap = get_gap_detail(db(), gap_id)
    if not gap:
        abort(404)
    generated = get_generated_drafts()
    gap_proposals = get_proposals_for_gap(db(), gap_id)
    related_drafts = get_drafts_for_gap(db(), gap_id)

    return render_template("gap_detail.html", gap=gap, generated_drafts=generated,
                           proposals=gap_proposals, related_drafts=related_drafts)


@admin_bp.route("/gaps/<int:gap_id>/generate", methods=["POST"])
@admin_required
def gap_generate(gap_id: int):
    """Trigger draft generation for a gap. Returns JSON with the generated text."""
    gap = get_gap_detail(db(), gap_id)
    if not gap:
        return jsonify({"error": "Gap not found"}), 404

    try:
        from ietf_analyzer.config import Config
        from ietf_analyzer.analyzer import Analyzer
        from ietf_analyzer.draftgen import DraftGenerator

        cfg = Config.load()
        database = db()
        analyzer = Analyzer(cfg, database)
        generator = DraftGenerator(cfg, database, analyzer)

        slug = gap["topic"].lower().replace(" ", "-")[:40]
        output_path = str(Path(_project_root) / "data" / "reports" / "generated-drafts" / f"draft-gap-{gap_id}-{slug}.txt")
        path = generator.generate(gap["topic"], output_path=output_path)
        draft_text = Path(path).read_text(errors="replace")

        return jsonify({
            "success": True,
            "text": draft_text,
            "filename": Path(path).name,
            "path": path,
        })
    except Exception as e:
        return jsonify({"error": str(e)}), 500


@admin_bp.route("/api/gaps")
@admin_required
def api_gaps():
    from webui.blueprints.api import _to_csv_response
    data = get_all_gaps(db())
    if request.args.get("format") == "csv":
        return _to_csv_response(data, "gaps.csv")
    return jsonify(data)


@admin_bp.route("/api/gaps/<int:gap_id>")
@admin_required
def api_gap_detail(gap_id: int):
    gap = get_gap_detail(db(), gap_id)
    if not gap:
        return jsonify({"error": "Gap not found"}), 404
    return jsonify(gap)


# ── Monitor ──────────────────────────────────────────────────────────────

@admin_bp.route("/monitor")
@admin_required
def monitor_page():
    status = get_monitor_status(db())
    return render_template("monitor.html", status=status)


@admin_bp.route("/api/monitor")
@admin_required
def api_monitor():
    data = get_monitor_status(db())
    return jsonify(data)


# ── Analytics ────────────────────────────────────────────────────────────

@admin_bp.route("/admin/analytics")
@admin_required
def analytics_dashboard():
    analytics_db = str(_project_root / "data" / "analytics.db")
    data = get_analytics_data(analytics_db)
    return render_template("analytics.html", data=data)


# ── Landscape & Similarity ───────────────────────────────────────────────

@admin_bp.route("/landscape")
@admin_required
def landscape():
    distributions = get_rating_distributions(db())
    tsne_data = get_landscape_tsne(db())
    return render_template(
        "landscape.html",
        dist=distributions,
        tsne_data=tsne_data,
    )


@admin_bp.route("/api/landscape")
@admin_required
def api_landscape():
    from webui.blueprints.api import _to_csv_response
    data = get_landscape_tsne(db())
    if request.args.get("format") == "csv":
        return _to_csv_response(data, "landscape.csv")
    return jsonify(data)


@admin_bp.route("/similarity")
@admin_required
def similarity():
    network = get_similarity_graph(db())
    return render_template("similarity.html", network=network)


@admin_bp.route("/api/similarity")
@admin_required
def api_similarity():
    data = get_similarity_graph(db())
    return jsonify(data)


# ── Compare ──────────────────────────────────────────────────────────────

@admin_bp.route("/compare")
@admin_required
def compare_page():
    draft_names = request.args.get("drafts", "")
    names = [n.strip() for n in draft_names.split(",") if n.strip()] if draft_names else []
    data = None
    if len(names) >= 2:
        data = get_comparison_data(db(), names)
    return render_template("comparison.html", names=names, data=data)


@admin_bp.route("/api/compare", methods=["POST"])
@admin_required
@rate_limit
def api_compare():
    """Run Claude comparison for drafts. Returns JSON with comparison text."""
    req_data = request.get_json(force=True, silent=True)
    if not req_data or "drafts" not in req_data:
        return jsonify({"error": "Missing 'drafts' in request body"}), 400

    names = req_data["drafts"]
    if len(names) < 2:
        return jsonify({"error": "Need at least 2 drafts to compare"}), 400

    try:
        from ietf_analyzer.config import Config
        from ietf_analyzer.analyzer import Analyzer

        cfg = Config.load()
        database = db()
        analyzer = Analyzer(cfg, database)
        result = analyzer.compare_drafts(names)
        return jsonify(result)
    except Exception as e:
        return jsonify({"error": str(e)}), 500


# ── Annotations ──────────────────────────────────────────────────────────

@admin_bp.route("/api/drafts/<string:name>/annotate", methods=["POST"])
@admin_required
def api_annotate(name: str):
    """Add or update annotation for a draft."""
    import json as _json
    database = db()
    draft = database.get_draft(name)
    if not draft:
        return jsonify({"error": "Draft not found"}), 404

    data = request.get_json(force=True, silent=True)
    if not data:
        return jsonify({"error": "Invalid JSON body"}), 400

    note = data.get("note")
    tags = data.get("tags")
    add_tag = data.get("add_tag")
    remove_tag = data.get("remove_tag")

    if add_tag or remove_tag:
        existing = database.get_annotation(name)
        current_tags = existing["tags"] if existing else []
        if add_tag and add_tag not in current_tags:
            current_tags.append(add_tag)
        if remove_tag and remove_tag in current_tags:
            current_tags.remove(remove_tag)
        tags = current_tags

    database.upsert_annotation(name, note=note, tags=tags)
    annotation = database.get_annotation(name)
    return jsonify({"success": True, "annotation": annotation})


# ── Ask/Synthesize (Claude-powered) ──────────────────────────────────────

@admin_bp.route("/api/ask/synthesize", methods=["POST"])
@admin_required
@rate_limit
def api_ask_synthesize():
    """Synthesize an answer via Claude (costs tokens, cached permanently). Returns JSON."""
    data = request.get_json(force=True, silent=True)
    if not data or "question" not in data:
        return jsonify({"error": "Missing 'question' in request body"}), 400
    question = data["question"]
    top_k = data.get("top_k", 5)
    result = get_ask_synthesize(db(), question, top_k=top_k, cheap=True)
    return jsonify(result)


# ── Sources & False Positives ────────────────────────────────────────────

@admin_bp.route("/sources")
@admin_required
def sources_page():
    data = get_source_comparison(db())
    return render_template("sources.html", data=data)


@admin_bp.route("/false-positives")
@admin_required
def false_positives_page():
    data = get_false_positive_profile(db())
    return render_template("false_positives.html", data=data)


@admin_bp.route("/api/sources")
@admin_required
def api_sources():
    data = get_source_comparison(db())
    return jsonify(data)


@admin_bp.route("/api/false-positives")
@admin_required
def api_false_positives():
    data = get_false_positive_profile(db())
    return jsonify(data)


# ── Citation Influence & BCP ─────────────────────────────────────────────

@admin_bp.route("/api/citations/influence")
@admin_required
def api_citation_influence():
    return jsonify(get_citation_influence(db()))


@admin_bp.route("/api/citations/bcp")
@admin_required
def api_bcp_analysis():
    return jsonify(get_bcp_analysis(db()))


# ── Idea Analysis ────────────────────────────────────────────────────────

@admin_bp.route("/idea-analysis")
@admin_required
def idea_analysis():
    data = get_idea_analysis(db())
    return render_template("idea_analysis.html", data=data)


@admin_bp.route("/api/idea-analysis")
@admin_required
def api_idea_analysis():
    data = get_idea_analysis(db())
    return jsonify(data)


# ── Idea Detail ──────────────────────────────────────────────────────────

@admin_bp.route("/ideas/<int:idea_id>")
@admin_required
def idea_detail(idea_id: int):
    idea = get_idea_detail(db(), idea_id)
    if not idea:
        abort(404)
    return render_template("idea_detail.html", idea=idea)


# ── Trends & Complexity ──────────────────────────────────────────────────

@admin_bp.route("/trends")
@admin_required
def trends():
    data = get_trends_data(db())
    return render_template("trends_analysis.html", data=data)


@admin_bp.route("/complexity")
@admin_required
def complexity():
    data = get_complexity_data(db())
    return render_template("complexity.html", data=data)


@admin_bp.route("/api/trends")
@admin_required
def api_trends():
    return jsonify(get_trends_data(db()))


@admin_bp.route("/api/complexity")
@admin_required
def api_complexity():
    return jsonify(get_complexity_data(db()))


# ── Proposals ────────────────────────────────────────────────────────────

@admin_bp.route("/proposals")
@admin_required
def proposals():
    proposal_list = get_all_proposals(db())
    gap_list = get_all_gaps(db())
    return render_template("proposals.html", proposals=proposal_list, gaps=gap_list)


@admin_bp.route("/proposals/new", methods=["GET", "POST"])
@admin_required
def proposal_new():
    if request.method == "POST":
        data = request.form
        slug = data.get("slug", "").strip()
        if not slug:
            import re
            slug = re.sub(r'[^a-z0-9]+', '-', data["title"].lower()).strip('-')
        gap_ids = [int(g_val) for g_val in request.form.getlist("gap_ids") if g_val]
        proposal = {
            "title": data["title"],
            "slug": slug,
            "status": data.get("status", "idea"),
            "description": data.get("description", ""),
            "content_md": data.get("content_md", ""),
            "source_paper": data.get("source_paper", ""),
            "source_url": data.get("source_url", ""),
            "intended_wg": data.get("intended_wg", ""),
            "draft_name": data.get("draft_name", ""),
            "gap_ids": gap_ids,
        }
        pid = db().upsert_proposal(proposal)
        return redirect(url_for("admin.proposal_detail", proposal_id=pid))
    gap_list = get_all_gaps(db())
    return render_template("proposal_edit.html", proposal=None, gaps=gap_list)


@admin_bp.route("/proposals/<int:proposal_id>")
@admin_required
def proposal_detail(proposal_id):
    proposal = get_proposal_detail(db(), proposal_id)
    if not proposal:
        abort(404)
    return render_template("proposal_detail.html", proposal=proposal)


@admin_bp.route("/proposals/<int:proposal_id>/edit", methods=["GET", "POST"])
@admin_required
def proposal_edit(proposal_id):
    if request.method == "POST":
        data = request.form
        slug = data.get("slug", "").strip()
        if not slug:
            import re
            slug = re.sub(r'[^a-z0-9]+', '-', data["title"].lower()).strip('-')
        gap_ids = [int(g_val) for g_val in request.form.getlist("gap_ids") if g_val]
        proposal = {
            "id": proposal_id,
            "title": data["title"],
            "slug": slug,
            "status": data.get("status", "idea"),
            "description": data.get("description", ""),
            "content_md": data.get("content_md", ""),
            "source_paper": data.get("source_paper", ""),
            "source_url": data.get("source_url", ""),
            "intended_wg": data.get("intended_wg", ""),
            "draft_name": data.get("draft_name", ""),
            "gap_ids": gap_ids,
        }
        db().upsert_proposal(proposal)
        return redirect(url_for("admin.proposal_detail", proposal_id=proposal_id))
    proposal = get_proposal_detail(db(), proposal_id)
    if not proposal:
        abort(404)
    gap_list = get_all_gaps(db())
    return render_template("proposal_edit.html", proposal=proposal, gaps=gap_list)


@admin_bp.route("/proposals/<int:proposal_id>/delete", methods=["POST"])
@admin_required
def proposal_delete(proposal_id):
    db().delete_proposal(proposal_id)
    return redirect(url_for("admin.proposals"))


@admin_bp.route("/proposals/<int:proposal_id>/generate", methods=["POST"])
@admin_required
def proposal_generate(proposal_id):
    """Generate an Internet-Draft from a proposal and its linked gaps."""
    proposal = get_proposal_detail(db(), proposal_id)
    if not proposal:
        return jsonify({"error": "Proposal not found"}), 404

    try:
        from ietf_analyzer.config import Config
        from ietf_analyzer.analyzer import Analyzer
        from ietf_analyzer.draftgen import DraftGenerator

        cfg = Config.load()
        database = db()
        analyzer = Analyzer(cfg, database)
        generator = DraftGenerator(cfg, database, analyzer)

        # Build rich topic from proposal + linked gaps
        topic = proposal["title"]
        gap_context = ""
        if proposal.get("gaps"):
            gap_topics = [g["topic"] for g in proposal["gaps"]]
            gap_context = "\n\nThis proposal addresses these gaps:\n" + "\n".join(
                f"- {g['topic']}: {g['description'][:200]}" for g in proposal["gaps"]
            )
        if proposal.get("description"):
            gap_context += f"\n\nProposal description: {proposal['description']}"

        slug = proposal.get("slug", "proposal")[:40]
        output_path = str(
            Path(_project_root) / "data" / "reports" / "generated-drafts"
            / f"draft-proposal-{proposal_id}-{slug}.txt"
        )
        path = generator.generate(topic + gap_context, output_path=output_path)
        draft_text = Path(path).read_text(errors="replace")

        return jsonify({
            "success": True,
            "text": draft_text,
            "filename": Path(path).name,
            "path": path,
        })
    except Exception as e:
        return jsonify({"error": str(e)}), 500


@admin_bp.route("/api/proposals")
@admin_required
def api_proposals():
    data = get_all_proposals(db())
    return jsonify(data)


@admin_bp.route("/api/proposals/<int:proposal_id>")
@admin_required
def api_proposal_detail(proposal_id):
    p = get_proposal_detail(db(), proposal_id)
    if not p:
        return jsonify({"error": "Proposal not found"}), 404
    return jsonify(p)


@admin_bp.route("/proposals/intake", methods=["GET", "POST"])
@admin_required
def proposal_intake():
    """Paste text/URLs -> Claude generates proposals automatically."""
    if request.method == "POST":
        raw_input = request.form.get("input_text", "").strip()
        if not raw_input:
            return jsonify({"error": "No input provided"}), 400

        try:
            from ietf_analyzer.config import Config
            from ietf_analyzer.proposal_intake import ProposalIntake

            cfg = Config.load()
            intake = ProposalIntake(cfg, db())
            proposals_result, usage = intake.process(raw_input, cheap=True)

            return jsonify({
                "success": True,
                "count": len(proposals_result),
                "proposals": [
                    {"id": p.get("id"), "title": p.get("title"), "slug": p.get("slug"),
                     "gap_ids": p.get("gap_ids", []), "description": p.get("description", ""),
                     "content_md": p.get("content_md", ""),
                     "intended_wg": p.get("intended_wg", ""), "draft_name": p.get("draft_name", ""),
                     "source_paper": p.get("source_paper", ""), "source_url": p.get("source_url", "")}
                    for p in proposals_result
                ],
                "usage": usage,
            })
        except Exception as e:
            return jsonify({"error": str(e)}), 500

    return render_template("proposal_intake.html")


# ── Blog Drafts (dev-only) ──────────────────────────────────────────────

@admin_bp.route("/blog")
@admin_required
def blog_drafts():
    drafts = get_all_blog_drafts(db())
    return render_template("blog_drafts.html", drafts=drafts)


@admin_bp.route("/blog/generate", methods=["GET", "POST"])
@admin_required
def blog_generate():
    """Generate a blog post from project data."""
    if request.method == "POST":
        topic = request.form.get("topic", "").strip()
        style = request.form.get("style", "deep-dive")
        instructions = request.form.get("instructions", "").strip()
        cheap = request.form.get("cheap") == "on"

        if not topic:
            return jsonify({"error": "No topic provided"}), 400

        try:
            from ietf_analyzer.config import Config
            from ietf_analyzer.blog_drafts import BlogDraftGenerator

            cfg = Config.load()
            gen = BlogDraftGenerator(cfg, db())
            result = gen.generate(topic=topic, style=style,
                                  instructions=instructions, cheap=cheap)

            # Auto-save to DB
            import re
            slug = re.sub(r'[^a-z0-9]+', '-', result["title"].lower()).strip('-')[:80]
            # Ensure unique slug
            existing = db().conn.execute(
                "SELECT count(*) FROM blog_drafts WHERE slug = ?", (slug,)
            ).fetchone()[0]
            if existing:
                slug = f"{slug}-{int(datetime.now(timezone.utc).timestamp()) % 10000}"

            draft_id = db().upsert_blog_draft({
                "title": result["title"],
                "subtitle": result.get("subtitle", ""),
                "slug": slug,
                "status": "draft",
                "style": style,
                "tags": result.get("tags", []),
                "topic": topic,
                "instructions": instructions,
                "content_md": result["full_markdown"],
                "model_used": result["usage"]["model"],
                "input_tokens": result["usage"]["input_tokens"],
                "output_tokens": result["usage"]["output_tokens"],
                "cost_usd": result["usage"]["cost_usd"],
            })

            return jsonify({
                "success": True,
                "id": draft_id,
                "title": result["title"],
                "subtitle": result.get("subtitle", ""),
                "content": result["full_markdown"],
                "tags": result.get("tags", []),
                "usage": result["usage"],
            })
        except Exception as e:
            import traceback
            traceback.print_exc()
            return jsonify({"error": str(e)}), 500

    return render_template("blog_generate.html")


@admin_bp.route("/blog/<int:draft_id>")
@admin_required
def blog_detail(draft_id):
    draft = get_blog_draft_detail(db(), draft_id)
    if not draft:
        abort(404)
    return render_template("blog_detail.html", draft=draft)


@admin_bp.route("/blog/<int:draft_id>/edit", methods=["GET", "POST"])
@admin_required
def blog_edit(draft_id):
    if request.method == "POST":
        data = request.form
        import re
        slug = data.get("slug", "").strip()
        if not slug:
            slug = re.sub(r'[^a-z0-9]+', '-', data["title"].lower()).strip('-')[:80]
        tags = [t.strip() for t in data.get("tags_str", "").split(",") if t.strip()]
        db().upsert_blog_draft({
            "id": draft_id,
            "title": data["title"],
            "subtitle": data.get("subtitle", ""),
            "slug": slug,
            "status": data.get("status", "draft"),
            "style": data.get("style", "deep-dive"),
            "tags": tags,
            "topic": data.get("topic", ""),
            "instructions": data.get("instructions", ""),
            "content_md": data.get("content_md", ""),
            "model_used": data.get("model_used", ""),
            "input_tokens": int(data.get("input_tokens", 0) or 0),
            "output_tokens": int(data.get("output_tokens", 0) or 0),
            "cost_usd": float(data.get("cost_usd", 0) or 0),
        })
        return redirect(url_for("admin.blog_detail", draft_id=draft_id))

    draft = get_blog_draft_detail(db(), draft_id)
    if not draft:
        abort(404)
    return render_template("blog_edit.html", draft=draft)


@admin_bp.route("/blog/<int:draft_id>/delete", methods=["POST"])
@admin_required
def blog_delete(draft_id):
    db().delete_blog_draft(draft_id)
    return redirect(url_for("admin.blog_drafts"))


@admin_bp.route("/blog/<int:draft_id>/export")
@admin_required
def blog_export(draft_id):
    """Download a blog draft as markdown file."""
    draft = get_blog_draft_detail(db(), draft_id)
    if not draft:
        abort(404)
    filename = f"{draft['slug']}.md"
    return Response(
        draft["content_md"],
        mimetype="text/markdown",
        headers={"Content-Disposition": f"attachment; filename={filename}"},
    )


# ── Obsidian Export ──────────────────────────────────────────────────────

@admin_bp.route("/export/obsidian")
@admin_required
def export_obsidian():
    """Download the entire research corpus as an Obsidian vault (ZIP)."""
    data = build_obsidian_vault(db())
    return Response(
        data,
        mimetype="application/zip",
        headers={"Content-Disposition": "attachment; filename=IETF-AI-Agent-Drafts.zip"},
    )
