import json

import aiosqlite
from config import DB_PATH

CREATE_GUIDES = """
CREATE TABLE IF NOT EXISTS guides (
    id TEXT PRIMARY KEY,
    topic TEXT NOT NULL,
    format TEXT NOT NULL,
    instructions TEXT NOT NULL DEFAULT '',
    status TEXT NOT NULL DEFAULT 'queued',
    progress TEXT,
    step INTEGER,
    error_msg TEXT,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
)
"""

CREATE_PROGRESS = """
CREATE TABLE IF NOT EXISTS guide_progress (
    guide_id TEXT NOT NULL,
    chapter TEXT NOT NULL,
    created_at TEXT NOT NULL,
    PRIMARY KEY (guide_id, chapter)
)
"""

CREATE_TOPICS = """
CREATE TABLE IF NOT EXISTS topics (
    name TEXT PRIMARY KEY,
    created_at TEXT NOT NULL
)
"""

CREATE_ELEMENTS = """
CREATE TABLE IF NOT EXISTS elements (
    id TEXT PRIMARY KEY,
    topic TEXT NOT NULL,
    title TEXT NOT NULL,
    description TEXT NOT NULL DEFAULT '',
    examples TEXT NOT NULL DEFAULT '[]',
    hints TEXT NOT NULL DEFAULT '[]',
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
)
"""

CREATE_BAUSTEIN_TEXTE = """
CREATE TABLE IF NOT EXISTS baustein_texte (
    topic TEXT NOT NULL,
    baustein TEXT NOT NULL,
    art TEXT NOT NULL,
    md TEXT NOT NULL,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    PRIMARY KEY (topic, baustein, art)
)
"""

CREATE_BAUSTEIN_PROGRESS = """
CREATE TABLE IF NOT EXISTS baustein_progress (
    topic TEXT NOT NULL,
    baustein TEXT NOT NULL,
    gute_antworten INTEGER NOT NULL DEFAULT 0,
    streak INTEGER NOT NULL DEFAULT 0,
    absolviert TEXT,
    verstanden TEXT,
    gemeistert TEXT,
    updated_at TEXT NOT NULL,
    PRIMARY KEY (topic, baustein)
)
"""

_db: aiosqlite.Connection | None = None


async def get_db() -> aiosqlite.Connection:
    global _db
    if _db is None:
        _db = await aiosqlite.connect(DB_PATH)
        _db.row_factory = None
    return _db


async def init_db():
    db = await get_db()
    # WAL übersteht Crashes deutlich besser; busy_timeout fängt kurze Locks ab.
    await db.execute("PRAGMA journal_mode=WAL")
    await db.execute("PRAGMA busy_timeout=5000")
    await db.execute(CREATE_GUIDES)
    await db.execute(CREATE_PROGRESS)
    await db.execute(CREATE_TOPICS)
    await db.execute(CREATE_ELEMENTS)
    await db.execute(CREATE_BAUSTEIN_TEXTE)
    await db.execute(CREATE_BAUSTEIN_PROGRESS)
    try:  # Migration für Bestands-DBs ohne step-Spalte
        await db.execute("ALTER TABLE guides ADD COLUMN step INTEGER")
    except aiosqlite.OperationalError:
        pass
    try:  # Migration für Bestands-DBs ohne verstanden-Spalte (Mastery-Stufe)
        await db.execute("ALTER TABLE baustein_progress ADD COLUMN verstanden TEXT")
    except aiosqlite.OperationalError:
        pass
    try:  # Migration für Bestands-DBs ohne gemeistert-Spalte (Meisterpfad 25)
        await db.execute("ALTER TABLE baustein_progress ADD COLUMN gemeistert TEXT")
    except aiosqlite.OperationalError:
        pass
    try:  # Migration für Bestands-DBs ohne streak-Spalte (persistente Streak-Bonus-Folge)
        await db.execute("ALTER TABLE baustein_progress ADD COLUMN streak INTEGER NOT NULL DEFAULT 0")
    except aiosqlite.OperationalError:
        pass
    # Offene-Frage-Anker: Basis/Streak VOR der aktuell offenen Frage — macht die Bewertung
    # serverseitig idempotent (Re-Bewertung) und driftfrei über Fragen hinweg.
    for _spalte, _typ in (("offene_frage", "TEXT"), ("offene_basis", "INTEGER"), ("offene_streak", "INTEGER")):
        try:
            await db.execute(f"ALTER TABLE baustein_progress ADD COLUMN {_spalte} {_typ}")
        except aiosqlite.OperationalError:
            pass
    # Migration: alte vertiefungen-Tabelle → baustein_texte (Bestand = lange Form, art 'deepdive')
    cursor = await db.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'vertiefungen'")
    if await cursor.fetchone():
        await db.execute(
            "INSERT OR IGNORE INTO baustein_texte (topic, baustein, art, md, created_at, updated_at) "
            "SELECT topic, baustein, 'deepdive', md, created_at, updated_at FROM vertiefungen"
        )
        await db.execute("DROP TABLE vertiefungen")
    await db.execute(
        "UPDATE guides SET status = 'error', progress = NULL, error_msg = 'Server-Neustart' "
        "WHERE status IN ('queued', 'generating')"
    )
    # Milestone-Migration auf Score-Skala 0–30: absolviert≥6, verstanden≥12, gemeistert≥18.
    # Leitet die Meilensteine aus dem aktuellen Score ab (bestehende Timestamps bleiben),
    # idempotent — nach einem Lauf stabil, im Normalbetrieb deckungsgleich mit der Prüf-Route.
    await db.execute("UPDATE baustein_progress SET gute_antworten = 30 WHERE gute_antworten > 30")  # Alt-Scores deckeln
    for _spalte, _schwelle in (("absolviert", 6), ("verstanden", 12), ("gemeistert", 18)):
        await db.execute(
            f"UPDATE baustein_progress SET {_spalte} = COALESCE({_spalte}, datetime('now')) "
            f"WHERE gute_antworten >= {_schwelle}"
        )
        await db.execute(f"UPDATE baustein_progress SET {_spalte} = NULL WHERE gute_antworten < {_schwelle}")
    await db.commit()


