from pathlib import Path
import math
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
                                PageBreak, Image, KeepTogether)

OUT = Path('/home/agent/insurance_work/life3a_questions_interactive')
OUT.mkdir(parents=True, exist_ok=True)
PDF = OUT / 'Helvetia_Lebensversicherung_2Prozent_Fragen_und_Einordnung.pdf'
CHART1 = OUT / 'technischer_zins_logik.png'
CHART2 = OUT / 'variantenvergleich_standard.png'
CHART3 = OUT / 'praemienstruktur.png'

# Contract/data basis
premium = 2400.0
premium_before_discount = 2674.30
capital_policy_premium = 2534.90
disability_premium = 139.40
discount = 274.30
surrender = 31090.65  # working value incl. surplus as of 01.05.2025
surrender_base = 29853.45
premium_free = 43736.0
guaranteed = 112662.0
start_year, end_year = 2025, 2050
r = 0.05
mortgage_r = 0.0077
annual = 2400.0

# Fonts
for fp, name in [('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf','DejaVuSans'),
                 ('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf','DejaVuSans-Bold')]:
    try: pdfmetrics.registerFont(TTFont(name, fp))
    except Exception: pass
FONT='DejaVuSans'; BOLD='DejaVuSans-Bold'

# Palette
NAVY='#17324D'; BLUE='#2F6BFF'; GREEN='#0F9D76'; RED='#C43D3D'; GOLD='#D49A28';
INK='#16212B'; MUTED='#667784'; LIGHT='#EEF3F6'; CREAM='#FAF7F0'; PURPLE='#7357C8'

plt.rcParams.update({'font.family':'DejaVu Sans','axes.titlesize':16,'axes.labelsize':10})

# Chart 1: flow logic
fig, ax = plt.subplots(figsize=(11.2,4.6), dpi=180)
ax.set_xlim(0,10); ax.set_ylim(0,4.6); ax.axis('off')
boxes=[
    (0.25,2.6,2.0,1.15,'Jahresprämie','CHF 2’400 effektiv'),
    (2.85,3.25,2.0,0.9,'Risikoschutz','Tod + Erwerbsunfähigkeit'),
    (2.85,2.05,2.0,0.9,'Kosten','Abschluss + Verwaltung'),
    (2.85,0.85,2.0,0.9,'Spar-/Deckungsteil','nicht separat ausgewiesen'),
    (5.55,0.85,1.75,0.9,'2,00 %','Rechnungszins in Tarifkalkulation'),
    (8.0,1.35,1.75,1.4,'Garantie 2050','CHF 112’662\nvertragliche Leistung'),
]
for x,y,w,h,t,s in boxes:
    fc = '#EAF0FF' if '2,00' in t else ('#E7F7F1' if 'Garantie' in t else 'white')
    ec = BLUE if '2,00' in t else (GREEN if 'Garantie' in t else '#B8C5CE')
    ax.add_patch(plt.Rectangle((x,y),w,h,facecolor=fc,edgecolor=ec,linewidth=1.8))
    ax.text(x+w/2,y+h*0.66,t,ha='center',va='center',fontsize=11,fontweight='bold',color=NAVY)
    ax.text(x+w/2,y+h*0.29,s,ha='center',va='center',fontsize=8.3,color=INK,wrap=True)
# arrows
for y2 in [3.7,2.5,1.3]:
    ax.annotate('',xy=(2.85,y2),xytext=(2.25,3.15),arrowprops=dict(arrowstyle='->',color=MUTED,lw=1.5))
ax.annotate('',xy=(5.55,1.3),xytext=(4.85,1.3),arrowprops=dict(arrowstyle='->',color=BLUE,lw=2))
ax.annotate('',xy=(8.0,2.05),xytext=(7.3,1.3),arrowprops=dict(arrowstyle='->',color=GREEN,lw=2))
ax.text(5.0,4.35,'Die 2 % sind eine Berechnungsgrundlage – keine 2-%-Nettorendite auf CHF 2’400',ha='center',va='top',fontsize=14,fontweight='bold',color=NAVY)
ax.text(5.0,0.2,'Die garantierte Leistung ist real. Entscheidend für den Vergleich ist aber die Rendite des gesamten Zahlungsstroms.',ha='center',fontsize=9.5,color=MUTED)
plt.tight_layout(); fig.savefig(CHART1,facecolor=CREAM); plt.close(fig)

