"""Generator für die Fabric-Notebook-Dokumentation.

Analog zu :mod:`core.dataflow_doc_generator`: enthält die eingebaute
Standard-Vorlage für ein eigenständiges Notebook-Dokument sowie die Funktionen,
die aus einem :class:`~core.models.NotebookInfo` das Platzhalter-Wörterbuch und
die Markdown-Tabellen/-Abschnitte erzeugen.

Die Vorlage wird – falls noch nicht vorhanden – als Datei neben der Anwendung
erzeugt und danach als Standard verwendet, sodass sie (wie die Berichts-,
Workspace- und Dataflow-Vorlagen) vom Benutzer angepasst werden kann.
"""
from __future__ import annotations

from core.models import NotebookCellInfo, NotebookInfo
from core.template_renderer import TemplateRenderer
from core.workspace_doc_generator import _escape_cell, ensure_template, load_template

# Dateiname der Standard-Vorlage (liegt neben der EXE / im Projektstamm)
NOTEBOOK_TEMPLATE_FILENAME = "WorkspaceNotebookvorlage.md"

__all__ = [
    "NOTEBOOK_TEMPLATE_FILENAME",
    "DEFAULT_NOTEBOOK_TEMPLATE",
    "ensure_template",
    "load_template",
    "build_cells_table",
    "build_languages_summary",
    "build_code_section",
    "build_markdown_section",
    "build_service_hint",
    "build_notebook_values",
    "render_notebook",
]


# ---------------------------------------------------------------------------
# Eingebaute Standard-Vorlage (deckungsgleich mit
# PowerBI_Fabric_Notebook_Dokumentationsvorlage.md im Projektstamm)
# ---------------------------------------------------------------------------

