#!/usr/bin/env python3
"""Folie 7 – Integrationsserver / Event-Sourcing-Anbindung (DEUTSCH, adesso-Stil).
Eingaenge (Order Entry/CLAB, Probeneingang [Atras+PVT], Geraete, Middleware, MiBi/Hybase,
Stammdaten) -> Adapter-Ebene -> Event Store -> unten Event-Prozessoren, Projektionen,
Konsumenten (SinaBI, Archivierung). Hintergrund: solides dunkles Blau (BG)."""

import os
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE, MSO_CONNECTOR
from pptx.oxml.ns import qn
from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF,
                          F, FC, chrome)

BG   = RGBColor(0x0A, 0x3D, 0x6B)
GRAY = RGBColor(0xAE, 0xB7, 0xC2)
ADC  = RGBColor(0xCF, 0xE2, 0xF7)
SUBC = RGBColor(0x3C, 0x4A, 0x5A)   # zweite Zeile – dunkel & gut lesbar

prs = Presentation(); prs.slide_width = Inches(13.333); prs.slide_height = Inches(7.5)
SW, SH = prs.slide_width, prs.slide_height
s = prs.slides.add_slide(prs.slide_layouts[6])


def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), adj=None):
    sh = s.shapes.add_shape(kind, Inches(x), Inches(y), Inches(w), Inches(h))
    if fill is None: sh.fill.background()
    else: sh.fill.solid(); sh.fill.fore_color.rgb = fill
    if line is None: sh.line.fill.background()
    else: sh.line.color.rgb = line; sh.line.width = lw
    if adj is not None:
        try: sh.adjustments[0] = adj
        except Exception: pass
    sh.shadow.inherit = False
    return sh


def txt(x, y, w, h, t, size=11, color=WHITE, bold=False, italic=False,
        align=PP_ALIGN.LEFT, font=F, anchor=MSO_ANCHOR.MIDDLE):
    tb = s.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)); tf = tb.text_frame
    tf.word_wrap = True; tf.vertical_anchor = anchor
    tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0)
    first = True
    for line in t.split("\n"):
        p = tf.paragraphs[0] if first else tf.add_paragraph(); first = False
        p.alignment = align
        r = p.add_run(); r.text = line
        r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic
        r.font.name = font; r.font.color.rgb = color
    return tb


def conn(x1, y1, x2, y2, color=LIGHT, w=1.6, both=False):
    c = s.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, Inches(x1), Inches(y1),
                               Inches(x2), Inches(y2))
    c.line.color.rgb = color; c.line.width = Pt(w); c.shadow.inherit = False
    ln = c.line._get_or_add_ln()
    if both:
        ln.append(ln.makeelement(qn('a:headEnd'), {'type': 'triangle'}))
    ln.append(ln.makeelement(qn('a:tailEnd'), {'type': 'triangle'}))
    return c


def group(x, y, w, h, title, tsize=11.5):
    shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=None, line=LIGHT, lw=Pt(1.25), adj=0.04)
    txt(x, y + 0.05, w, 0.26, title, size=tsize, color=WHITE, bold=True,
        align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.TOP)


def box(x, y, w, h, title, sub=None, fill=WHITE, tcol=INK, tsize=10.5, line=None, lw=Pt(1)):
    shp(MSO_SHAPE.ROUNDED_RECTANGLE, x, y, w, h, fill=fill, line=line, lw=lw, adj=0.16)
    tb = s.shapes.add_textbox(Inches(x + 0.05), Inches(y), Inches(w - 0.10), Inches(h))
    tf = tb.text_frame; tf.word_wrap = True; tf.vertical_anchor = MSO_ANCHOR.MIDDLE
    tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0)
    p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER; p.line_spacing = 0.98
    r = p.add_run(); r.text = title; r.font.size = Pt(tsize); r.font.bold = True
    r.font.name = F; r.font.color.rgb = tcol
    if sub is not None:
        p.space_after = Pt(2)
        p2 = tf.add_paragraph(); p2.alignment = PP_ALIGN.CENTER; p2.line_spacing = 0.98
        r2 = p2.add_run(); r2.text = sub; r2.font.size = Pt(tsize - 0.5)
        r2.font.name = F; r2.font.color.rgb = SUBC
    return tb


AD_Y, AD_H = 3.50, 0.48
def adapter(x, w, title):
    box(x, AD_Y, w, AD_H, title, fill=ADC, tcol=INK, tsize=9, line=BLUE, lw=Pt(1.1))


# ---- Hintergrund + Header --------------------------------------------------
shp(MSO_SHAPE.RECTANGLE, 0, 0, SW.inches, SH.inches, fill=BG)
chrome(s, "Event Sourcing — Integrationsserver", 8)

# ---- Legende ---------------------------------------------------------------
ly = 0.95
def legsq(x, color, label, lw_txt):
    shp(MSO_SHAPE.RECTANGLE, x, ly + 0.02, 0.15, 0.15, fill=color)
    txt(x + 0.21, ly - 0.03, lw_txt, 0.26, label, size=10, color=WHITE, anchor=MSO_ANCHOR.MIDDLE)
legsq(0.40, CORAL, "Event Store", 1.2)
legsq(1.76, GREEN, "Prop-Limbach", 1.3)
legsq(3.28, ADC, "Adapter (own)", 1.4)
legsq(4.90, WHITE, "System", 0.9)
txt(6.30, 0.92, 6.63, 0.3,
    "↕ publizieren & abonnieren   ↓ publizieren   grau = Kommunikation",
    size=10, color=LIGHT, italic=True, anchor=MSO_ANCHOR.MIDDLE, align=PP_ALIGN.RIGHT)