async def close_db():
    global _db
    if _db is not None:
        await _db.close()
        _db = None


def _row_to_dict(row, cursor):
    columns = [d[0] for d in cursor.description]
    return dict(zip(columns, row))


async def create_guide(guide: dict) -> dict:
    db = await get_db()
    await db.execute(
        """INSERT INTO guides (id, topic, format, instructions, status, progress, created_at, updated_at)
           VALUES (:id, :topic, :format, :instructions, :status, :progress, :created_at, :updated_at)""",
        guide,
    )
    await db.commit()
    return guide


async def get_guide(guide_id: str) -> dict | None:
    db = await get_db()
    cursor = await db.execute("SELECT * FROM guides WHERE id = ?", (guide_id,))
    row = await cursor.fetchone()
    if row is None:
        return None
    return _row_to_dict(row, cursor)


async def list_guides() -> list[dict]:
    db = await get_db()
    cursor = await db.execute("SELECT * FROM guides ORDER BY created_at DESC")
    rows = await cursor.fetchall()
    return [_row_to_dict(row, cursor) for row in rows]


async def update_guide(guide_id: str, **fields) -> None:
    sets = ", ".join(f"{k} = :{k}" for k in fields)
    fields["id"] = guide_id
    db = await get_db()
    await db.execute(f"UPDATE guides SET {sets} WHERE id = :id", fields)
    await db.commit()


async def delete_guide(guide_id: str) -> bool:
    db = await get_db()
    cursor = await db.execute("DELETE FROM guides WHERE id = ?", (guide_id,))
    await db.commit()
    return cursor.rowcount > 0


# --- Themen ---

async def create_topic(name: str) -> None:
    from datetime import datetime, timezone
    db = await get_db()
    await db.execute(
        "INSERT OR IGNORE INTO topics (name, created_at) VALUES (?, ?)",
        (name, datetime.now(timezone.utc).isoformat()),
    )
    await db.commit()


async def list_topics() -> list[str]:
    db = await get_db()
    cursor = await db.execute("SELECT name FROM topics ORDER BY created_at DESC")
    rows = await cursor.fetchall()
    return [row[0] for row in rows]


async def delete_topic(name: str) -> None:
    db = await get_db()
    await db.execute("DELETE FROM topics WHERE name = ?", (name,))
    await db.commit()


# --- Elemente ---

def _element_row(row, cursor) -> dict:
    el = _row_to_dict(row, cursor)
    el["examples"] = json.loads(el["examples"] or "[]")
    el["hints"] = json.loads(el["hints"] or "[]")
    return el


async def create_element(element: dict) -> dict:
    db = await get_db()
    await db.execute(
        """INSERT INTO elements (id, topic, title, description, examples, hints, created_at, updated_at)
           VALUES (:id, :topic, :title, :description, :examples, :hints, :created_at, :updated_at)""",
        {**element, "examples": json.dumps(element["examples"], ensure_ascii=False),
         "hints": json.dumps(element["hints"], ensure_ascii=False)},
    )
    await db.commit()
    return element


async def list_elements(topic: str) -> list[dict]:
    db = await get_db()
    cursor = await db.execute(
        "SELECT * FROM elements WHERE topic = ? ORDER BY updated_at DESC", (topic,)
    )
    rows = await cursor.fetchall()
    return [_element_row(row, cursor) for row in rows]


