import asyncio
import shutil
import uuid
from datetime import datetime, timezone

from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse, Response

from agents import provider_available
from config import PROJECTS_DIR, UNI_DIR, PROVIDERS
from database import (
    create_guide, delete_guide, get_guide, list_guides,
    create_topic, list_topics as db_list_topics, delete_topic,
    list_progress, set_progress, delete_progress,
    create_element, list_elements, get_element, update_element, delete_element,
    list_baustein_progress, get_baustein_progress, set_offene_frage,
    set_baustein_score, set_baustein_absolviert, set_baustein_verstanden, set_baustein_gemeistert, delete_baustein_daten,
    delete_topic_pipeline, delete_quelle, get_guide_content, delete_guide_content,
)
from bausteine import generate_bausteine, cancel_bausteine, bausteine_status, active_bausteine, reset_bausteine, lade_quelle, lade_uebersicht, subbausteine_titel, lade_frage_muster
from elements import generate_element, chat_with_guide, chat_with_element, check_element, style_element, refine_suggestion
from lernen import NOETIG, MASTERY, MEISTERN, NIVEAUS, baustein_chat, baustein_diskussion, baustein_element_anlegen, pruefung_bewertung, pruefung_bewertung_schnell, pruefung_frage, pruefung_frage_variante, quiz_generieren, lueckwahl_generieren, lueckentext_generieren, lueckentext_pruefen, score_berechnen, floor_aus_meilensteinen, _zufall_malus
from guide import generate_guide, guide_slot_dateien, guide_fertig_step
from pipeline import cancel_guide
from regeln import FORMATE, formate_stats, guide_lock, ist_absolviert, lade_lernstand, thema_abgeschlossen
from models import (
    GuideCreateRequest, GuideResponse,
    TopicCreateRequest,
    BausteineCreateRequest, BausteineStatusResponse,
    GuideChatRequest, GuideChatResponse,
    ElementCreateRequest, ElementChatRequest, ElementChatResponse, ElementResponse,
    ElementUpdateRequest, ElementCheckRequest, ElementCheckResponse, ElementStyleResponse,
    ElementRefineRequest, ElementRefineResponse,
    ProgressUpdate, ProgressResponse, ProjectResponse, ProviderInfo,
    FolderResponse, BausteineQuelleUpdate, BausteineQuelleResponse, BausteinUebersicht,
    BausteinChatRequest, BausteinChatResponse,
    BausteinPruefungRequest, BausteinPruefungResponse, BausteinLernstandResponse,
)
from paths import bausteine_topics, guide_content_path, project_dir, topic_dir, quelle_path, safe_ordner
from fsutil import atomic_write_json

router = APIRouter(prefix="/api")


@router.get("/providers", response_model=list[ProviderInfo])
async def get_providers():
    return [{"id": pid, "available": provider_available(pid)} for pid in PROVIDERS]


@router.get("/topics")
async def get_topics():
    db_topics = await db_list_topics()
    guides = await list_guides()
    derived = {g["topic"] for g in guides}
    derived.update(bausteine_topics())
    derived.update(job["topic"] for job in active_bausteine())
    # DB ist führend (Reihenfolge: neueste zuerst); Abgeleitetes ohne DB-Eintrag hinten anhängen
    return db_topics + sorted(derived - set(db_topics))


@router.get("/stats")
async def get_stats():
    """Tracker: Themen-Anzahl + pro Format erstellt/absolviert."""
    guides, progress, levels = await lade_lernstand()
    themen = set(await db_list_topics()) | {g["topic"] for g in guides} | set(bausteine_topics())
    if PROJECTS_DIR.is_dir():
        themen |= {e.name for e in PROJECTS_DIR.iterdir() if e.is_dir()}
    return {"themen": len(themen), "formate": formate_stats(guides, progress, levels)}


@router.get("/topics/fortschritt")
async def topic_fortschritt(topic: str):
    """Absolviert-Status pro Format + Themen-Abschluss — fürs Freischalten der nächsten Ausbaustufe."""
    guides, progress, levels = await lade_lernstand()
    status = {fmt: ist_absolviert(topic, fmt, guides, progress, levels) for fmt in FORMATE}
    status["abgeschlossen"] = thema_abgeschlossen(topic, guides, progress, levels)
    return status