# Chart 3: premium structure (known positions)
fig, ax = plt.subplots(figsize=(9.5,4.0), dpi=180)
labels=['Kapitalbildende Versicherung\n(inkl. Sparteil, Todesfallrisiko, Kosten)','Prämienbefreiung\nErwerbsunfähigkeit','Rabatt']
vals=[capital_policy_premium,disability_premium,-discount]
cols=[BLUE,GOLD,GREEN]
y=[2,1,0]
ax.barh(y, vals, color=cols, height=.58)
for yi,v in zip(y,vals):
    ax.text(v + (25 if v>=0 else -25), yi, f"CHF {abs(v):,.2f}".replace(',', '’'),
            ha='left' if v>=0 else 'right', va='center', fontweight='bold', color=INK)
ax.set_yticks(y,labels); ax.axvline(0,color='#8899A6',lw=.8)
ax.set_title('Ausgewiesene Prämienpositionen vor Rabatt',loc='left',fontweight='bold',color=NAVY)
ax.text(0,-.35,'Effektiv geschuldete Jahresprämie nach Rabatt: CHF 2’400',transform=ax.transAxes,fontsize=10,fontweight='bold',color=GREEN)
ax.spines[['top','right','left','bottom']].set_visible(False); ax.grid(axis='x',alpha=.15)
ax.set_xlim(-420,2850); ax.tick_params(axis='y',length=0)
plt.tight_layout(); fig.savefig(CHART3,facecolor='white',bbox_inches='tight'); plt.close(fig)

# Financial model for standard chart
years=list(range(start_year,end_year+1))
def contrib_fv(t, rate):
    return sum(annual*((1+rate)**(t-k)) for k in range(t))
def A(t): return surrender*(1+r)**t + contrib_fv(t,r)
def B(t): return premium_free + contrib_fv(t,r)  # future-benefit entitlement + depot, not liquid interim
def C(t): return surrender*(1+r)**t
def E(t):
    saving=surrender*mortgage_r
    return surrender + contrib_fv(t,r) + sum(saving*((1+r)**(t-k)) for k in range(t))
valsA=[A(y-start_year) for y in years]
valsB=[B(y-start_year) for y in years]
valsC=[C(y-start_year) for y in years]
valsE=[E(y-start_year) for y in years]
fig, ax=plt.subplots(figsize=(11.0,6.0),dpi=190)
ax.plot(years,valsA,color=BLUE,lw=3,label='A Transferwert + Folgebeiträge in Wertschriften')
ax.plot(years,valsB,color=GREEN,lw=3,label='B Prämienfreie Anwartschaft + Beitragsdepot')
ax.plot(years,valsE,color=GOLD,lw=3,label='E Hypothek tilgen + Beiträge + Zinsersparnis')
ax.plot(years,valsC,color=PURPLE,lw=2.3,label='C Nur Transferwert investieren')
ax.axhline(guaranteed,color=RED,lw=2,ls='--',label='D Garantie 2050 (nicht liquid vor Fälligkeit)')
ax.set_title('Standardvergleich: 5 % Wertschriftenrendite, 0,77 % Hypothekarzins',loc='left',fontweight='bold',color=NAVY)
ax.set_ylabel('Wirtschaftlicher / projizierter Wert (CHF)')
ax.yaxis.set_major_formatter(FuncFormatter(lambda x,pos:f'{x/1000:.0f}k'))
ax.grid(alpha=.18); ax.spines[['top','right']].set_visible(False)
ax.legend(loc='upper left',frameon=False,fontsize=8.5)
for series,col,name,dy in [(valsA,BLUE,'A',0),(valsB,GREEN,'B',0),(valsE,GOLD,'E',-3500),(valsC,PURPLE,'C',-3500)]:
    ax.text(2050.15,series[-1]+dy,f'{name}  CHF {series[-1]:,.0f}'.replace(',', '’'),color=col,va='center',fontsize=9,fontweight='bold')
