#!/usr/bin/env python3
"""Add Tantieme driver + founder bonus logic + explanation text to Sensitivity/Cohort.

Changes per file (Wandeldarlehen-400k Base/Bull/Bear + Series-A):
  1. Treiber: append 'Tantieme Gründer' block (2028, 2029, 2030, default 0%).
  2. Personalkosten: modify rows 27 (Benjamin) and 28 (Sharang) for columns from
     Jan 2028 onwards — wrap base brutto in (1 + tantieme_for_year). Cols B-R
     (Aug 2026 to Dec 2027) stay untouched.
  3. Cohort-Analyse: add 'Erläuterung' block at the bottom explaining what the
     sheet shows and how to read it.
  4. Sensitivity: add 'Erläuterung' block explaining baseline column + reading.

Usage:
    python3 pitch-deck/scripts/add-tantieme-and-explanations.py --dry-run
    python3 pitch-deck/scripts/add-tantieme-and-explanations.py
"""

from __future__ import annotations

import argparse
import re
import shutil
import sys
from datetime import datetime
from pathlib import Path

from openpyxl import load_workbook
from openpyxl.styles import Alignment, Font

EXPORTS = Path(__file__).resolve().parent.parent / "exports"

TARGETS = [
    "Finanzplan-Series-A-Ambitioniert.xlsx",
    "Finanzplan-Wandeldarlehen-400k.xlsx",
    "Finanzplan-Wandeldarlehen-400k-Bull.xlsx",
    "Finanzplan-Wandeldarlehen-400k-Bear.xlsx",
]

# Founders detected by column-A label ('Benjamin', 'Sharang') — actual row varies
# by file (400k: rows 27/28, Series-A: rows 39/40).
FOUNDER_NAME_HINTS = ("Benjamin", "Sharang")
# Year when Tantieme starts (Jan)
TANTIEME_START_YEAR = 2028
TANTIEME_END_YEAR = 2030
TANTIEME_DEFAULT = 0.0  # 0% by default; user sets 0.20 to 1.00


COHORT_EXPLANATION = [
    "Erläuterung",
    "",
    "Was zeigt diese Tabelle? Jede Zeile entspricht einer Akquise-Kohorte (= alle",
    "Kunden, die in diesem Monat neu gewonnen wurden). Die Spalten M0, M1, M2 …",
    "zeigen, wie viele dieser Kunden nach 0, 1, 2 … Monaten noch aktiv sind.",
    "Die Werte sinken nach Akquise durch Churn (B5 = Retention pro Monat).",
    "",
    "Warum nützlich? Die Cohort-Analyse macht sichtbar, wie schnell Kunden",
    "abwandern und wie lang die durchschnittliche Customer Lifetime ist. Daraus",
    "ergibt sich der LTV: Lifetime (Monate) × ARPU × Bruttomarge.",
    "",
    "Beispiel: Bei monatlicher Churn-Rate 1,5% verbleiben nach 12 Monaten ~83%,",
    "nach 24 Monaten ~70%. Lifetime ≈ 1 / 0,015 = 67 Monate (~5,6 Jahre).",
    "Das Beobachtungsfenster ist auf 24 Monate begrenzt (B6 änderbar).",
]


SENSITIVITY_EXPLANATION = [
    "Erläuterung",
    "",
    "Was ist Spalte E? Der 'Baseline'-Wert für EBIT 2030 ($B$5 = GuV!F16). In",
    "jeder Zeile gleich, weil keine Variable verändert wird = Erwartung wenn",
    "alle Annahmen wie geplant. Wenn EBIT 2030 nicht sinnvoll erscheint, hängt",
    "das von Umsatz-, Personal- und Aufwand-Annahmen ab — siehe Treiber-Sheet.",
    "",
    "Was zeigen die Spalten -30% bis +30%? Pro Zeile (= eine Variable) wird",
    "diese eine Annahme um den genannten Prozentsatz variiert. Andere Annahmen",
    "bleiben Baseline. Ergebnis: EBIT 2030 unter dieser Variation.",
    "",
    "Beispiel: Zeile 'Monatliche Churn-Rate', Spalte '+30%'. Bedeutung: Wenn die",
    "Churn-Rate 30% schlechter ist als geplant, wie hoch wäre der EBIT? Die",
    "Differenz zur Baseline (E) = Sensitivität dieses Faktors.",
    "",
    "Wie interpretieren? Die Variable mit der größten Spannweite zwischen -30%",
    "und +30% ist am riskantesten (Tornado-Logik). 2D-Heatmap (Row 26+) zeigt",
    "kombinierte Effekte von Churn × ARPU auf LTV/CAC — gesund: > 3.",
    "",
    "Limitierung: One-at-a-Time ignoriert Wechselwirkungen (z.B. ändert sich",
    "Preis-Erhöhung → auch Churn). Für Investoren ist OAT trotzdem üblich.",
]


