#!/usr/bin/env python3
"""Copy 'extra' sheets from the Series-A Ambitioniert reference workbook to the
400k Wandeldarlehen Base/Bull/Bear workbooks.

Source: Finanzplan-Series-A-Ambitioniert.xlsx
Sheets copied:
  - Charts            (153 rows + 12 chart objects)
  - Unit Economics    (Investor-KPIs incl. LTV/CAC, NRR)
  - Wandeldarlehen    (Tranchen-Konditionen)
  - Cohort-Analyse    (Retention pro Akquise-Monat)
  - Sensitivity       (Sensitivity-Analyse)
  - Hiring-Plan       (Quartal-Übersicht)

Also renames 'Net Dollar Retention' → 'Net Revenue Retention' and 'NDR' → 'NRR'
across all target files (and the source), since we calculate in Euro.

Cell values, number formats, basic styles, merged cells, column widths, row
heights, and freeze panes are copied. Charts are deep-copied — they may fail
silently if the openpyxl chart structure isn't fully serializable; a warning
is printed in that case.

Usage:
    python3 pitch-deck/scripts/copy-extra-sheets.py --dry-run
    python3 pitch-deck/scripts/copy-extra-sheets.py
"""

from __future__ import annotations

import argparse
import shutil
import sys
from copy import copy as shallow_copy
from copy import deepcopy
from datetime import datetime
from pathlib import Path

from openpyxl import load_workbook

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

SOURCE_FILE = "Finanzplan-Series-A-Ambitioniert.xlsx"

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

SHEETS_TO_COPY = [
    "Charts",
    "Unit Economics",
    "Wandeldarlehen",
    "Cohort-Analyse",
    "Sensitivity",
    "Hiring-Plan",
]

# Currency-neutral renames (Euro project, not USD)
RENAMES = {
    "Net Dollar Retention": "Net Revenue Retention",
    "NDR Annahme": "NRR Annahme",
    "• NDR steigt": "• NRR steigt",
    "NDR": "NRR",  # last; safety renames any remaining bare 'NDR'
}


def copy_cell_style(src_cell, dst_cell) -> None:
    if src_cell.has_style:
        dst_cell.font = shallow_copy(src_cell.font)
        dst_cell.fill = shallow_copy(src_cell.fill)
        dst_cell.border = shallow_copy(src_cell.border)
        dst_cell.alignment = shallow_copy(src_cell.alignment)
        dst_cell.number_format = src_cell.number_format
        dst_cell.protection = shallow_copy(src_cell.protection)


def copy_sheet_content(src_ws, dst_ws) -> None:
    """Copy cells, dimensions, merged ranges, freeze panes, and charts."""
    for row in src_ws.iter_rows():
        for src_cell in row:
            dst_cell = dst_ws.cell(row=src_cell.row, column=src_cell.column, value=src_cell.value)
            copy_cell_style(src_cell, dst_cell)

    # Column widths + hidden state
    for col_key, dim in src_ws.column_dimensions.items():
        d = dst_ws.column_dimensions[col_key]
        if dim.width:
            d.width = dim.width
        d.hidden = dim.hidden

    # Row heights + hidden state
    for row_key, dim in src_ws.row_dimensions.items():
        d = dst_ws.row_dimensions[row_key]
        if dim.height:
            d.height = dim.height
        d.hidden = dim.hidden

    # Merged cells
    for merged_range in list(src_ws.merged_cells.ranges):
        dst_ws.merge_cells(str(merged_range))

    # Freeze panes
    if src_ws.freeze_panes:
        dst_ws.freeze_panes = src_ws.freeze_panes

    # Charts (best-effort deepcopy)
    charts_ok = 0
    charts_fail = 0
    for chart in src_ws._charts:
        try:
            new_chart = deepcopy(chart)
            dst_ws.add_chart(new_chart)
            charts_ok += 1
        except Exception as e:  # noqa: BLE001
            charts_fail += 1
            print(f"      ⚠ chart copy failed: {type(e).__name__}: {e}", file=sys.stderr)
    if charts_ok or charts_fail:
        print(f"      charts: {charts_ok} ok, {charts_fail} failed")


def insert_sheet_before_formeluebersicht(wb, sheet_name: str) -> None:
    """If 'Formelübersicht' is in the workbook, move new sheet just before it."""
    if "Formelübersicht" not in wb.sheetnames:
        return
    new_idx = wb.sheetnames.index(sheet_name)
    fmt_idx = wb.sheetnames.index("Formelübersicht")
    if new_idx > fmt_idx:
        # move new sheet to just before Formelübersicht
        offset = (fmt_idx) - new_idx
        wb.move_sheet(sheet_name, offset=offset)


def rename_in_workbook(wb) -> int:
    """Apply NDR → NRR renames across all string cells. Returns count of changes."""
    n = 0
    for ws in wb.worksheets:
        for row in ws.iter_rows():
            for cell in row:
                if not isinstance(cell.value, str) or not cell.value:
                    continue
                new_value = cell.value
                for old, new in RENAMES.items():
                    if old in new_value:
                        new_value = new_value.replace(old, new)
                if new_value != cell.value:
                    cell.value = new_value
                    n += 1
    return n


def process_target(target_path: Path, src_wb, dry_run: bool) -> dict:
    wb = load_workbook(target_path)
    copied_sheets = []
    for sheet_name in SHEETS_TO_COPY:
        if sheet_name not in src_wb.sheetnames:
            print(f"    skip {sheet_name}: not in source")
            continue
        if sheet_name in wb.sheetnames:
            del wb[sheet_name]
        new_ws = wb.create_sheet(title=sheet_name)
        print(f"    copying {sheet_name} ...")
        copy_sheet_content(src_wb[sheet_name], new_ws)
        insert_sheet_before_formeluebersicht(wb, sheet_name)
        copied_sheets.append(sheet_name)

    renames = rename_in_workbook(wb)

    if not dry_run:
        wb.save(target_path)

    return {"copied": copied_sheets, "renames": renames}


def backup(path: Path) -> Path:
    ts = datetime.now().strftime("%Y%m%d-%H%M%S")
    bk = path.with_name(f"{path.stem}.BACKUP-pre-extra-sheets-{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")
    ap.add_argument("--skip-rename", action="store_true",
                    help="Don't apply NDR → NRR renames")
    args = ap.parse_args()

    if args.skip_rename:
        RENAMES.clear()

    source_path = EXPORTS / SOURCE_FILE
    if not source_path.exists():
        print(f"ERROR: source not found: {source_path}", file=sys.stderr)
        return 2
    src_wb = load_workbook(source_path)

    # Also rename in source
    if not args.dry_run:
        bk_src = backup(source_path)
        print(f"  ✓ source backup: {bk_src.name}")
    n_src_renames = rename_in_workbook(src_wb)
    if not args.dry_run:
        src_wb.save(source_path)
    print(f"  source ({SOURCE_FILE}): {n_src_renames} cell rename(s)")
    # Re-open source as the now-renamed version so target copies pick up new labels
    src_wb = load_workbook(source_path)

    for name in TARGETS:
        path = EXPORTS / name
        if not path.exists():
            print(f"\n  ⨯ skip {name}: not found")
            continue
        if not args.dry_run and not args.no_backup:
            bk = backup(path)
            print(f"\n  ✓ backup: {bk.name}")
        print(f"  === {name} ===")
        info = process_target(path, src_wb, dry_run=args.dry_run)
        print(f"    copied: {info['copied']}")
        print(f"    cell renames: {info['renames']}")
    return 0


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