@router.post("/topics")
async def add_topic(req: TopicCreateRequest):
    await create_topic(req.name.strip())
    return {"ok": True}


@router.delete("/topics")
async def remove_topic(topic: str):
    await delete_topic(topic)
    await delete_baustein_daten(topic)
    await delete_topic_pipeline(topic)
    await delete_quelle(topic)          # Themen-Config (DB) — beim Thema-Löschen mit weg
    await delete_guide_content(topic)
    shutil.rmtree(topic_dir(topic), ignore_errors=True)
    return {"ok": True}


def _safe_project_name(name: str) -> str:
    if not name or "/" in name or "\\" in name or ".." in name or "\x00" in name:
        raise HTTPException(400, "Ungültiger Projektname")
    return name


@router.get("/projects", response_model=list[ProjectResponse])
async def list_projects():
    if not PROJECTS_DIR.is_dir():
        return []
    return [{"name": entry.name} for entry in sorted(PROJECTS_DIR.iterdir()) if entry.is_dir()]


@router.delete("/projects/{name}")
async def remove_project(name: str):
    _safe_project_name(name)
    pdir = project_dir(name)
    if not pdir.is_dir():
        raise HTTPException(404, "Projekt nicht gefunden")
    shutil.rmtree(pdir)
    return {"ok": True}


@router.get("/folders", response_model=list[FolderResponse])
async def list_folders(kind: str):
    """Ordner für die Quellen-Auswahl: kind=projekt → projects/, kind=uni → uni/."""
    base = {"projekt": (PROJECTS_DIR, "projects"), "uni": (UNI_DIR, "uni")}.get(kind)
    if base is None:
        raise HTTPException(400, "kind muss 'projekt' oder 'uni' sein")
    root, prefix = base
    if not root.is_dir():
        return []
    return [{"name": e.name, "ort": f"{prefix}/{e.name}"} for e in sorted(root.iterdir()) if e.is_dir()]


# --- Bausteine ---

@router.get("/bausteine/status", response_model=BausteineStatusResponse)
async def get_bausteine_status(topic: str):
    return bausteine_status(topic)


@router.get("/bausteine/active")
async def get_active_bausteine():
    return active_bausteine()


@router.post("/bausteine")
async def create_bausteine(req: BausteineCreateRequest):
    topic = req.topic.strip()
    if bausteine_status(topic)["generating"]:
        return {"ok": True, "status": "already_generating"}
    await create_topic(topic)
    qp = quelle_path(topic)
    # Quelle nur beim ERSTEN Mal festschreiben; ▶/Resume erhält die bestehende Wahl.
    if not qp.exists():
        typ, ort = req.source_type, req.source_ort.strip()
        if typ in ("projekt", "uni"):
            ordner = safe_ordner(ort)
            if ordner is None or not ordner.is_dir():
                raise HTTPException(400, "Ordner ungültig oder nicht gefunden (Pfad relativ zum Projekt-Root, kein ../).")
        elif typ == "link":
            if not ort.lower().startswith(("http://", "https://")):
                raise HTTPException(400, "Link muss mit http:// oder https:// beginnen.")
        qp.parent.mkdir(parents=True, exist_ok=True)
        atomic_write_json(qp, {"type": typ, "ort": ort, "spec": req.instructions.strip()})
    asyncio.create_task(generate_bausteine(topic, req.instructions.strip(), req.provider, ab_phase=req.ab_phase))
    return {"ok": True}


@router.post("/bausteine/cancel")
async def cancel_bausteine_route(topic: str):
    if not cancel_bausteine(topic):
        raise HTTPException(404, "Keine laufende Generierung")
    return {"ok": True}


@router.delete("/bausteine")
async def remove_bausteine(topic: str):
    reset_bausteine(topic)              # Dateien: Crawl + Sichtung + Inventar…Fragen weg; quelle.json bleibt
    await delete_topic_pipeline(topic)  # DB: Bausteine-Bereich weg; Themen-Config (quelle) bleibt
    return {"ok": True}


def _validate_quelle(typ: str, ort: str) -> None:
    """Quellen-Eingabe prüfen (gleiche Regeln wie beim Erstellen)."""
    if typ in ("projekt", "uni"):
        ordner = safe_ordner(ort)
        if ordner is None or not ordner.is_dir():
            raise HTTPException(400, "Ordner ungültig oder nicht gefunden (Pfad relativ zum Projekt-Root, kein ../).")
    elif typ == "link":
        if not ort.lower().startswith(("http://", "https://")):
            raise HTTPException(400, "Link muss mit http:// oder https:// beginnen.")