# ---- EVENT STORE -----------------------------------------------------------
ST_X, ST_Y, ST_W, ST_H = 0.40, 4.18, 12.53, 0.60
shp(MSO_SHAPE.ROUNDED_RECTANGLE, ST_X, ST_Y, ST_W, ST_H, fill=CORAL, line=INK, lw=Pt(3.5), adj=0.10)
txt(ST_X, ST_Y + 0.06, ST_W, 0.30, "Event Store  —  append-only Event-Log",
    size=15, color=INK, bold=True, align=PP_ALIGN.CENTER)
txt(ST_X, ST_Y + 0.35, ST_W, 0.22, "kanonische Wahrheit · Replay-fähig   ·   (Kafka)",
    size=10.5, color=INK, align=PP_ALIGN.CENTER)

# ===========================================================================
# OBEN – Systeme + Adapter-Ebene
# ===========================================================================
TGY, TGH = 1.28, 2.78

# --- 1: Order Entry ueber CLAB ---------------------------------------------
group(0.40, TGY, 2.70, TGH, "Order Entry über CLAB")
box(0.52, 1.62, 1.16, 0.42, "LabAccess")
box(1.82, 1.62, 1.16, 0.42, "MDN")
box(1.18, 2.22, 1.14, 0.44, "AB+M")
box(1.05, 2.84, 1.40, 0.42, "CLAB (LIS)", fill=GREEN)
adapter(0.78, 1.94, "CLAB-Adapter")
conn(1.10, 2.04, 1.55, 2.22); conn(2.40, 2.04, 1.95, 2.22)
conn(1.75, 2.66, 1.75, 2.84); conn(1.75, 3.26, 1.75, AD_Y)
conn(1.75, AD_Y + AD_H, 1.75, ST_Y, both=True)

# --- 2: Probeneingang (Atras SL + PVT) -------------------------------------
group(3.26, TGY, 1.95, TGH, "Probeneingang")
box(3.34, 2.30, 0.86, 0.52, "Atras SL")
box(4.27, 2.30, 0.86, 0.52, "PVT")
adapter(3.34, 1.79, "Adapter (Probe → Event)")
conn(2.32, 2.50, 3.34, 2.56, color=GRAY, both=True)       # Praeanalytik-Kommunikation
txt(2.28, 2.04, 1.30, 0.34, "Präanalytik-\nKomm.", size=8, color=GRAY, italic=True,
    align=PP_ALIGN.CENTER)
conn(3.77, 2.82, 3.95, AD_Y); conn(4.70, 2.82, 4.55, AD_Y)
conn(4.235, AD_Y + AD_H, 4.235, ST_Y)

# --- 3: Geraete -------------------------------------------------------------
group(5.37, TGY, 1.78, TGH, "Geräte-Adapter")
box(5.49, 2.10, 1.54, 0.70, "Geräte", "Sysmex · Analyzer")
adapter(5.45, 1.62, "Adapter (Device → Event)")
conn(6.26, 2.80, 6.26, AD_Y); conn(6.26, AD_Y + AD_H, 6.26, ST_Y, both=True)

# --- 4: Middleware ----------------------------------------------------------
group(7.31, TGY, 1.78, TGH, "Middleware")
box(7.43, 2.10, 1.54, 0.70, "Middleware-Systeme", "Myla/Maestria · LabOps · Liason")
adapter(7.39, 1.62, "Adapter (Middleware → Event)")
conn(8.20, 2.80, 8.20, AD_Y); conn(8.20, AD_Y + AD_H, 8.20, ST_Y, both=True)

# --- 5: MiBi (Hybase) -------------------------------------------------------
group(9.25, TGY, 1.70, TGH, "MiBi")
box(9.37, 2.10, 1.46, 0.70, "Hybase", "Resistenz / ID-Daten")
adapter(9.33, 1.54, "Adapter (MiBi → Event)")
conn(10.10, 2.80, 10.10, AD_Y); conn(10.10, AD_Y + AD_H, 10.10, ST_Y, both=True)

# --- 6: Stammdaten ----------------------------------------------------------
group(11.11, TGY, 1.82, TGH, "Stammdaten")
box(11.23, 2.10, 1.58, 0.70, "Stammdaten-Quellen", "SinaCRM · OTS/Znooni")
adapter(11.19, 1.66, "Adapter (MasterData → Event)")
conn(12.02, 2.80, 12.02, AD_Y); conn(12.02, AD_Y + AD_H, 12.02, ST_Y)

# ===========================================================================
# UNTEN – Event-Prozessoren · Projektionen · Konsumenten
# ===========================================================================
BGY, BGH = 4.96, 1.96

group(0.40, BGY, 2.95, BGH, "Event-Prozessoren")
box(0.56, 5.42, 2.63, 0.55, "Mapping / Transformation")
box(0.56, 6.12, 2.63, 0.55, "Routing Engine")
conn(1.85, ST_Y + ST_H, 1.85, BGY, both=True)

group(3.50, BGY, 5.85, BGH, "Projektionen")
box(3.66, 5.46, 1.78, 1.02, "Auftragsstatus")
box(5.56, 5.46, 1.78, 1.02, "Audit Trail")
box(7.46, 5.46, 1.73, 1.02, "Reporting")
conn(6.40, ST_Y + ST_H, 6.40, BGY)

group(9.50, BGY, 3.43, BGH, "Konsumenten")
box(9.66, 5.38, 3.11, 0.68, "SinaBI (Power BI)", "Reporting / BI")
box(9.66, 6.12, 3.11, 0.68, "Archivierung · Vaultbox", "Roche Navify · LDT2")
conn(11.21, ST_Y + ST_H, 11.21, BGY)

out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie7.pptx")
prs.save(out)
print("saved", out)
