"""
Modulare Pipeline-Bausteine fuers Studio.

Fuehrt einzelne Pipeline-Stufen ISOLIERT auf dem vorhandenen DB-Bestand aus,
statt des durchlaufenden orchestrator._run_refresh. Das grosse _run_refresh
bleibt unangetastet (dient weiter als "kompletter Lauf").

Nutzt die bereits reinen Agenten (AnalyzerAgent/FactCheckerAgent) und bildet nur
die Lade-/Persistenz-Logik aus _run_refresh nach.

Verhalten: KOMPLETT NEU (ersetzt das aktive Ergebnis), Historie bleibt einsehbar:
  - Analyse     -> altes Lagebild wird als incident_snapshots archiviert, dann neu gesetzt
  - Faktencheck -> alter Faktenstand wird als fact_check_runs archiviert, dann ersetzt

Phase 1: analyze, factcheck. (Spaeter: collect, geoparse, network.)
"""
from __future__ import annotations

import asyncio
import json
import logging
from datetime import datetime

from config import TIMEZONE
from database import get_db

logger = logging.getLogger("osint.stages")

# Im Studio einzeln startbare Bausteine (Phase 1)
STAGES = {"analyze", "factcheck"}

# Status-Registry je Incident (Single-Flight) + Task-Referenzen (GC-Schutz)
_STATE: dict[int, dict] = {}
_TASKS: set = set()


def _now() -> str:
    return datetime.now(TIMEZONE).strftime("%Y-%m-%d %H:%M:%S")


def get_state(incident_id: int) -> dict | None:
    return _STATE.get(incident_id)


def is_running(incident_id: int) -> bool:
    st = _STATE.get(incident_id)
    return bool(st and st.get("status") == "running")


async def _load_incident(db, incident_id: int) -> dict | None:
    cur = await db.execute("SELECT * FROM incidents WHERE id = ?", (incident_id,))
    row = await cur.fetchone()
    return dict(row) if row else None


async def _output_language(db, tenant_id) -> str:
    from services.org_settings import get_org_language, language_display
    iso = await get_org_language(db, tenant_id) if tenant_id else "de"
    return language_display(iso)


async def _count(db, sql: str, params) -> int:
    row = await (await db.execute(sql, params)).fetchone()
    return (row[0] if row else 0) or 0


