"""FastMCP 3.x Server für die Gamma.app API."""

from __future__ import annotations

import asyncio
import json
from contextlib import asynccontextmanager
from typing import Literal

from fastmcp import Context, FastMCP
from pydantic import BaseModel, Field

from gamma31.gamma_client import GammaAPIError, GammaClient


# ------------------------------------------------------------------ #
# Lifespan
# ------------------------------------------------------------------ #

@asynccontextmanager
async def lifespan(server):  # noqa: ARG001
    """Gamma API Client beim Start initialisieren, beim Shutdown schließen."""
    client = GammaClient()
    try:
        yield {"gamma_client": client}
    finally:
        await client.close()


mcp = FastMCP(
    "Gamma Präsentations-Server",
    instructions=(
        "MCP-Server für die Gamma.app API — erstellt Präsentationen, "
        "Dokumente, Social Posts und Webseiten mit KI."
    ),
    lifespan=lifespan,
)


# ------------------------------------------------------------------ #
# Helpers
# ------------------------------------------------------------------ #

def _get_client(ctx: Context) -> GammaClient:
    return ctx.lifespan_context["gamma_client"]


def _error_response(message: str) -> str:
    return json.dumps(
        {"fehler": True, "nachricht": message},
        ensure_ascii=False,
        indent=2,
    )


def _success_response(data: dict) -> str:
    return json.dumps(data, ensure_ascii=False, indent=2)


# ------------------------------------------------------------------ #
# Elicitation Models
# ------------------------------------------------------------------ #

class GenerierungsOptionen(BaseModel):
    """Optionale Einstellungen vor der Gamma-Generierung."""

    sprache: str = Field(
        default="de",
        description="Sprachcode (z.B. de, en, fr, es, it, pt, ja, zh)",
    )
    zielgruppe: str = Field(
        default="",
        description="Zielgruppe, z.B. 'Geschäftsführer', 'Studenten', 'Kunden'",
    )
    tonalitaet: str = Field(
        default="",
        description="Tonalität, z.B. 'professionell', 'locker', 'inspirierend'",
    )
    textmenge: Literal["brief", "medium", "detailed", "extensive"] = Field(
        default="medium",
        description="Wie viel Text pro Folie/Karte",
    )
    zusaetzliche_anweisungen: str = Field(
        default="",
        description="Weitere Anweisungen an die KI (optional)",
    )


async def _elicit_generation_prefs(
    ctx: Context,
    text_sprache: str | None,
    text_zielgruppe: str | None,
    text_ton: str | None,
    text_menge: str | None,
    zusaetzliche_anweisungen: str | None,
) -> tuple[str | None, str | None, str | None, str | None, str | None]:
    """Fragt den Nutzer interaktiv nach Präferenzen, falls nicht gesetzt."""
    try:
        result = await ctx.elicit(
            "Bevor die Gamma erstellt wird — ein paar optionale Angaben "
            "für ein besseres Ergebnis (einfach bestätigen für Standardwerte):",
            response_type=GenerierungsOptionen,
        )
        if result.action == "accept":
            opts = result.data
            if not text_sprache and opts.sprache:
                text_sprache = opts.sprache
            if not text_zielgruppe and opts.zielgruppe:
                text_zielgruppe = opts.zielgruppe
            if not text_ton and opts.tonalitaet:
                text_ton = opts.tonalitaet
            if not text_menge and opts.textmenge:
                text_menge = opts.textmenge
            if not zusaetzliche_anweisungen and opts.zusaetzliche_anweisungen:
                zusaetzliche_anweisungen = opts.zusaetzliche_anweisungen
    except Exception:
        pass

    return text_sprache, text_zielgruppe, text_ton, text_menge, zusaetzliche_anweisungen


# ------------------------------------------------------------------ #
# Tool 1: gamma_generieren
# ------------------------------------------------------------------ #