async def get_element(element_id: str) -> dict | None:
    db = await get_db()
    cursor = await db.execute("SELECT * FROM elements WHERE id = ?", (element_id,))
    row = await cursor.fetchone()
    if row is None:
        return None
    return _element_row(row, cursor)


async def update_element(element_id: str, **fields) -> None:
    for key in ("examples", "hints"):
        if key in fields:
            fields[key] = json.dumps(fields[key], ensure_ascii=False)
    sets = ", ".join(f"{k} = :{k}" for k in fields)
    fields["id"] = element_id
    db = await get_db()
    await db.execute(f"UPDATE elements SET {sets} WHERE id = :id", fields)
    await db.commit()


async def delete_element(element_id: str) -> bool:
    db = await get_db()
    cursor = await db.execute("DELETE FROM elements WHERE id = ?", (element_id,))
    await db.commit()
    return cursor.rowcount > 0


# --- Kapitel-Fortschritt ---

async def list_progress_all() -> dict[str, set[str]]:
    """Kompletter Kapitel-Fortschritt in einem Query: guide_id → Kapitel-Titel."""
    db = await get_db()
    cursor = await db.execute("SELECT guide_id, chapter FROM guide_progress")
    rows = await cursor.fetchall()
    out: dict[str, set[str]] = {}
    for guide_id, chapter in rows:
        out.setdefault(guide_id, set()).add(chapter)
    return out


async def list_progress(guide_id: str) -> list[str]:
    db = await get_db()
    cursor = await db.execute(
        "SELECT chapter FROM guide_progress WHERE guide_id = ?", (guide_id,)
    )
    rows = await cursor.fetchall()
    return [row[0] for row in rows]


async def set_progress(guide_id: str, chapter: str, done: bool) -> None:
    from datetime import datetime, timezone
    db = await get_db()
    if done:
        await db.execute(
            "INSERT OR IGNORE INTO guide_progress (guide_id, chapter, created_at) VALUES (?, ?, ?)",
            (guide_id, chapter, datetime.now(timezone.utc).isoformat()),
        )
    else:
        await db.execute(
            "DELETE FROM guide_progress WHERE guide_id = ? AND chapter = ?", (guide_id, chapter)
        )
    await db.commit()


async def delete_progress(guide_id: str) -> None:
    db = await get_db()
    await db.execute("DELETE FROM guide_progress WHERE guide_id = ?", (guide_id,))
    await db.commit()


# --- Baustein-Lernen: Vertiefungen + Prüfungs-Fortschritt ---

def _now() -> str:
    from datetime import datetime, timezone
    return datetime.now(timezone.utc).isoformat()


async def list_baustein_progress(topic: str) -> list[dict]:
    db = await get_db()
    cursor = await db.execute(
        "SELECT baustein, gute_antworten, streak, absolviert, verstanden, gemeistert FROM baustein_progress WHERE topic = ?", (topic,)
    )
    rows = await cursor.fetchall()
    return [{"baustein": b, "gute_antworten": n, "streak": s, "absolviert": a, "verstanden": v, "gemeistert": m} for b, n, s, a, v, m in rows]


async def get_baustein_progress(topic: str, baustein: str) -> dict:
    """Eine Baustein-Zeile inkl. Offene-Frage-Anker. Defaults, falls noch keine existiert."""
    db = await get_db()
    cursor = await db.execute(
        "SELECT gute_antworten, streak, absolviert, verstanden, gemeistert, "
        "offene_frage, offene_basis, offene_streak FROM baustein_progress "
        "WHERE topic = ? AND baustein = ?",
        (topic, baustein),
    )
    row = await cursor.fetchone()
    if row is None:
        return {"gute_antworten": 0, "streak": 0, "absolviert": None, "verstanden": None,
                "gemeistert": None, "offene_frage": None, "offene_basis": None, "offene_streak": None}
    return {"gute_antworten": row[0], "streak": row[1], "absolviert": row[2], "verstanden": row[3],
            "gemeistert": row[4], "offene_frage": row[5], "offene_basis": row[6], "offene_streak": row[7]}


async def set_offene_frage(topic: str, baustein: str, frage: str, basis: int, streak: int) -> None:
    """Friert Basis + Streak VOR der jetzt offenen Frage ein (Anker für idempotente Re-Bewertung)."""
    db = await get_db()
    now = _now()
    await db.execute(
        """INSERT INTO baustein_progress (topic, baustein, offene_frage, offene_basis, offene_streak, updated_at)
           VALUES (?, ?, ?, ?, ?, ?)
           ON CONFLICT(topic, baustein) DO UPDATE SET
               offene_frage = excluded.offene_frage, offene_basis = excluded.offene_basis,
               offene_streak = excluded.offene_streak, updated_at = excluded.updated_at""",
        (topic, baustein, frage, basis, streak, now),
    )
    await db.commit()