ax.text(2049.7,guaranteed+4500,'D  CHF 112’662',ha='right',color=RED,fontsize=9,fontweight='bold')
ax.set_xlim(2025,2055); ax.set_ylim(0,max(valsA)*1.12)
ax.text(0,-.18,'Hinweis: B enthält eine garantierte Endleistungs-Anwartschaft; E ist ein wirtschaftlicher Bilanzwert, kein liquides Depot.',transform=ax.transAxes,fontsize=8.2,color=MUTED)
plt.tight_layout(); fig.savefig(CHART2,facecolor='white',bbox_inches='tight'); plt.close(fig)

# PDF styles
styles=getSampleStyleSheet()
styles.add(ParagraphStyle(name='DocTitle',fontName=BOLD,fontSize=22,leading=27,textColor=colors.HexColor(NAVY),spaceAfter=12))
styles.add(ParagraphStyle(name='Sub',fontName=FONT,fontSize=10.5,leading=15,textColor=colors.HexColor(MUTED),spaceAfter=12))
styles.add(ParagraphStyle(name='H1x',fontName=BOLD,fontSize=15,leading=19,textColor=colors.HexColor(NAVY),spaceBefore=7,spaceAfter=7))
styles.add(ParagraphStyle(name='H2x',fontName=BOLD,fontSize=11.5,leading=15,textColor=colors.HexColor(BLUE),spaceBefore=6,spaceAfter=4))
styles.add(ParagraphStyle(name='Bodyx',fontName=FONT,fontSize=9,leading=13,textColor=colors.HexColor(INK),spaceAfter=6))
styles.add(ParagraphStyle(name='Smallx',fontName=FONT,fontSize=7.7,leading=10.2,textColor=colors.HexColor(MUTED)))
styles.add(ParagraphStyle(name='Callout',fontName=BOLD,fontSize=10,leading=14,textColor=colors.HexColor(NAVY),backColor=colors.HexColor('#EAF0FF'),borderColor=colors.HexColor(BLUE),borderWidth=1,borderPadding=8,spaceBefore=6,spaceAfter=9))
styles.add(ParagraphStyle(name='Q',fontName=FONT,fontSize=8.6,leading=12,textColor=colors.HexColor(INK),leftIndent=12,firstLineIndent=-12,spaceAfter=5))

def P(txt, style='Bodyx'): return Paragraph(txt,styles[style])
def money(x): return 'CHF '+f'{x:,.0f}'.replace(',','’')

def table(data,widths=None,header=True):
    t=Table([[P(str(c),'Smallx') for c in row] for row in data],colWidths=widths,repeatRows=1 if header else 0,hAlign='LEFT')
    cmds=[('VALIGN',(0,0),(-1,-1),'TOP'),('GRID',(0,0),(-1,-1),.35,colors.HexColor('#CBD5DC')),('LEFTPADDING',(0,0),(-1,-1),5),('RIGHTPADDING',(0,0),(-1,-1),5),('TOPPADDING',(0,0),(-1,-1),5),('BOTTOMPADDING',(0,0),(-1,-1),5)]
    if header: cmds += [('BACKGROUND',(0,0),(-1,0),colors.HexColor(NAVY)),('TEXTCOLOR',(0,0),(-1,0),colors.white)]
    for rr in range(1 if header else 0,len(data)):
        if rr%2==0: cmds.append(('BACKGROUND',(0,rr),(-1,rr),colors.HexColor('#F5F8FA')))
    t.setStyle(TableStyle(cmds)); return t