DEFAULT_NOTEBOOK_TEMPLATE = """# Microsoft Fabric Notebook-Dokumentation

> **Hinweis:** Diese Dokumentation ist verpflichtend für alle Produktiv-Notebooks.
> Nicht relevante Kapitel können leer bleiben, dürfen jedoch nicht entfernt werden.
> Automatisch befüllte Felder stammen aus dem Microsoft Fabric Service; mit
> _„Manuell zu ergänzen"_ markierte Felder sind fachlich zu vervollständigen.

---

## 1. Metadaten

| Feld | Wert |
|-----|------|
| Notebook-Name | {{NB_Name}} |
| Beschreibung | {{NB_Beschreibung}} |
| Notebook-ID | {{NB_ID}} |
| Web-URL | {{NB_WebUrl}} |
| Standardsprache | {{NB_DefaultLanguage}} |
| Standard-Lakehouse | {{NB_DefaultLakehouse}} |
| Erstellt / konfiguriert von | {{NB_ConfiguredBy}} |
| Zuletzt geändert | {{NB_ModifiedTime}} |
| Fachbereich | {{NB_Fachbereich}} |
| Konzerndomäne | {{NB_Konzerndomaene}} |
| Aktuelle Version | 1.0 |
| Status | Produktiv |
| Letzte Aktualisierung | {{NB_ExportDatum}} |

---

## 2. Ansprechpartner & Verantwortlichkeiten

| Rolle | Name / Team | Kontakt |
|-----|-------------|--------|
| Fachlicher Owner | {{KONTAKT_BusinessOwner}} | {{KONTAKT_BusinessOwnerMail}} |
| Technischer Owner | {{KONTAKT_TechOwner}} | {{KONTAKT_TechOwnerMail}} |
| Data Steward | {{KONTAKT_DataSteward}} | {{KONTAKT_DataStewardMail}} |
| Betrieb / Support | {{KONTAKT_Betrieb}} | {{KONTAKT_BetriebMail}} |

---

## 3. Business-Ziel & fachlicher Kontext

### 3.1 Zielsetzung
{{NB_Zielsetzung}}

### 3.2 Fachlicher Anwendungsfall
{{NB_Anwendungsfall}}

### 3.3 Nachgelagerte Verbraucher (Pipelines / Berichte / weitere Notebooks)
{{NB_Konsumenten}}

---

## 4. Aufbau & Struktur

### 4.1 Übersicht der Zellen

{{NB_ZellenTabelle}}

### 4.2 Verwendete Sprachen

{{NB_Sprachen}}

---

## 5. Datenquellen & Abhängigkeiten

### 5.1 Angebundene Lakehouses / Datenquellen

{{NB_DatenquellenTabelle}}

### 5.2 Detailbeschreibung je Datenquelle
{{NB_DatenquellenDetail}}

---

## 6. Code & Logik

{{NB_ServiceHinweis}}

### 6.1 Zellen-Inhalt (Code)

{{NB_CodeZellen}}

### 6.2 Dokumentation im Notebook (Markdown-Zellen)

{{NB_MarkdownDoku}}

### 6.3 Parameter & Konfiguration
{{NB_ParameterHinweis}}

### 6.4 Fehlerbehandlung & Datenqualität

- Umgang mit NULL-/Fehlwerten: {{NB_NullHandling}}
- Fehlerbehandlung (try/except, Retries): {{NB_ErrorHandling}}
- Datenvalidierungsregeln: {{NB_Validierungsregeln}}

### 6.5 Performance-Hinweise

- Spark-/Ressourcen-Konfiguration: {{NB_Ressourcen}}
- Bekannte Performance-Engpässe: {{NB_PerformanceHinweise}}
- Optimierungsmaßnahmen: {{NB_Optimierungen}}

---

## 7. Betrieb & Wartung

### 7.1 Ausführung & Orchestrierung

{{NB_Ausfuehrung}}

### 7.2 Monitoring & Fehlerbehandlung
{{NB_Monitoring}}

### 7.3 Bekannte Probleme / Technische Schulden
{{NB_BekannteProbleme}}

---

## 8. Sicherheit & Berechtigungen

### 8.1 Zugriff & Freigaben
{{NB_Zugriffe}}

### 8.2 Sensitivity Labels
{{NB_Labels}}

### 8.3 Risiken & Einschränkungen
{{NB_Risiken}}

---

## 9. Versionierung & Änderungsverlauf

### 9.1 Repository / Ablage
{{NB_Repository}}

### 9.2 Änderungsprotokoll

| Version | Datum | Änderung | Typ (fachlich/technisch) | Autor |
|-------|-------|---------|--------------------------|-------|
| 1.0 | {{NB_ExportDatum}} | Automatische Erst-Dokumentation | technisch | Auto-Dokumentation |

---

## 10. Freigabe & Review

| Rolle | Name | Datum | Kommentar |
|-----|------|-------|----------|
| Fachlich | {{REVIEW_FachName}} | {{REVIEW_FachDatum}} | {{REVIEW_FachKommentar}} |
| Technisch | {{REVIEW_TechName}} | {{REVIEW_TechDatum}} | {{REVIEW_TechKommentar}} |

---

## 11. Anhänge & Referenzen
- Übergeordnete Workspace-Dokumentation
- Architekturdiagramme
- Regulatorische Nachweise (separat)
"""


# ---------------------------------------------------------------------------
# Lokalisierungs-Hilfen
# ---------------------------------------------------------------------------

def _t(language: str, de: str, en: str) -> str:
    """Wählt den Text je nach Sprache (Default: Deutsch)."""
    return en if (language or "de").lower().startswith("en") else de


def _manual_hint(language: str) -> str:
    return _t(language, "_Manuell zu ergänzen_", "_To be completed_")


def _none(language: str) -> str:
    return _t(language, "–", "–")


# Zuordnung Zellsprache -> Markdown-Codeblock-Sprache (für das Syntax-Highlighting)
_FENCE_LANGUAGE = {
    "python": "python",
    "pyspark": "python",
    "sql": "sql",
    "sparksql": "sql",
    "scala": "scala",
    "spark": "scala",
    "r": "r",
    "sparkr": "r",
    "csharp": "csharp",
    "html": "html",
}


def _fence_language(language: str) -> str:
    """Ermittelt die Codeblock-Sprache für ein Zell-Sprachkürzel."""
    return _FENCE_LANGUAGE.get((language or "").strip().lower(), "")


def _preview(source: str) -> str:
    """Liefert die erste nicht-leere Zeile eines Zellinhalts (gekürzt)."""
    for line in (source or "").splitlines():
        stripped = line.strip()
        if stripped:
            return stripped[:80]
    return ""