@router.get("/bausteine/quelle", response_model=BausteineQuelleResponse)
async def get_bausteine_quelle(topic: str):
    return lade_quelle(topic)


@router.put("/bausteine/quelle", response_model=BausteineQuelleResponse)
async def update_bausteine_quelle(req: BausteineQuelleUpdate):
    """Nur speichern — KEINE Neugenerierung. Quellen-/Spec-Wahl überschreiben."""
    topic, typ, ort = req.topic.strip(), req.type, req.ort.strip()
    _validate_quelle(typ, ort)
    qp = quelle_path(topic)
    qp.parent.mkdir(parents=True, exist_ok=True)
    daten = {"type": typ, "ort": ort, "spec": req.spec.strip()}
    atomic_write_json(qp, daten)
    return daten


@router.get("/bausteine/uebersicht", response_model=list[BausteinUebersicht])
async def get_bausteine_uebersicht(topic: str):
    return await lade_uebersicht(topic)


@router.get("/bausteine/frage-muster")
async def get_frage_muster(topic: str, baustein: str):
    """Vordefinierte Frage-Muster eines Bausteins (leer = Fallback auf Live-Generierung)."""
    return {"muster": await lade_frage_muster(topic, baustein)}


# --- Baustein-Lernen: Chat, Prüfung ---

@router.get("/bausteine/lernstand", response_model=BausteinLernstandResponse)
async def baustein_lernstand(topic: str):
    """Prüfungs-Stand pro Baustein (roher Titel als Key)."""
    progress = await list_baustein_progress(topic)
    bausteine = {
        p["baustein"]: {
            "gute_antworten": p["gute_antworten"],
            "streak": p["streak"],
            "absolviert": p["absolviert"] is not None,
            "verstanden": p["verstanden"] is not None,
            "gemeistert": p["gemeistert"] is not None,
        }
        for p in progress
    }
    return {"bausteine": bausteine}


@router.post("/bausteine/chat", response_model=BausteinChatResponse)
async def baustein_chat_route(req: BausteinChatRequest):
    reply = await baustein_chat(
        req.topic, req.baustein, req.section, req.section_kompakt,
        [m.model_dump() for m in req.messages], provider=req.provider,
    )
    return {"reply": reply}


# Bewertungen je (topic, baustein) serialisieren — sonst überschreiben zwei
# gleichzeitige Bewertungen den absoluten Score mit veralteter Basis (Race).
_pruef_locks: dict[tuple[str, str], asyncio.Lock] = {}


def _pruef_lock(topic: str, baustein: str) -> asyncio.Lock:
    key = (topic, baustein)
    lock = _pruef_locks.get(key)
    if lock is None:
        lock = _pruef_locks[key] = asyncio.Lock()
    return lock


def _basis(stand: dict, frage: str) -> tuple[int, bool]:
    """Score-Basis VOR der Frage. Gleiche offene Frage → Re-Bewertung auf derselben Basis
    (idempotent); sonst neue Frage auf dem aktuellen Stand. → (basis, re_bewertung)."""
    re_bewertung = stand["offene_frage"] == frage and stand["offene_basis"] is not None
    return (stand["offene_basis"] if re_bewertung else stand["gute_antworten"]), re_bewertung


def _farbe(punkte: int) -> str:
    """Punkte-Delta → grobe Einfärbung der Bubble."""
    return "gut" if punkte > 0 else ("neutral" if punkte == 0 else "schlecht")


def _erklaeren_delta(niveau: str, nachgefragt: bool) -> int:
    """Erklären-Form: Punkt-Delta aus dem Niveau (−1…+3). Mit Nachfrage höchstens +1."""
    delta = NIVEAUS.get(niveau, 0)
    return min(delta, 1) if nachgefragt else delta