def add_tantieme_to_treiber(ws_t) -> dict[int, int]:
    """Append Tantieme driver block after row 81. Returns {year: row}."""
    start = ws_t.max_row + 2  # blank then header
    r = start
    ws_t.cell(row=r, column=1).value = "Tantieme Gründer (% Brutto, ab Jan 2028)"
    r += 1
    refs: dict[int, int] = {}
    for yr in range(TANTIEME_START_YEAR, TANTIEME_END_YEAR + 1):
        ws_t.cell(row=r, column=1).value = f"Tantieme Gründer {yr}"
        ws_t.cell(row=r, column=2).value = TANTIEME_DEFAULT
        ws_t.cell(row=r, column=2).number_format = "0%"
        refs[yr] = r
        r += 1
    return refs


def find_jan_2028_col(ws_pk) -> int:
    for c in range(2, ws_pk.max_column + 1):
        y = ws_pk.cell(row=1, column=c).value
        m = ws_pk.cell(row=2, column=c).value
        if y == 2028 and m == 1:
            return c
    raise RuntimeError("Could not locate Jan 2028 column in Personalkosten")


# Match the base brutto subexpression we want to wrap: $D$X*(1+$E$X/100)^(<col>$1-$G$X)
BASE_BRUTTO_RE = re.compile(
    r"(\$D\$(\d+)\*\(1\+\$E\$\2/100\)\^\(([A-Z]+)\$1-\$G\$\2\))"
)


def wrap_with_tantieme(formula: str, tantieme_first: int, tantieme_last: int) -> str | None:
    """Wrap the base-brutto subexpression with (1 + tantieme_for_year). Returns
    the new formula, or None if no match or already wrapped (idempotent).
    """
    # Idempotency: skip if a Tantieme multiplier with these rows already exists
    if f"INDEX(Treiber!$B${tantieme_first}:$B${tantieme_last}" in formula:
        return None
    m = BASE_BRUTTO_RE.search(formula)
    if not m:
        return None
    base_expr = m.group(1)
    col_letter = m.group(3)
    tantieme_factor = (
        f"(1+INDEX(Treiber!$B${tantieme_first}:$B${tantieme_last},"
        f"{col_letter}$1-{TANTIEME_START_YEAR - 1}))"
    )
    new_expr = f"{base_expr}*{tantieme_factor}"
    return formula.replace(base_expr, new_expr)


def find_founder_brutto_rows(ws_pk) -> list[int]:
    rows: list[int] = []
    for r in range(1, ws_pk.max_row + 1):
        a = ws_pk.cell(row=r, column=1).value
        if not isinstance(a, str):
            continue
        if "— Brutto" in a and any(hint in a for hint in FOUNDER_NAME_HINTS):
            rows.append(r)
    return rows


def apply_tantieme_to_personalkosten(ws_pk, refs: dict[int, int]) -> int:
    jan_2028 = find_jan_2028_col(ws_pk)
    tantieme_first = min(refs.values())
    tantieme_last = max(refs.values())
    rows = find_founder_brutto_rows(ws_pk)
    n = 0
    for row in rows:
        for c in range(jan_2028, ws_pk.max_column + 1):
            cell = ws_pk.cell(row=row, column=c)
            if not isinstance(cell.value, str) or not cell.value.startswith("="):
                continue
            new_formula = wrap_with_tantieme(cell.value, tantieme_first, tantieme_last)
            if new_formula is not None:
                cell.value = new_formula
                n += 1
    return n