story=[]
story += [P('Helvetia Lebensversicherung Säule 3a','DocTitle'),
          P('Die 2 % verstehen · garantierte und nicht garantierte Werte trennen · präzise Fragen an Helvetia', 'Sub'),
          P('<b>Kernaussage:</b> Die 2,00 % stehen im ursprünglichen Antrag als „in der Prämienberechnung bereits berücksichtigte Verzinsung“. Sie sind eine tarifliche Rechnungsgrundlage. Sie bedeuten nicht, dass die vollständige Jahresprämie von CHF 2’400 netto mit 2 % verzinst wird.', 'Callout'),
          Image(str(CHART1),width=18.0*cm,height=7.4*cm),Spacer(1,7),
          P('Was ist sicher belegt?','H1x'),
          table([['Unterlage / Wert','Aussage','Status'],
                 ['Antrag 2009','Verzinsung von 2,00 % bereits in der Prämienberechnung berücksichtigt','Belegt'],
                 ['Police','Garantiertes Kapital am 01.05.2050: CHF 112’662','Garantiert bei vertragsgemässer Weiterführung'],
                 ['Projektion 2009','Voraussichtliches Gesamtkapital CHF 163’230 inkl. Überschüsse','Nicht garantiert'],
                 ['Wertanfrage 2025','Rückkaufswert CHF 29’853.45; prämienfreie Versicherungssumme CHF 43’736','Stichtagswerte 2025; aktualisieren']], [4.0*cm,8.4*cm,5.0*cm]),
          Spacer(1,7),P('Die echte Garantie ist die vertragliche Leistung von CHF 112’662 im Jahr 2050. Die 2 % sind darin bereits verarbeitet und werden nicht zusätzlich ausbezahlt.','Bodyx')]

story += [PageBreak(),P('1. So ist die Prämie aufgebaut','H1x'),Image(str(CHART3),width=17.5*cm,height=7.2*cm),
          P('Der Antrag weist CHF 2’534.90 für die kapitalbildende Versicherung und CHF 139.40 für die Prämienbefreiung bei Erwerbsunfähigkeit aus. Nach einem Rabatt von CHF 274.30 beträgt die effektive Jahresprämie CHF 2’400.', 'Bodyx'),
          P('Nicht ausgewiesen ist die weitere Aufteilung der kapitalbildenden Position in reinen Sparanteil, Todesfallrisiko, Abschlusskosten und laufende Verwaltungskosten. Genau deshalb kann der technische Rechnungszins nicht direkt als persönliche Nettorendite interpretiert werden.','Callout'),
          P('2. Drei verschiedene Zinssätze – drei verschiedene Bedeutungen','H1x'),
          table([['Zinssatz','Unterlage','Bedeutung'],['2,00 %','Antrag 2009','Bereits in der ursprünglichen Prämien-/Garantiekalkulation berücksichtigt.'],['1,75 %','Überschussmitteilung 2022','Damals verwendete Kapitalrendite als Basis der Überschussbeteiligung.'],['0,50 %','Überschussmitteilung 2022','Verzinsung des bereits zugeteilten Überschusskontos.']], [2.5*cm,4.0*cm,10.9*cm]),
          Spacer(1,7),P('Nur bereits zugeteilte Überschüsse sind erworben. Künftige Überschüsse, der Schlussbonus und das 2009 projizierte Gesamtkapital von CHF 163’230 sind nicht garantiert.','Bodyx')]