# ---------------------------------------------------------------------------
# Baustein: Analyse (Lagebild / Recherchebericht) — komplett neu
# ---------------------------------------------------------------------------
async def _run_analysis(db, inc: dict, user_id) -> dict:
    from agents.analyzer import AnalyzerAgent, build_fact_context_block
    from services.license_service import charge_usage_to_tenant

    incident_id = inc["id"]
    tenant_id = inc.get("tenant_id")
    incident_type = inc.get("type") or "adhoc"
    title = inc.get("title") or ""
    description = inc.get("description") or ""
    prev_summary = inc.get("summary") or ""
    prev_sources = inc.get("sources_json")
    now = _now()
    output_language = await _output_language(db, tenant_id)

    # 1) Altes Lagebild als Snapshot archivieren (Historie), bevor es ersetzt wird
    if prev_summary:
        acnt = await _count(db, "SELECT COUNT(*) FROM articles WHERE incident_id = ?", (incident_id,))
        fcnt = await _count(db, "SELECT COUNT(*) FROM fact_checks WHERE incident_id = ?", (incident_id,))
        await db.execute(
            """INSERT INTO incident_snapshots
               (incident_id, summary, sources_json, article_count, fact_check_count,
                refresh_log_id, created_at, tenant_id)
               VALUES (?,?,?,?,?,?,?,?)""",
            (incident_id, prev_summary, prev_sources, acnt, fcnt, None, now, tenant_id),
        )
        await db.commit()

    # 2) Alle Artikel + bestehende Fakten laden (Frische-Bias bei adhoc)
    order = "published_at IS NULL, published_at DESC" if incident_type == "adhoc" else "collected_at DESC"
    arts = [dict(r) for r in await (await db.execute(
        f"SELECT * FROM articles WHERE incident_id = ? ORDER BY {order}", (incident_id,)
    )).fetchall()]
    if not arts:
        raise ValueError("Keine Artikel vorhanden — bitte zuerst sammeln.")

    facts = [dict(r) for r in await (await db.execute(
        "SELECT id, claim, status, sources_count, evidence FROM fact_checks WHERE incident_id = ?",
        (incident_id,),
    )).fetchall()]
    fact_ctx = ""
    try:
        fact_ctx = build_fact_context_block(facts, [], incident_type)
    except Exception as e:
        logger.warning(f"Faktenkontext fuer Analyse fehlgeschlagen: {e}")

    # 3) Analyse komplett neu ueber ALLE Artikel
    analyzer = AnalyzerAgent()
    analysis, usage = await analyzer.analyze(
        title, description, arts, incident_type,
        fact_context_block=fact_ctx, output_language=output_language,
    )
    if usage:
        try:
            await charge_usage_to_tenant(db, tenant_id, usage, source="analysis")
        except Exception:
            pass
    if not analysis or not (analysis.get("summary") or "").strip():
        raise ValueError("Analyse lieferte kein Lagebild.")

    summary = analysis.get("summary") or ""
    sources = analysis.get("sources") or []
    for s in sources:
        if isinstance(s.get("nr"), str):
            try:
                s["nr"] = int(s["nr"])
            except ValueError:
                pass
    sources_json = json.dumps(sources, ensure_ascii=False) if sources else prev_sources

    # summary_at = Entstehungszeit des Lagebilds. JETZT stempeln, nicht 'now' vom Beginn
    # des Bausteins: die Analyse laeuft Minuten, und der Stempel muss den Stand abdecken,
    # der tatsaechlich verarbeitet wurde. (updated_at wird auch beim Sammeln gesetzt und
    # taugt als Bericht-Zeitpunkt ohnehin nicht.)
    summary_now = _now()
    await db.execute(
        "UPDATE incidents SET summary = ?, sources_json = ?, executive_summary = NULL, "
        "updated_at = ?, summary_at = ? WHERE id = ?",
        (summary, sources_json, summary_now, summary_now, incident_id),
    )
    await db.commit()
    return {"articles": len(arts), "summary_len": len(summary), "sources": len(sources)}


# ---------------------------------------------------------------------------
# Baustein: Faktencheck — komplett neu (alter Stand als Lauf archiviert)
# ---------------------------------------------------------------------------
async def _run_factcheck(db, inc: dict, user_id) -> dict:
    from agents.factchecker import FactCheckerAgent, deduplicate_new_facts
    from services.license_service import charge_usage_to_tenant

    incident_id = inc["id"]
    tenant_id = inc.get("tenant_id")
    incident_type = inc.get("type") or "adhoc"
    title = inc.get("title") or ""
    now = _now()
    output_language = await _output_language(db, tenant_id)

    # 1) Aktuellen Faktenstand als Lauf archivieren (Historie)
    cur_facts = [dict(r) for r in await (await db.execute(
        "SELECT claim, status, sources_count, evidence, is_notification, checked_at, status_history "
        "FROM fact_checks WHERE incident_id = ? ORDER BY id", (incident_id,)
    )).fetchall()]
    if cur_facts:
        await db.execute(
            "INSERT INTO fact_check_runs (incident_id, tenant_id, created_at, facts_json, fact_count) VALUES (?,?,?,?,?)",
            (incident_id, tenant_id, now, json.dumps(cur_facts, ensure_ascii=False), len(cur_facts)),
        )
        await db.commit()

    # 2) Alle Artikel laden
    arts = [dict(r) for r in await (await db.execute(
        "SELECT * FROM articles WHERE incident_id = ? ORDER BY collected_at DESC", (incident_id,)
    )).fetchall()]
    if not arts:
        raise ValueError("Keine Artikel vorhanden — bitte zuerst sammeln.")

    # 3) Faktencheck komplett neu
    fc = FactCheckerAgent()
    facts, usage = await fc.check(title, arts, incident_type, output_language=output_language)
    if usage:
        try:
            await charge_usage_to_tenant(db, tenant_id, usage, source="factcheck")
        except Exception:
            pass
    facts = deduplicate_new_facts(facts or [])

    # 4) Bestehende Fakten ersetzen
    await db.execute("DELETE FROM fact_checks WHERE incident_id = ?", (incident_id,))
    for f in facts:
        init_hist = json.dumps([{"status": f.get("status", "developing"), "at": now}])
        await db.execute(
            """INSERT INTO fact_checks
               (incident_id, claim, status, sources_count, evidence, is_notification, tenant_id, status_history, checked_at)
               VALUES (?,?,?,?,?,?,?,?,?)""",
            (incident_id, f.get("claim", ""), f.get("status", "developing"),
             f.get("sources_count", 0), f.get("evidence"), f.get("is_notification", 0),
             tenant_id, init_hist, now),
        )
    await db.commit()
    return {"facts": len(facts), "archived": len(cur_facts), "articles": len(arts)}