# ---------------------------------------------------------------------------
# Tabellen-/Abschnitts-Generierung
# ---------------------------------------------------------------------------

def build_cells_table(cells: list[NotebookCellInfo], language: str = "de") -> str:
    """Baut die Übersichtstabelle der Notebook-Zellen."""
    if not cells:
        return _t(
            language,
            "_Keine Zellen verfügbar._",
            "_No cells available._",
        )
    header = _t(
        language,
        "| # | Typ | Sprache | Zeilen | Vorschau |",
        "| # | Type | Language | Lines | Preview |",
    )
    code_lbl = _t(language, "Code", "Code")
    md_lbl = _t(language, "Markdown", "Markdown")
    lines = [header, "|---|---|---|---|---|"]
    for cell in cells:
        cell_type = md_lbl if cell.cell_type == "markdown" else code_lbl
        line_count = len((cell.source or "").splitlines()) or (
            1 if (cell.source or "").strip() else 0
        )
        lines.append(
            f"| {cell.index} | {cell_type} | "
            f"{_escape_cell(cell.language) or '–'} | {line_count} | "
            f"{_escape_cell(_preview(cell.source)) or '–'} |"
        )
    return "\n".join(lines)


def build_languages_summary(
    cells: list[NotebookCellInfo], default_language: str = "", language: str = "de"
) -> str:
    """Baut eine Übersicht der im Notebook verwendeten Sprachen (Code-Zellen)."""
    counts: dict[str, int] = {}
    for cell in cells:
        if cell.cell_type != "code":
            continue
        lang = (cell.language or default_language or "").strip() or _t(
            language, "unbekannt", "unknown"
        )
        counts[lang] = counts.get(lang, 0) + 1
    if not counts:
        return _t(
            language,
            "_Keine Code-Zellen verfügbar._",
            "_No code cells available._",
        )
    header = _t(
        language,
        "| Sprache | Anzahl Code-Zellen |",
        "| Language | Code cells |",
    )
    lines = [header, "|---|---|"]
    for lang, count in sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])):
        lines.append(f"| {_escape_cell(lang)} | {count} |")
    return "\n".join(lines)


def build_code_section(
    cells: list[NotebookCellInfo], default_language: str = "", language: str = "de"
) -> str:
    """Baut den Abschnitt mit dem Quelltext je Code-Zelle (als Codeblöcke)."""
    blocks: list[str] = []
    for cell in cells:
        if cell.cell_type != "code":
            continue
        source = (cell.source or "").strip()
        if not source:
            continue
        lang = (cell.language or default_language or "").strip()
        fence = _fence_language(lang)
        heading = _t(language, "Zelle", "Cell")
        suffix = f" ({lang})" if lang else ""
        blocks.append(f"#### {heading} {cell.index}{suffix}\n\n```{fence}\n{source}\n```")
    if not blocks:
        return _t(
            language,
            "_Keine Code-Zellen verfügbar._",
            "_No code cells available._",
        )
    return "\n\n".join(blocks)


def build_markdown_section(
    cells: list[NotebookCellInfo], language: str = "de"
) -> str:
    """Baut den Abschnitt mit den Markdown-Zellen (Notebook-interne Doku)."""
    blocks: list[str] = []
    for cell in cells:
        if cell.cell_type != "markdown":
            continue
        source = (cell.source or "").strip()
        if not source:
            continue
        heading = _t(language, "Zelle", "Cell")
        blocks.append(f"#### {heading} {cell.index}\n\n{source}")
    if not blocks:
        return _t(
            language,
            "_Keine Markdown-Zellen verfügbar._",
            "_No markdown cells available._",
        )
    return "\n\n".join(blocks)


def build_service_hint(language: str = "de") -> str:
    """Baut den Hinweis zur Datenherkunft/Extraktion."""
    return _t(
        language,
        "> **Hinweis:** Der Quelltext wird aus der Notebook-Definition "
        "(`getDefinition` → `notebook-content.ipynb` bzw. `notebook-content.py`) "
        "des Microsoft Fabric Service gelesen. Ist der Export nicht verfügbar, "
        "bleiben die Code-Abschnitte leer.",
        "> **Note:** The source code is read from the notebook definition "
        "(`getDefinition` → `notebook-content.ipynb` or `notebook-content.py`) "
        "of the Microsoft Fabric service. If the export is unavailable, the "
        "code sections remain empty.",
    )