questions=[
('Berechnungsbasis der 2 %','Auf welchen konkreten Teil meiner Prämie beziehungsweise meines Deckungskapitals werden die 2,00 % angewendet?'),
('Sparanteil','Wie hoch sind der aktuelle Sparanteil und das aktuelle Deckungskapital meiner Police?'),
('Kosten und Risiken','Welche jährlichen Beträge beziehungsweise Prozentsätze werden für Todesfallrisiko, Prämienbefreiung bei Erwerbsunfähigkeit, Abschluss-/Vertriebskosten, Verwaltungskosten und weitere Kosten belastet?'),
('Art der Garantie','Sind die 2,00 % eine eigenständige jährliche Gutschrift oder ausschliesslich eine Rechnungsgrundlage, die bereits im garantierten Erlebensfallkapital von CHF 112’662 enthalten ist?'),
('Dauer','Ist die 2-%-Rechnungsgrundlage für die gesamte Restlaufzeit unverändert garantiert? Bitte die konkrete Vertrags-/Tarifgrundlage nennen.'),
('Effektive Rendite','Welche effektive garantierte Rendite ergibt sich ab heute unter Einbezug des aktuellen Rückkaufs-/Transferwertes, aller verbleibenden Prämien und des garantierten Kapitals 2050?'),
('Aktueller Rückkaufswert','Wie hoch sind per aktuellem Stichtag der Rückkaufswert, die definitiv erworbenen Überschüsse und der effektive Netto-Transferwert?'),
('Prämienfreistellung','Wie hoch sind bei sofortiger Prämienfreistellung die garantierte Erlebensfallleistung, Todesfallleistung und allfällige weiterhin belastete Kosten?'),
('3a-Transfer','Welche Unterlagen sind für den direkten Transfer in eine andere anerkannte Säule-3a-Einrichtung erforderlich? Welche Gebühren oder Abzüge fallen an?'),
('Wohneigentum','Welche Unterlagen sind für die direkte Verwendung zur Amortisation der selbstbewohnten Hypothek erforderlich, und welcher Nettobetrag wird überwiesen?'),
('Überschüsse','Welche Überschüsse sind bereits definitiv erworben? Mit welcher aktuellen Überschuss- und Schlussbonusprojektion rechnet Helvetia heute – jeweils klar getrennt von garantierten Werten?'),
('Vergleich','Bitte eine tabellarische Gegenüberstellung Weiterführung, Prämienfreistellung, 3a-Transfer und Wohneigentumsbezug mit identischem Stichtag erstellen.')]
story += [PageBreak(),P('3. Fragenkatalog an Helvetia / Michele','H1x'),P('Ziel ist nicht, den technischen Zinssatz zu bestreiten. Ziel ist, die 2 % von der tatsächlich erzielbaren Kundenrendite zu trennen und alle Varianten auf demselben Stichtag vergleichbar zu machen.','Bodyx')]
for i,(h,q) in enumerate(questions,1):
    story.append(P(f'<b>{i}. {h}:</b> {q}','Q'))
story += [Spacer(1,8),P('Gewünschtes Antwortformat','H2x'),
          table([['Variante','Aktueller Wert','Künftige Prämien','Garantie 2050','Risikoschutz','Kosten/Abzüge'],['Weiterführen','…','…','…','…','…'],['Prämienfreistellen','…','keine','…','…','…'],['Direkter 3a-Transfer','…','frei für neue 3a','keine alte Garantie','entfällt','…'],['Wohneigentum/Hypothek','…','frei für neue 3a','keine alte Garantie','entfällt','…']], [3.2*cm,2.4*cm,3.0*cm,3.0*cm,2.7*cm,3.1*cm])]

story += [PageBreak(),P('4. Finanzielle Einordnung der Alternativen','H1x'),P('Die folgende Standardrechnung verwendet die zuletzt analysierten Werte per 01.05.2025: Arbeitswert Rückkauf inkl. Überschuss CHF 31’090.65, Jahresbeitrag CHF 2’400, Wertschriftenannahme 5 % und Hypothekarzins 0,77 %. Diese Werte sind im interaktiven HTML veränderbar.','Bodyx'),Image(str(CHART2),width=18.0*cm,height=9.8*cm),Spacer(1,6),
          table([['Variante','Modell-Endwert 2050','Einordnung'],['A Transfer + Beiträge',money(valsA[-1]),'Höchster erwarteter Endwert bei 5 %, aber Marktrisiko.'],['B Prämienfreistellung + Beiträge',money(valsB[-1]),'Garantieanteil bleibt; Folgebeiträge separat investiert.'],['E Hypothek + Beiträge + Zinsersparnis',money(valsE[-1]),'Defensiver Bilanzwert; stark abhängig vom Hypothekarzins.'],['C Nur Transferwert investieren',money(valsC[-1]),'Ohne Folgebeiträge; isolierter Vergleich.'],['D Police weiterführen',money(guaranteed),'Garantierte Leistung 2050 plus Risikoschutz.']], [5.2*cm,3.4*cm,8.8*cm]),
          Spacer(1,6),P('Interpretationsregel: Eine Alternative muss nicht den technischen Rechnungszins von 2 % „schlagen“, sondern den vollständigen Zahlungsstrom der Police nach Kosten und Risikoleistungen. Auf Basis der bisherigen Rechnung lag die garantierte Weiterführungsrendite ab dem damaligen Wertstand ungefähr bei 1,22–1,39 % pro Jahr.','Callout')]