# ---------------------------------------------------------------------------
# Orchestrierung: Start (Single-Flight) + Status
# ---------------------------------------------------------------------------
_RUNNERS = {"analyze": _run_analysis, "factcheck": _run_factcheck}

_LABELS = {"analyze": "Analyse", "factcheck": "Faktencheck"}


def start_stage(incident_id: int, stage: str, user_id) -> bool:
    """Startet einen Baustein im Hintergrund. False, wenn schon einer laeuft."""
    if stage not in STAGES:
        raise ValueError(f"Unbekannter Baustein: {stage}")
    if is_running(incident_id):
        return False
    _STATE[incident_id] = {
        "stage": stage, "label": _LABELS.get(stage, stage),
        "status": "running", "started_at": _now(),
        "finished_at": None, "error": None, "result": None,
    }
    t = asyncio.create_task(_execute(incident_id, stage, user_id))
    _TASKS.add(t)
    t.add_done_callback(_TASKS.discard)
    return True


async def _execute(incident_id: int, stage: str, user_id):
    db = await get_db()
    started_at = _STATE.get(incident_id, {}).get("started_at")
    try:
        inc = await _load_incident(db, incident_id)
        if not inc:
            raise ValueError("Lage nicht gefunden")
        res = await _RUNNERS[stage](db, inc, user_id)
        _STATE[incident_id] = {
            "stage": stage, "label": _LABELS.get(stage, stage), "status": "done",
            "started_at": started_at, "finished_at": _now(), "error": None, "result": res,
        }
        logger.info(f"Baustein {stage} Lage {incident_id} fertig: {res}")
    except Exception as e:
        logger.warning(f"Baustein {stage} Lage {incident_id} Fehler: {e}", exc_info=True)
        _STATE[incident_id] = {
            "stage": stage, "label": _LABELS.get(stage, stage), "status": "error",
            "started_at": started_at, "finished_at": _now(), "error": str(e)[:400], "result": None,
        }
    finally:
        await db.close()


def start_job(incident_id: int, label: str, coro_factory, user_id=None) -> bool:
    """Startet einen beliebigen Hintergrund-Job unter derselben Single-Flight-
    Registry wie die Bausteine (z.B. fokussierte Folge-Recherche). coro_factory()
    liefert ein awaitable mit dict-Ergebnis. So zeigt die run-status-Abfrage den
    Job an und andere Bausteine sind waehrenddessen blockiert."""
    if is_running(incident_id):
        return False
    _STATE[incident_id] = {
        "stage": "research", "label": label,
        "status": "running", "started_at": _now(),
        "finished_at": None, "error": None, "result": None,
    }

    async def _run():
        started_at = _STATE.get(incident_id, {}).get("started_at")
        try:
            res = await coro_factory()
            _STATE[incident_id] = {
                "stage": "research", "label": label, "status": "done",
                "started_at": started_at, "finished_at": _now(), "error": None, "result": res,
            }
            logger.info(f"Job '{label}' Lage {incident_id} fertig: {res}")
        except Exception as e:
            logger.warning(f"Job '{label}' Lage {incident_id} Fehler: {e}", exc_info=True)
            _STATE[incident_id] = {
                "stage": "research", "label": label, "status": "error",
                "started_at": started_at, "finished_at": _now(), "error": str(e)[:400], "result": None,
            }

    t = asyncio.create_task(_run())
    _TASKS.add(t)
    t.add_done_callback(_TASKS.discard)
    return True