@mcp.tool(tags={"core"}, timeout=30.0)
async def gamma_generieren(
    inhalt: str,
    ctx: Context,
    text_modus: str = "generate",
    format: str = "presentation",
    interaktiv: bool = True,
    theme_id: str | None = None,
    anzahl_karten: int | None = None,
    karten_aufteilung: str = "auto",
    zusaetzliche_anweisungen: str | None = None,
    ordner_ids: list[str] | None = None,
    export_als: str | None = None,
    text_menge: str | None = None,
    text_ton: str | None = None,
    text_zielgruppe: str | None = None,
    text_sprache: str | None = None,
    bild_quelle: str | None = None,
    bild_modell: str | None = None,
    bild_stil: str | None = None,
    karten_dimensionen: str | None = None,
    workspace_zugriff: str | None = None,
    externer_zugriff: str | None = None,
    email_empfaenger: list[str] | None = None,
    email_zugriff: str | None = None,
) -> str:
    """Erstellt eine neue Gamma (Präsentation, Dokument, Social Post oder Webseite).

    Nutze dieses Tool, um mit KI eine neue Gamma zu generieren. Der Inhalt
    wird als Prompt oder Quelltext übergeben; der text_modus bestimmt, wie
    Gamma damit umgeht.

    Wenn interaktiv=True (Standard) und keine Text-Optionen (Sprache,
    Zielgruppe, Ton, Menge) angegeben sind, wird der Nutzer vorher
    nach seinen Präferenzen gefragt.

    Args:
        inhalt: Inhalt oder Prompt für die Gamma (1–400.000 Zeichen).
        text_modus: Umgang mit dem Inhalt — "generate" (KI schreibt neu),
            "condense" (kürzt zusammen) oder "preserve" (übernimmt wörtlich).
        format: Ausgabeformat — "presentation", "document", "social" oder
            "webpage". Standard: "presentation".
        interaktiv: Wenn True, werden fehlende Einstellungen (Sprache,
            Zielgruppe, Ton, Textmenge) beim Nutzer nachgefragt.
        theme_id: Optionale Theme-ID (aus gamma_themes_auflisten).
        anzahl_karten: Anzahl Karten/Folien (1–60 für Pro, 1–75 für Ultra).
        karten_aufteilung: Wie Inhalt auf Karten verteilt wird — "auto" oder
            "inputTextBreaks".
        zusaetzliche_anweisungen: Zusätzliche Anweisungen an die KI (max 500 Zeichen).
        ordner_ids: Liste von Ordner-IDs als Ziel (aus gamma_ordner_auflisten).
        export_als: Sofort exportieren als "pdf" oder "pptx". Die Download-URLs
            sind temporär und verfallen nach kurzer Zeit.
        text_menge: Textmenge — "brief", "medium", "detailed" oder "extensive".
        text_ton: Tonalität, z.B. "professionell, inspirierend" (max 500 Zeichen).
        text_zielgruppe: Zielgruppe, z.B. "Geschäftsführer, Investoren" (max 500 Zeichen).
        text_sprache: Sprachcode, z.B. "de", "en", "fr".
        bild_quelle: Bildquelle — "aiGenerated", "unsplash", "webImages",
            "pictographic", "gifs", "placeholder" oder "noImages".
        bild_modell: KI-Bildmodell, z.B. "imagen-4-pro".
        bild_stil: Bildstil, z.B. "fotorealistisch" (max 500 Zeichen).
        karten_dimensionen: Kartengröße — "fluid", "16x9", "4x3", "pageless",
            "letter" oder "a4".
        workspace_zugriff: Workspace-Zugriff — "noAccess", "view", "comment",
            "edit" oder "fullAccess".
        externer_zugriff: Externer Zugriff — "noAccess", "view", "comment",
            "edit" oder "fullAccess".
        email_empfaenger: Liste von E-Mail-Adressen zum Teilen.
        email_zugriff: Zugriffslevel für E-Mail-Empfänger.
    """
    try:
        has_text_prefs = any([
            text_sprache, text_zielgruppe, text_ton,
            text_menge, zusaetzliche_anweisungen,
        ])
        if interaktiv and not has_text_prefs:
            (
                text_sprache, text_zielgruppe, text_ton,
                text_menge, zusaetzliche_anweisungen,
            ) = await _elicit_generation_prefs(
                ctx, text_sprache, text_zielgruppe,
                text_ton, text_menge, zusaetzliche_anweisungen,
            )

        await ctx.info("Gamma wird generiert …")

        payload: dict = {
            "inputText": inhalt,
            "textMode": text_modus,
            "format": format,
            "cardSplit": karten_aufteilung,
        }

        if theme_id is not None:
            payload["themeId"] = theme_id
        if anzahl_karten is not None:
            payload["numCards"] = anzahl_karten
        if zusaetzliche_anweisungen is not None:
            payload["additionalInstructions"] = zusaetzliche_anweisungen
        if ordner_ids is not None:
            payload["folderIds"] = ordner_ids
        if export_als is not None:
            payload["exportAs"] = export_als

        text_options: dict = {}
        if text_menge is not None:
            text_options["amount"] = text_menge
        if text_ton is not None:
            text_options["tone"] = text_ton
        if text_zielgruppe is not None:
            text_options["audience"] = text_zielgruppe
        if text_sprache is not None:
            text_options["language"] = text_sprache
        if text_options:
            payload["textOptions"] = text_options

        image_options: dict = {}
        if bild_quelle is not None:
            image_options["source"] = bild_quelle
        if bild_modell is not None:
            image_options["model"] = bild_modell
        if bild_stil is not None:
            image_options["style"] = bild_stil
        if image_options:
            payload["imageOptions"] = image_options

        card_options: dict = {}
        if karten_dimensionen is not None:
            card_options["dimensions"] = karten_dimensionen
        if card_options:
            payload["cardOptions"] = card_options

        sharing_options: dict = {}
        if workspace_zugriff is not None:
            sharing_options["workspaceAccess"] = workspace_zugriff
        if externer_zugriff is not None:
            sharing_options["externalAccess"] = externer_zugriff
        if email_empfaenger is not None:
            email_opts: dict = {"recipients": email_empfaenger}
            if email_zugriff is not None:
                email_opts["access"] = email_zugriff
            sharing_options["emailOptions"] = email_opts
        if sharing_options:
            payload["sharingOptions"] = sharing_options

        client = _get_client(ctx)
        result = await client.generate(payload)

        gen_id = result.get("generationId", "?")
        await ctx.info(
            f"Generation gestartet (ID: {gen_id}). "
            "Rufe gamma_status_abrufen auf, um das Ergebnis abzuholen."
        )
        return _success_response(result)

    except GammaAPIError as exc:
        return _error_response(exc.message)
    except Exception as exc:
        return _error_response(f"Unerwarteter Fehler: {exc}")