story += [PageBreak(),P('5. Entscheidungslogik','H1x'),
          table([['Wenn dir am wichtigsten ist …','Dann zuerst prüfen','Warum'],['Maximaler erwarteter Vermögensaufbau','Direkter 3a-Transfer in günstige Wertschriftenlösung','Langer Horizont; Startkapital und Folgebeiträge arbeiten am Kapitalmarkt.'],['Garantie behalten, keine weitere Prämienbindung','Prämienfreistellung','Reduzierte garantierte Endleistung bleibt bestehen.'],['Schulden und Fixkosten senken','Wohneigentumsbezug / Hypothekaramortisation','Sichere Zinsersparnis, aber Kapital im Eigenheim gebunden.'],['Risikoschutz priorisieren','Bestehende Police weiterführen oder separat ersetzen','Todesfall- und Erwerbsunfähigkeitsschutz haben einen echten Wert.']], [5.2*cm,5.8*cm,6.4*cm]),
          Spacer(1,10),P('Vor einer Entscheidung','H1x'),
          P('1. Aktuellen Rückkaufs- und Netto-Transferwert mit identischem Stichtag einholen.<br/>2. Prämienfreie garantierte Werte schriftlich bestätigen lassen.<br/>3. Wegfallenden Todesfall-/Erwerbsunfähigkeitsschutz bewerten und bei Bedarf separat ersetzen.<br/>4. Erst danach Weiterführung, Transfer, Prämienfreistellung und Wohneigentumsbezug vergleichen.<br/>5. Nicht einfach die Zahlung einstellen; eine Prämienfreistellung/Umwandlung schriftlich verlangen.','Bodyx'),
          P('Zum interaktiven Rechner','H1x'),P('Das separate HTML enthält Schieberegler und Eingabefelder für Transferwert, Jahresbeitrag, Wertschriftenrendite, Hypothekarzins, garantierte Police und prämienfreie Leistung. Linien, Endwerte und Rangfolge aktualisieren sich sofort im Browser.','Bodyx'),
          P('<b>Datenstand:</b> Vertragsunterlagen 2009, Überschussmitteilung 2022, Wertanfrage 2025 und bisherige Variantenanalyse. Aktuelle Werte von Helvetia sind noch einzuholen.','Smallx')]

def footer(canvas,doc):
    canvas.saveState(); canvas.setStrokeColor(colors.HexColor('#D5DEE4')); canvas.line(1.5*cm,1.0*cm,A4[0]-1.5*cm,1.0*cm)
    canvas.setFont(FONT,7); canvas.setFillColor(colors.HexColor(MUTED)); canvas.drawString(1.5*cm,.65*cm,'Helvetia Säule 3a – 2 % verstehen und Varianten vergleichen')
    canvas.drawRightString(A4[0]-1.5*cm,.65*cm,f'Seite {doc.page}'); canvas.restoreState()

doc=SimpleDocTemplate(str(PDF),pagesize=A4,leftMargin=1.5*cm,rightMargin=1.5*cm,topMargin=1.35*cm,bottomMargin=1.25*cm,title='Helvetia Lebensversicherung – 2 Prozent, Fragen und Einordnung',author='JARVIS')
doc.build(story,onFirstPage=footer,onLaterPages=footer)
print(PDF)
print(PDF.stat().st_size)