async def set_baustein_score(topic: str, baustein: str, score: int) -> int:
    """Setzt den Score absolut (vom Aufrufer geclampt) und liefert ihn zurück."""
    db = await get_db()
    await db.execute(
        """INSERT INTO baustein_progress (topic, baustein, gute_antworten, updated_at)
           VALUES (?, ?, ?, ?)
           ON CONFLICT(topic, baustein) DO UPDATE SET
               gute_antworten = excluded.gute_antworten, updated_at = excluded.updated_at""",
        (topic, baustein, score, _now()),
    )
    await db.commit()
    return score


async def set_baustein_streak(topic: str, baustein: str, streak: int) -> None:
    """Persistiert die Streak-Folge (Bonus) absolut — überlebt Reload/Session-Wechsel."""
    db = await get_db()
    await db.execute(
        """INSERT INTO baustein_progress (topic, baustein, streak, updated_at)
           VALUES (?, ?, ?, ?)
           ON CONFLICT(topic, baustein) DO UPDATE SET
               streak = excluded.streak, updated_at = excluded.updated_at""",
        (topic, baustein, streak, _now()),
    )
    await db.commit()


async def set_baustein_verstanden(topic: str, baustein: str) -> bool:
    """Markiert verstanden (Mastery); True nur beim ersten Mal. Sticky wie absolviert."""
    db = await get_db()
    now = _now()
    await db.execute(
        "INSERT OR IGNORE INTO baustein_progress (topic, baustein, gute_antworten, updated_at) VALUES (?, ?, 0, ?)",
        (topic, baustein, now),
    )
    cursor = await db.execute(
        "UPDATE baustein_progress SET verstanden = ?, updated_at = ? "
        "WHERE topic = ? AND baustein = ? AND verstanden IS NULL",
        (now, now, topic, baustein),
    )
    await db.commit()
    return cursor.rowcount > 0


async def set_baustein_gemeistert(topic: str, baustein: str) -> bool:
    """Markiert gemeistert (Meisterpfad, Score 25); True nur beim ersten Mal. Sticky."""
    db = await get_db()
    now = _now()
    await db.execute(
        "INSERT OR IGNORE INTO baustein_progress (topic, baustein, gute_antworten, updated_at) VALUES (?, ?, 0, ?)",
        (topic, baustein, now),
    )
    cursor = await db.execute(
        "UPDATE baustein_progress SET gemeistert = ?, updated_at = ? "
        "WHERE topic = ? AND baustein = ? AND gemeistert IS NULL",
        (now, now, topic, baustein),
    )
    await db.commit()
    return cursor.rowcount > 0


async def set_baustein_absolviert(topic: str, baustein: str) -> bool:
    """Markiert absolviert; True nur beim ersten Mal (steuert den Element-Task)."""
    db = await get_db()
    now = _now()
    await db.execute(
        "INSERT OR IGNORE INTO baustein_progress (topic, baustein, gute_antworten, updated_at) VALUES (?, ?, 0, ?)",
        (topic, baustein, now),
    )
    cursor = await db.execute(
        "UPDATE baustein_progress SET absolviert = ?, updated_at = ? "
        "WHERE topic = ? AND baustein = ? AND absolviert IS NULL",
        (now, now, topic, baustein),
    )
    await db.commit()
    return cursor.rowcount > 0


async def list_baustein_levels_all() -> dict[str, dict[str, set[str]]]:
    """Bausteine je Meilenstein in EINER Query: {"absolviert"/"verstanden"/"gemeistert": {topic → {Titel}}}."""
    db = await get_db()
    cursor = await db.execute(
        "SELECT topic, baustein, absolviert, verstanden, gemeistert FROM baustein_progress"
    )
    rows = await cursor.fetchall()
    levels: dict[str, dict[str, set[str]]] = {"absolviert": {}, "verstanden": {}, "gemeistert": {}}
    for topic, baustein, absolviert, verstanden, gemeistert in rows:
        if absolviert is not None:
            levels["absolviert"].setdefault(topic, set()).add(baustein)
        if verstanden is not None:
            levels["verstanden"].setdefault(topic, set()).add(baustein)
        if gemeistert is not None:
            levels["gemeistert"].setdefault(topic, set()).add(baustein)
    return levels


async def delete_baustein_daten(topic: str) -> None:
    db = await get_db()
    await db.execute("DELETE FROM baustein_texte WHERE topic = ?", (topic,))
    await db.execute("DELETE FROM baustein_progress WHERE topic = ?", (topic,))
    await db.commit()