# ------------------------------------------------------------------ #
# Tool 2: gamma_aus_vorlage_erstellen
# ------------------------------------------------------------------ #

@mcp.tool(tags={"beta"}, timeout=30.0)
async def gamma_aus_vorlage_erstellen(
    gamma_id: str,
    prompt: str,
    ctx: Context,
    theme_id: str | None = None,
    ordner_ids: list[str] | None = None,
    export_als: str | None = None,
    bild_modell: str | None = None,
    bild_stil: str | None = None,
    workspace_zugriff: str | None = None,
    externer_zugriff: str | None = None,
    email_empfaenger: list[str] | None = None,
    email_zugriff: str | None = None,
) -> str:
    """Erstellt eine Gamma basierend auf einem bestehenden Template (Beta-API).

    Nutze dieses Tool, wenn du eine existierende Gamma als Vorlage verwenden
    möchtest. Die Gamma-ID findest du in der URL eines Gammas
    (Format: g_abcdef123456ghi).

    Args:
        gamma_id: ID des Template-Gammas (z.B. "g_abcdef123456ghi").
        prompt: Anweisungen und Inhalt für die neue Gamma.
        theme_id: Optionale Theme-ID.
        ordner_ids: Ziel-Ordner-IDs.
        export_als: Sofort exportieren als "pdf" oder "pptx".
        bild_modell: KI-Bildmodell, z.B. "imagen-4-pro".
        bild_stil: Bildstil, z.B. "fotorealistisch".
        workspace_zugriff: Workspace-Zugriff — "noAccess", "view", "comment",
            "edit" oder "fullAccess".
        externer_zugriff: Externer Zugriff — "noAccess", "view", "comment",
            "edit" oder "fullAccess".
        email_empfaenger: Liste von E-Mail-Adressen zum Teilen.
        email_zugriff: Zugriffslevel für E-Mail-Empfänger.
    """
    try:
        await ctx.info("Gamma aus Vorlage wird generiert …")

        payload: dict = {
            "gammaId": gamma_id,
            "prompt": prompt,
        }

        if theme_id is not None:
            payload["themeId"] = theme_id
        if ordner_ids is not None:
            payload["folderIds"] = ordner_ids
        if export_als is not None:
            payload["exportAs"] = export_als

        image_options: dict = {}
        if bild_modell is not None:
            image_options["model"] = bild_modell
        if bild_stil is not None:
            image_options["style"] = bild_stil
        if image_options:
            payload["imageOptions"] = image_options

        sharing_options: dict = {}
        if workspace_zugriff is not None:
            sharing_options["workspaceAccess"] = workspace_zugriff
        if externer_zugriff is not None:
            sharing_options["externalAccess"] = externer_zugriff
        if email_empfaenger is not None:
            email_opts: dict = {"recipients": email_empfaenger}
            if email_zugriff is not None:
                email_opts["access"] = email_zugriff
            sharing_options["emailOptions"] = email_opts
        if sharing_options:
            payload["sharingOptions"] = sharing_options

        client = _get_client(ctx)
        result = await client.generate_from_template(payload)

        gen_id = result.get("generationId", "?")
        await ctx.info(
            f"Generation aus Vorlage gestartet (ID: {gen_id}). "
            "Rufe gamma_status_abrufen auf, um das Ergebnis abzuholen."
        )
        return _success_response(result)

    except GammaAPIError as exc:
        return _error_response(exc.message)
    except Exception as exc:
        return _error_response(f"Unerwarteter Fehler: {exc}")