# ---------------------------------------------------------------------------
# Platzhalter-Werte & Rendering
# ---------------------------------------------------------------------------

# Manuell auszufüllende Platzhalter (erhalten einen lokalisierten Hinweis, damit
# das Dokument keine leeren Zellen enthält).
_MANUAL_PLACEHOLDERS = (
    "KONTAKT_BusinessOwner",
    "KONTAKT_BusinessOwnerMail",
    "KONTAKT_TechOwner",
    "KONTAKT_TechOwnerMail",
    "KONTAKT_DataSteward",
    "KONTAKT_DataStewardMail",
    "KONTAKT_Betrieb",
    "KONTAKT_BetriebMail",
    "NB_Fachbereich",
    "NB_Konzerndomaene",
    "NB_Zielsetzung",
    "NB_Anwendungsfall",
    "NB_Konsumenten",
    "NB_DatenquellenDetail",
    "NB_ParameterHinweis",
    "NB_NullHandling",
    "NB_ErrorHandling",
    "NB_Validierungsregeln",
    "NB_Ressourcen",
    "NB_PerformanceHinweise",
    "NB_Optimierungen",
    "NB_Monitoring",
    "NB_BekannteProbleme",
    "NB_Zugriffe",
    "NB_Labels",
    "NB_Risiken",
    "NB_Repository",
    "REVIEW_FachName",
    "REVIEW_FachDatum",
    "REVIEW_FachKommentar",
    "REVIEW_TechName",
    "REVIEW_TechDatum",
    "REVIEW_TechKommentar",
)


def build_notebook_values(
    nb: NotebookInfo, export_date: str, language: str = "de"
) -> dict[str, str]:
    """Baut das Platzhalter-Wörterbuch für die Notebook-Vorlage."""
    hint = _manual_hint(language)
    dash = _none(language)

    values: dict[str, str] = {name: hint for name in _MANUAL_PLACEHOLDERS}
    values.update(
        {
            "NB_Name": nb.name or dash,
            "NB_Beschreibung": nb.description or dash,
            "NB_ID": nb.id or dash,
            "NB_WebUrl": nb.web_url or dash,
            "NB_DefaultLanguage": nb.default_language or dash,
            "NB_DefaultLakehouse": nb.default_lakehouse or dash,
            "NB_ConfiguredBy": nb.configured_by or dash,
            "NB_ModifiedTime": nb.modified_time or dash,
            "NB_ExportDatum": export_date,
            "NB_ZellenTabelle": build_cells_table(nb.cells, language),
            "NB_Sprachen": build_languages_summary(
                nb.cells, nb.default_language, language
            ),
            "NB_CodeZellen": build_code_section(
                nb.cells, nb.default_language, language
            ),
            "NB_MarkdownDoku": build_markdown_section(nb.cells, language),
            "NB_ServiceHinweis": build_service_hint(language),
            "NB_DatenquellenTabelle": _t(
                language,
                "_Angebundene Datenquellen werden aus dem Code abgeleitet "
                "(siehe Abschnitt 6). Bitte fachlich ergänzen._",
                "_Attached data sources are derived from the code (see section "
                "6). Please complete from a business perspective._",
            ),
            "NB_Ausfuehrung": _t(
                language,
                "_Notebooks werden i. d. R. über Data-Pipelines oder Scheduler "
                "ausgeführt. Bitte Orchestrierung/Trigger fachlich ergänzen._",
                "_Notebooks are typically executed via data pipelines or a "
                "scheduler. Please complete the orchestration/trigger details._",
            ),
        }
    )
    return values


def render_notebook(
    template_content: str,
    nb: NotebookInfo,
    export_date: str,
    language: str = "de",
) -> str:
    """Rendert ein eigenständiges Notebook-Dokument anhand der Vorlage."""
    values = build_notebook_values(nb, export_date, language)
    rendered, _warnings = TemplateRenderer().render(template_content, values)
    return rendered