def add_explanation_block(ws, lines: list[str], start_row: int) -> int:
    """Write lines starting at start_row, col A. Returns last row written."""
    bold = Font(bold=True, size=12)
    wrap = Alignment(wrap_text=True, vertical="top")
    r = start_row
    for line in lines:
        cell = ws.cell(row=r, column=1, value=line)
        if line == "Erläuterung":
            cell.font = bold
        else:
            cell.alignment = wrap
        r += 1
    return r - 1


def process_file(path: Path, dry_run: bool) -> dict:
    wb = load_workbook(path)
    stats: dict = {}

    # 1 — Tantieme driver in Treiber
    if "Treiber" not in wb.sheetnames or "Personalkosten" not in wb.sheetnames:
        return {"error": "missing required sheets"}
    # Detect existing Tantieme block to avoid duplicating
    ws_t = wb["Treiber"]
    existing_row = None
    for r in range(1, ws_t.max_row + 1):
        v = ws_t.cell(row=r, column=1).value
        if isinstance(v, str) and v.startswith("Tantieme Gründer (%"):
            existing_row = r
            break
    if existing_row:
        # Already present: read existing references, don't re-append
        refs = {}
        r = existing_row + 1
        for yr in range(TANTIEME_START_YEAR, TANTIEME_END_YEAR + 1):
            refs[yr] = r
            r += 1
        stats["tantieme_block"] = "preserved"
    else:
        refs = add_tantieme_to_treiber(ws_t)
        stats["tantieme_block"] = f"added rows {min(refs.values())}-{max(refs.values())}"

    # 2 — Apply Tantieme to founder Brutto rows
    ws_pk = wb["Personalkosten"]
    n_formulas = apply_tantieme_to_personalkosten(ws_pk, refs)
    stats["formulas_wrapped"] = n_formulas

    # 3 — Cohort-Analyse explanation
    if "Cohort-Analyse" in wb.sheetnames:
        ws_co = wb["Cohort-Analyse"]
        # Append at row 49 (after content at row 47). Idempotent: don't re-add.
        existing = any(
            ws_co.cell(row=r, column=1).value == "Erläuterung"
            for r in range(48, min(ws_co.max_row + 1, 70))
        )
        if not existing:
            last = add_explanation_block(ws_co, COHORT_EXPLANATION, start_row=49)
            stats["cohort_explanation"] = f"rows 49-{last}"
        else:
            stats["cohort_explanation"] = "already present"

    # 4 — Sensitivity explanation
    if "Sensitivity" in wb.sheetnames:
        ws_se = wb["Sensitivity"]
        existing = any(
            ws_se.cell(row=r, column=1).value == "Erläuterung"
            for r in range(38, min(ws_se.max_row + 1, 70))
        )
        if not existing:
            last = add_explanation_block(ws_se, SENSITIVITY_EXPLANATION, start_row=39)
            stats["sensitivity_explanation"] = f"rows 39-{last}"
        else:
            stats["sensitivity_explanation"] = "already present"

    if not dry_run:
        wb.save(path)
    return stats


def backup(path: Path) -> Path:
    ts = datetime.now().strftime("%Y%m%d-%H%M%S")
    bk = path.with_name(f"{path.stem}.BACKUP-pre-tantieme-{ts}{path.suffix}")
    shutil.copy2(path, bk)
    return bk


def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("--dry-run", action="store_true")
    ap.add_argument("--no-backup", action="store_true")
    args = ap.parse_args()

    for name in TARGETS:
        path = EXPORTS / name
        if not path.exists():
            print(f"  ⨯ skip {name}: not found")
            continue
        if not args.dry_run and not args.no_backup:
            bk = backup(path)
            print(f"  ✓ backup: {bk.name}")
        stats = process_file(path, dry_run=args.dry_run)
        print(f"\n  === {name} ===")
        for k, v in stats.items():
            print(f"    {k}: {v}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