# ------------------------------------------------------------------ #
# Tool 3: gamma_status_abrufen
# ------------------------------------------------------------------ #

@mcp.tool(tags={"core"}, timeout=330.0)
async def gamma_status_abrufen(
    generation_id: str,
    ctx: Context,
    automatisch_warten: bool = True,
    max_wartezeit_sekunden: int = 300,
) -> str:
    """Prüft den Status einer Gamma-Generierung und wartet optional auf Fertigstellung.

    Rufe dieses Tool IMMER nach gamma_generieren oder
    gamma_aus_vorlage_erstellen auf, um die fertige Gamma-URL und
    ggf. Download-Links zu erhalten.

    Bei automatisch_warten=True (Standard) pollt das Tool alle 5 Sekunden,
    bis die Generierung abgeschlossen ist oder die maximale Wartezeit
    erreicht wurde. Export-URLs (PDF, PPTX) sind temporär und verfallen
    nach kurzer Zeit.

    Args:
        generation_id: Die generationId aus gamma_generieren oder
            gamma_aus_vorlage_erstellen.
        automatisch_warten: Wenn True, wird automatisch gepollt bis
            completed/failed. Wenn False, wird nur der aktuelle Status
            einmal abgerufen.
        max_wartezeit_sekunden: Maximale Wartezeit in Sekunden (Standard: 300).
    """
    try:
        client = _get_client(ctx)

        if not automatisch_warten:
            result = await client.get_generation(generation_id)
            return _success_response(result)

        await ctx.info(f"Warte auf Fertigstellung (max. {max_wartezeit_sekunden}s) …")

        elapsed = 0
        result: dict = {}
        while elapsed < max_wartezeit_sekunden:
            result = await client.get_generation(generation_id)
            status = result.get("status", "unknown")

            await ctx.report_progress(
                progress=elapsed,
                total=max_wartezeit_sekunden,
                message=f"Gamma-Status: {status}",
            )

            if status in ("completed", "failed"):
                break

            await asyncio.sleep(5)
            elapsed += 5
        else:
            result = {
                "generationId": generation_id,
                "status": "timeout",
                "_hinweis": (
                    f"Kein Ergebnis nach {max_wartezeit_sekunden} Sekunden. "
                    "Versuchen Sie gamma_status_abrufen erneut."
                ),
            }

        final_status = result.get("status", "unknown")
        if final_status == "completed":
            url = result.get("gammaUrl", "")
            await ctx.info(f"Gamma fertig: {url}")
        elif final_status == "failed":
            await ctx.warning("Gamma-Generierung fehlgeschlagen.")
        else:
            await ctx.info(f"Aktueller Status: {final_status}")

        if result.get("credits"):
            total = await ctx.get_state("gamma_credits_verbraucht") or 0
            deducted = result["credits"].get("deducted", 0)
            new_total = total + deducted
            await ctx.set_state("gamma_credits_verbraucht", new_total)
            result["_session_credits_gesamt"] = new_total

        return _success_response(result)

    except GammaAPIError as exc:
        return _error_response(exc.message)
    except Exception as exc:
        return _error_response(f"Unerwarteter Fehler: {exc}")