async def _punkte_buchen(req, frage: str, delta: int) -> dict:
    """Score driftfrei buchen (Lock + offene_frage-Anker + Meilensteine). Gemeinsam für
    Quiz/Lückentext (deterministisch/schnell). → Response-Felder ohne feedback."""
    async with _pruef_lock(req.topic, req.baustein):
        stand = await get_baustein_progress(req.topic, req.baustein)
        absolviert = stand["absolviert"] is not None
        verstanden = stand["verstanden"] is not None
        gemeistert = stand["gemeistert"] is not None
        basis, re_bewertung = _basis(stand, frage)
        if not re_bewertung:
            await set_offene_frage(req.topic, req.baustein, frage, basis, 0)
        floor = floor_aus_meilensteinen(absolviert, verstanden, gemeistert)
        score = score_berechnen(basis, _zufall_malus(basis, delta), floor, req.cap)
        punkte = score - basis
        gute = await set_baustein_score(req.topic, req.baustein, score)
        if score >= NOETIG and not absolviert:
            absolviert = True
            if await set_baustein_absolviert(req.topic, req.baustein):
                asyncio.create_task(baustein_element_anlegen(req.topic, req.baustein, req.section, req.provider))
        if score >= MASTERY and not verstanden:
            await set_baustein_verstanden(req.topic, req.baustein)
            verstanden = True
        if score >= MEISTERN and not gemeistert:
            await set_baustein_gemeistert(req.topic, req.baustein)
            gemeistert = True
        return {"punkte": punkte, "bewertung": _farbe(punkte), "gute_antworten": gute,
                "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}


@router.post("/bausteine/pruefung", response_model=BausteinPruefungResponse)
async def baustein_pruefung_route(req: BausteinPruefungRequest):
    stand = await get_baustein_progress(req.topic, req.baustein)
    gute = stand["gute_antworten"]
    absolviert = stand["absolviert"] is not None
    verstanden = stand["verstanden"] is not None
    gemeistert = stand["gemeistert"] is not None
    kompakt = req.section_kompakt
    msgs = [m.model_dump() for m in req.messages]

    if req.aktion == "frage":
        if req.muster.strip():
            # Aus gezogenem Muster eine konkrete, leicht andere Frage formulieren (kein Dedup nötig).
            frage = await pruefung_frage_variante(req.topic, req.baustein, req.section, kompakt, req.muster, provider=req.provider)
        else:
            # Fallback (kein Muster-Sidecar): Live-Generierung mit zufälligem Typ/Subbaustein.
            subs = await subbausteine_titel(req.topic, req.baustein)  # Fokus-Kandidaten (zufällig gewählt)
            frage = await pruefung_frage(req.topic, req.baustein, req.section, kompakt, msgs, subbausteine=subs, vermeide=req.vermeide, provider=req.provider)
        if frage is None:
            raise HTTPException(502, "Frage fehlgeschlagen — bitte erneut versuchen")
        return {"frage": frage, "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}

    if req.aktion == "diskussion":
        if not req.frage.strip():
            raise HTTPException(400, "Diskussion braucht eine laufende Frage")
        reply = await baustein_diskussion(
            req.topic, req.baustein, req.section, kompakt,
            req.frage, req.letzte_bewertung or None, msgs, provider=req.provider,
        )
        if reply is None:
            raise HTTPException(502, "Diskussion fehlgeschlagen — bitte erneut versuchen")
        return {"reply": reply, "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}

    # --- Quiz: leicht (1 von 4) +1/−1 · schwer (x von 4) +3/−1 — deterministisch ---
    if req.aktion == "quiz_frage":
        if not req.muster.strip():
            raise HTTPException(400, "Quiz braucht ein Muster")
        quiz = await quiz_generieren(req.topic, req.baustein, req.section, kompakt, req.muster, schwer=req.schwer, provider=req.provider)
        if quiz is None:
            raise HTTPException(502, "Quiz-Frage fehlgeschlagen — bitte erneut versuchen")
        return {"frage": quiz["frage"], "optionen": quiz["optionen"],
                "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}

    if req.aktion == "quiz_antwort":
        if not req.frage.strip():
            raise HTTPException(400, "Quiz-Antwort braucht eine Frage")
        getroffen = set(req.auswahl) == set(req.korrekt)  # exakt die richtige Menge
        delta = (3 if req.schwer else 1) if getroffen else -1
        res = await _punkte_buchen(req, req.frage, delta)
        res["feedback"] = "Richtig — alle korrekten getroffen." if getroffen else "Nicht ganz — die markierten waren richtig."
        return res

    # --- Lückentext: leicht (Begriff aus 4) +1/−1 · schwer (frei tippen) +3/−1 ---
    if req.aktion == "lueck_frage":
        if not req.muster.strip():
            raise HTTPException(400, "Lückentext braucht ein Muster")
        if req.schwer:
            lt = await lueckentext_generieren(req.topic, req.baustein, req.section, kompakt, req.muster, provider=req.provider)
            if lt is None:
                raise HTTPException(502, "Lückentext fehlgeschlagen — bitte erneut versuchen")
            return {"satz": lt["satz"], "loesung": lt["loesung"], "alternativen": lt["alternativen"],
                    "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
        lw = await lueckwahl_generieren(req.topic, req.baustein, req.section, kompakt, req.muster, provider=req.provider)
        if lw is None:
            raise HTTPException(502, "Lückentext fehlgeschlagen — bitte erneut versuchen")
        return {"satz": lw["satz"], "optionen": lw["optionen"],
                "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}

    if req.aktion == "lueck_antwort":
        if not req.frage.strip():
            raise HTTPException(400, "Lückentext-Antwort braucht einen Satz")
        if req.schwer:  # frei getippt → Synonym-tolerante KI-Prüfung
            ok = await lueckentext_pruefen(req.topic, req.baustein, req.frage, req.loesung, req.alternativen, req.eingabe, provider=req.provider)
            feedback = "Richtig!" if ok else f"Nicht ganz — erwartet war „{req.loesung}“."
        else:  # Begriff aus 4 gewählt → deterministisch
            ok = set(req.auswahl) == set(req.korrekt)
            feedback = "Richtig!" if ok else "Nicht ganz — der markierte Begriff war richtig."
        res = await _punkte_buchen(req, req.frage, (3 if req.schwer else 1) if ok else -1)
        res["feedback"] = feedback
        return res

    # aktion "antwort" (Agent 1 schnell) / "antwort_pruefen" (Agent 2 genau).
    if not any(m.get("role") == "user" for m in msgs):
        raise HTTPException(400, "Antwort braucht eine Nutzer-Antwort")
    if not req.frage.strip():
        raise HTTPException(400, "Antwort braucht eine laufende Frage")

    if req.aktion == "antwort":
        # Agent 1: nur Vorschau — Niveau + voraussichtliche Punkte, NICHTS persistieren, kein Anker.
        data = await pruefung_bewertung_schnell(
            req.topic, req.baustein, req.section, kompakt, req.frage, msgs, provider=req.provider,
        )
        if data is None:
            raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")
        basis, _ = _basis(stand, req.frage)
        floor = floor_aus_meilensteinen(absolviert, verstanden, gemeistert)
        score = score_berechnen(basis, _zufall_malus(basis, _erklaeren_delta(data["niveau"], req.nachgefragt)), floor, req.cap)
        punkte = score - basis
        return {"feedback": data["feedback"], "punkte": punkte, "bewertung": _farbe(punkte),
                "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}

    # aktion "antwort_pruefen" (Agent 2 genau): verbindlich, persistiert. Serialisiert je Baustein,
    # Basis driftfrei über den offene_frage-Anker. NUR hier ändert sich der Score.
    async with _pruef_lock(req.topic, req.baustein):
        stand = await get_baustein_progress(req.topic, req.baustein)
        absolviert = stand["absolviert"] is not None
        verstanden = stand["verstanden"] is not None
        gemeistert = stand["gemeistert"] is not None
        basis, re_bewertung = _basis(stand, req.frage)
        if not re_bewertung:
            await set_offene_frage(req.topic, req.baustein, req.frage, basis, 0)

        data = await pruefung_bewertung(
            req.topic, req.baustein, req.section, kompakt, req.frage, msgs, provider=req.provider,
            role="guide" if req.gruendlich else "judge", begruendung=req.begruendung,
        )
        if data is None:
            raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")

        floor = floor_aus_meilensteinen(absolviert, verstanden, gemeistert)
        score = score_berechnen(basis, _zufall_malus(basis, _erklaeren_delta(data["niveau"], req.nachgefragt)), floor, req.cap)
        punkte = score - basis
        gute = await set_baustein_score(req.topic, req.baustein, score)
        if score >= NOETIG and not absolviert:
            absolviert = True
            if await set_baustein_absolviert(req.topic, req.baustein):
                asyncio.create_task(baustein_element_anlegen(req.topic, req.baustein, req.section, req.provider))
        if score >= MASTERY and not verstanden:
            await set_baustein_verstanden(req.topic, req.baustein)
            verstanden = True
        if score >= MEISTERN and not gemeistert:
            await set_baustein_gemeistert(req.topic, req.baustein)
            gemeistert = True
        return {"feedback": data["feedback"], "punkte": punkte, "bewertung": _farbe(punkte),
                "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}


# --- Guides ---

@router.post("/guides", response_model=GuideResponse)
async def create(req: GuideCreateRequest):
    guides, progress, levels = await lade_lernstand()
    grund = guide_lock(req.topic.strip(), req.format, guides, progress, levels)
    if grund:
        raise HTTPException(400 if grund == "Erst Bausteine erstellen" else 409, grund)
    await create_topic(req.topic.strip())
    now = datetime.now(timezone.utc).isoformat()
    guide = {
        "id": str(uuid.uuid4()),
        "topic": req.topic.strip(),
        "format": req.format,
        "instructions": req.instructions.strip(),
        "status": "queued",
        "progress": None,
        "created_at": now,
        "updated_at": now,
    }
    await create_guide(guide)
    asyncio.create_task(generate_guide(guide["id"], guide["topic"], guide["format"], guide["instructions"], req.provider, ab_step=req.ab_step))
    return guide


@router.get("/guides", response_model=list[GuideResponse])
async def list_all():
    return await list_guides()


@router.get("/guides/locks")
async def guide_locks(topic: str):
    """Sperr-Gründe pro Format für den ▶-Button — None = erstellbar."""
    guides, progress, levels = await lade_lernstand()
    return {fmt: guide_lock(topic, fmt, guides, progress, levels) for fmt in ("FullGuide", "Rest", *FORMATE)}


@router.get("/guides/steps")
async def guide_steps(topic: str):
    """Höchster voll abgeschlossener Schritt-Index je Format (artefakt-basiert, -1 = keiner).
    Treibt die klickbaren Schritt-Kugeln (wie die Bausteine-Phasen)."""
    return {fmt: guide_fertig_step(guide_content_path(topic, fmt)) for fmt in ("Guide", "FullGuide", "Rest")}


@router.get("/guides/{guide_id}", response_model=GuideResponse)
async def get_one(guide_id: str):
    guide = await get_guide(guide_id)
    if guide is None:
        raise HTTPException(404, "Guide nicht gefunden")
    return guide


@router.get("/guides/{guide_id}/content")
async def guide_content(guide_id: str):
    guide = await get_guide(guide_id)
    if guide is None:
        raise HTTPException(404, "Guide nicht gefunden")
    if guide["status"] != "done":
        raise HTTPException(404, "Inhalt nicht verfügbar")
    stored = await get_guide_content(guide["topic"], guide["format"])  # DB-first
    if stored is not None:
        return Response(content=stored, media_type="application/json")
    path = guide_content_path(guide["topic"], guide["format"])  # Fallback: Datei (Alt-Themen)
    if not path.exists():
        raise HTTPException(404, "Datei nicht gefunden")
    return FileResponse(path, media_type="application/json")


@router.post("/guides/{guide_id}/chat", response_model=GuideChatResponse)
async def guide_chat(guide_id: str, req: GuideChatRequest):
    guide = await get_guide(guide_id)
    if guide is None:
        raise HTTPException(404, "Guide nicht gefunden")
    reply = await chat_with_guide(
        guide["topic"], guide["format"], req.section, req.outline,
        [m.model_dump() for m in req.messages],
        provider=req.provider,
    )
    return {"reply": reply}


# --- Elemente (persönliche Zusammenfassung) ---

@router.get("/elements", response_model=list[ElementResponse])
async def get_elements(topic: str):
    return await list_elements(topic)


@router.post("/elements", response_model=ElementResponse)
async def post_element(req: ElementCreateRequest):
    fields = await generate_element(req.topic, req.hint, provider=req.provider)
    now = datetime.now(timezone.utc).isoformat()
    element = {"id": str(uuid.uuid4()), "topic": req.topic, **fields, "created_at": now, "updated_at": now}
    await create_element(element)
    return element


@router.post("/elements/{element_id}/chat", response_model=ElementChatResponse)
async def element_chat(element_id: str, req: ElementChatRequest):
    element = await get_element(element_id)
    if element is None:
        raise HTTPException(404, "Element nicht gefunden")
    reply, changes = await chat_with_element(element, [m.model_dump() for m in req.messages], provider=req.provider)
    return {"reply": reply, "changes": changes}


@router.post("/elements/{element_id}/refine", response_model=ElementRefineResponse)
async def element_refine(element_id: str, req: ElementRefineRequest):
    element = await get_element(element_id)
    if element is None:
        raise HTTPException(404, "Element nicht gefunden")
    change = await refine_suggestion(element, req.suggestion.model_dump(), req.instruction, provider=req.provider)
    if change is None:
        raise HTTPException(502, "Überarbeitung fehlgeschlagen — bitte erneut versuchen")
    return {"change": change}


@router.put("/elements/{element_id}", response_model=ElementResponse)
async def put_element(element_id: str, req: ElementUpdateRequest):
    if await get_element(element_id) is None:
        raise HTTPException(404, "Element nicht gefunden")
    fields = req.model_dump(exclude_unset=True, exclude_none=True)
    if fields:
        now = datetime.now(timezone.utc).isoformat()
        await update_element(element_id, **fields, updated_at=now)
    return await get_element(element_id)


@router.post("/elements/{element_id}/style", response_model=ElementStyleResponse)
async def element_style(element_id: str, req: ElementCheckRequest):
    element = await get_element(element_id)
    if element is None:
        raise HTTPException(404, "Element nicht gefunden")
    changes = await style_element(element, provider=req.provider)
    if changes is None:
        raise HTTPException(502, "Stil-Prüfung fehlgeschlagen — bitte erneut versuchen")
    return {"changes": changes}


@router.post("/elements/{element_id}/check", response_model=ElementCheckResponse)
async def element_check(element_id: str, req: ElementCheckRequest):
    element = await get_element(element_id)
    if element is None:
        raise HTTPException(404, "Element nicht gefunden")
    suggestions = await check_element(element, provider=req.provider)
    if suggestions is None:
        raise HTTPException(502, "Prüfung fehlgeschlagen — bitte erneut versuchen")
    return {"suggestions": suggestions}


@router.delete("/elements/{element_id}")
async def remove_element(element_id: str):
    if not await delete_element(element_id):
        raise HTTPException(404, "Element nicht gefunden")
    return {"ok": True}


@router.post("/guides/{guide_id}/cancel")
async def cancel(guide_id: str):
    cancelled = await cancel_guide(guide_id)
    if not cancelled:
        raise HTTPException(404, "Kein aktiver Prozess gefunden")
    return {"ok": True}


@router.delete("/guides/{guide_id}")
async def remove(guide_id: str, slots: bool = False):
    guide = await get_guide(guide_id)
    if guide is None:
        raise HTTPException(404, "Guide nicht gefunden")
    await delete_progress(guide_id)
    await delete_guide(guide_id)
    # Content-/Schritt-Dateien teilen sich alle Läufe eines Thema+Formats — erst löschen,
    # wenn kein Eintrag sie mehr braucht. Teilfortschritt (Schritt-Dateien ohne fertigen
    # Content) bleibt fürs Resume erhalten, außer es wird explizit verlangt (slots=1).
    rest = [g for g in await list_guides() if g["topic"] == guide["topic"] and g["format"] == guide["format"]]
    if not rest:
        await delete_guide_content(guide["topic"], guide["format"])
        content = guide_content_path(guide["topic"], guide["format"])
        if slots or content.exists():
            for p in guide_slot_dateien(content):
                p.unlink(missing_ok=True)
            content.unlink(missing_ok=True)
    return {"ok": True}


@router.get("/guides/{guide_id}/progress", response_model=ProgressResponse)
async def get_progress(guide_id: str):
    guide = await get_guide(guide_id)
    if guide is None:
        raise HTTPException(404, "Guide nicht gefunden")
    return {"chapters": await list_progress(guide_id)}


@router.post("/guides/{guide_id}/progress", response_model=ProgressResponse)
async def update_progress(guide_id: str, req: ProgressUpdate):
    guide = await get_guide(guide_id)
    if guide is None:
        raise HTTPException(404, "Guide nicht gefunden")
    await set_progress(guide_id, req.chapter, req.done)
    return {"chapters": await list_progress(guide_id)}