# ------------------------------------------------------------------ #
# Tool 4: gamma_themes_auflisten
# ------------------------------------------------------------------ #

@mcp.tool(tags={"lookup"}, timeout=15.0)
async def gamma_themes_auflisten(
    ctx: Context,
    limit: int = 50,
    after: str | None = None,
) -> str:
    """Listet verfügbare Gamma-Themes auf.

    Themes bestimmen das visuelle Design einer Gamma. Verwende die
    zurückgegebene Theme-ID als theme_id-Parameter in gamma_generieren
    oder gamma_aus_vorlage_erstellen.

    Die Ergebnisse sind paginiert. Wenn hasMore=true, verwende den
    zurückgegebenen nextCursor als after-Parameter für die nächste Seite.

    Args:
        limit: Anzahl Ergebnisse pro Seite (Standard: 50).
        after: Cursor für die nächste Seite (nextCursor aus vorherigem Aufruf).
    """
    try:
        client = _get_client(ctx)
        result = await client.list_themes(limit=limit, after=after)
        count = len(result.get("data", []))
        await ctx.info(f"{count} Themes geladen.")
        return _success_response(result)

    except GammaAPIError as exc:
        return _error_response(exc.message)
    except Exception as exc:
        return _error_response(f"Unerwarteter Fehler: {exc}")


# ------------------------------------------------------------------ #
# Tool 5: gamma_ordner_auflisten
# ------------------------------------------------------------------ #

@mcp.tool(tags={"lookup"}, timeout=15.0)
async def gamma_ordner_auflisten(
    ctx: Context,
    limit: int = 50,
    after: str | None = None,
) -> str:
    """Listet Workspace-Ordner in Gamma auf.

    Ordner können als Ziel für neue Gammas angegeben werden. Verwende
    die zurückgegebene Ordner-ID als ordner_ids-Parameter in
    gamma_generieren oder gamma_aus_vorlage_erstellen.

    Die Ergebnisse sind paginiert. Wenn hasMore=true, verwende den
    zurückgegebenen nextCursor als after-Parameter für die nächste Seite.

    Args:
        limit: Anzahl Ergebnisse pro Seite (Standard: 50).
        after: Cursor für die nächste Seite (nextCursor aus vorherigem Aufruf).
    """
    try:
        client = _get_client(ctx)
        result = await client.list_folders(limit=limit, after=after)
        count = len(result.get("data", []))
        await ctx.info(f"{count} Ordner geladen.")
        return _success_response(result)

    except GammaAPIError as exc:
        return _error_response(exc.message)
    except Exception as exc:
        return _error_response(f"Unerwarteter Fehler: {exc}")
