"""Generate SANEF Patching presentation (PPTX) from the PDF source material."""
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_SHAPE
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.oxml.ns import qn
from copy import deepcopy

# --- Couleurs SANEF / cyber ---
ACCENT = RGBColor(0x00, 0xA3, 0xC4)       # cyan principal
ACCENT_DARK = RGBColor(0x00, 0x6F, 0x8C)
DARK = RGBColor(0x1A, 0x1A, 0x2E)         # noir profond
LIGHT_BG = RGBColor(0xF5, 0xF9, 0xFC)
RED_ALERT = RGBColor(0xC0, 0x39, 0x2B)
ORANGE = RGBColor(0xE0, 0x8A, 0x0A)
GREEN = RGBColor(0x28, 0xA7, 0x45)
GRAY = RGBColor(0x66, 0x66, 0x66)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
LIGHT_GRAY = RGBColor(0xEE, 0xEE, 0xEE)

prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
SW, SH = prs.slide_width, prs.slide_height

blank_layout = prs.slide_layouts[6]


def set_bg(slide, color):
    bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SW, SH)
    bg.fill.solid()
    bg.fill.fore_color.rgb = color
    bg.line.fill.background()
    bg.shadow.inherit = False
    slide.shapes._spTree.remove(bg._element)
    slide.shapes._spTree.insert(2, bg._element)
    return bg


def add_rect(slide, x, y, w, h, fill=None, line=None, line_w=None):
    shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, y, w, h)
    if fill is None:
        shape.fill.background()
    else:
        shape.fill.solid()
        shape.fill.fore_color.rgb = fill
    if line is None:
        shape.line.fill.background()
    else:
        shape.line.color.rgb = line
        if line_w:
            shape.line.width = line_w
    shape.shadow.inherit = False
    return shape


def add_text(slide, x, y, w, h, text, *, size=18, bold=False, color=DARK,
             align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, font="Calibri"):
    tb = slide.shapes.add_textbox(x, y, w, h)
    tf = tb.text_frame
    tf.word_wrap = True
    tf.margin_left = Emu(50000)
    tf.margin_right = Emu(50000)
    tf.margin_top = Emu(30000)
    tf.margin_bottom = Emu(30000)
    tf.vertical_anchor = anchor
    lines = text.split("\n") if isinstance(text, str) else text
    for i, ln in enumerate(lines):
        p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
        p.alignment = align
        r = p.add_run()
        r.text = ln
        r.font.name = font
        r.font.size = Pt(size)
        r.font.bold = bold
        r.font.color.rgb = color
    return tb


def add_title_bar(slide, title, subtitle=None):
    # top accent bar
    add_rect(slide, 0, 0, SW, Inches(0.15), fill=ACCENT)
    # title
    add_text(slide, Inches(0.5), Inches(0.3), Inches(12), Inches(0.7), title,
             size=28, bold=True, color=DARK)
    if subtitle:
        add_text(slide, Inches(0.5), Inches(0.95), Inches(12), Inches(0.45), subtitle,
                 size=14, color=GRAY)
    # divider
    add_rect(slide, Inches(0.5), Inches(1.45), Inches(1.5), Emu(30000), fill=ACCENT)


def add_footer(slide, page_num):
    add_text(slide, Inches(0.5), Inches(7.05), Inches(7), Inches(0.3),
             "SANEF DSI / SECOPS — Processus Patching & Automatisation",
             size=9, color=GRAY)
    add_text(slide, Inches(11.5), Inches(7.05), Inches(1.3), Inches(0.3),
             f"{page_num}", size=9, color=GRAY, align=PP_ALIGN.RIGHT)


def add_bullets(slide, x, y, w, h, items, *, size=16, color=DARK, line_spacing=1.2):
    tb = slide.shapes.add_textbox(x, y, w, h)
    tf = tb.text_frame
    tf.word_wrap = True
    for i, item in enumerate(items):
        if isinstance(item, tuple):
            bullet_color, text = item
        else:
            bullet_color, text = ACCENT, item
        p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
        p.alignment = PP_ALIGN.LEFT
        p.line_spacing = line_spacing
        r1 = p.add_run()
        r1.text = "▸ "
        r1.font.size = Pt(size)
        r1.font.bold = True
        r1.font.color.rgb = bullet_color
        r1.font.name = "Calibri"
        r2 = p.add_run()
        r2.text = text
        r2.font.size = Pt(size)
        r2.font.color.rgb = color
        r2.font.name = "Calibri"
    return tb


def add_table(slide, x, y, w, h, header, rows, *,
              header_color=ACCENT, alt_row=RGBColor(0xF5, 0xF9, 0xFC),
              header_text=WHITE, font_size=12, header_size=13):
    ncols = len(header)
    nrows = len(rows) + 1
    tbl = slide.shapes.add_table(nrows, ncols, x, y, w, h).table
    # Header row
    for ci, htxt in enumerate(header):
        c = tbl.cell(0, ci)
        c.fill.solid()
        c.fill.fore_color.rgb = header_color
        c.text = ""
        tf = c.text_frame
        tf.margin_left = Emu(50000); tf.margin_right = Emu(50000)
        tf.margin_top = Emu(40000); tf.margin_bottom = Emu(40000)
        p = tf.paragraphs[0]
        p.alignment = PP_ALIGN.CENTER
        r = p.add_run(); r.text = htxt
        r.font.size = Pt(header_size); r.font.bold = True
        r.font.color.rgb = header_text; r.font.name = "Calibri"
    # Data rows
    for ri, row in enumerate(rows, start=1):
        for ci, cell_data in enumerate(row):
            c = tbl.cell(ri, ci)
            c.fill.solid()
            c.fill.fore_color.rgb = alt_row if ri % 2 == 0 else WHITE
            if isinstance(cell_data, tuple):
                text, color = cell_data
            else:
                text, color = cell_data, DARK
            c.text = ""
            tf = c.text_frame
            tf.margin_left = Emu(50000); tf.margin_right = Emu(50000)
            tf.margin_top = Emu(30000); tf.margin_bottom = Emu(30000)
            tf.word_wrap = True
            p = tf.paragraphs[0]
            p.alignment = PP_ALIGN.LEFT if ci == 0 else PP_ALIGN.CENTER
            r = p.add_run(); r.text = text
            r.font.size = Pt(font_size)
            r.font.color.rgb = color
            r.font.name = "Calibri"
            if ci == 0:
                r.font.bold = True
    return tbl


def add_callout(slide, x, y, w, h, title, body, *, bg=LIGHT_BG, border=ACCENT,
                title_color=ACCENT_DARK):
    # left accent bar
    add_rect(slide, x, y, Emu(70000), h, fill=border)
    # box
    box = add_rect(slide, x + Emu(70000), y, w - Emu(70000), h, fill=bg)
    # title
    add_text(slide, x + Inches(0.25), y + Inches(0.1), w - Inches(0.4), Inches(0.4),
             title, size=14, bold=True, color=title_color)
    # body
    add_text(slide, x + Inches(0.25), y + Inches(0.5), w - Inches(0.4), h - Inches(0.6),
             body, size=12, color=DARK)


def add_kpi(slide, x, y, w, h, value, label, color=ACCENT):
    add_rect(slide, x, y, w, h, fill=WHITE, line=color, line_w=Pt(1.5))
    add_text(slide, x, y + Inches(0.2), w, Inches(0.8), value,
             size=36, bold=True, color=color, align=PP_ALIGN.CENTER)
    add_text(slide, x, y + Inches(1.0), w, Inches(0.4), label,
             size=12, color=GRAY, align=PP_ALIGN.CENTER)


# ============================================================
# SLIDE 1 — Title
# ============================================================
slide = prs.slides.add_slide(blank_layout)
set_bg(slide, DARK)
# Accent graphic
add_rect(slide, 0, Inches(2.8), SW, Inches(0.05), fill=ACCENT)
add_rect(slide, Inches(5.5), Inches(2.3), Inches(2.3), Inches(0.05), fill=ACCENT)

add_text(slide, Inches(0.8), Inches(1.5), Inches(11.5), Inches(1),
         "PROCESSUS DE PATCHING", size=46, bold=True, color=WHITE)
add_text(slide, Inches(0.8), Inches(2.4), Inches(11.5), Inches(0.7),
         "Manuel vs Automatisation", size=26, color=ACCENT, bold=False)

add_text(slide, Inches(0.8), Inches(3.2), Inches(11.5), Inches(0.5),
         "État des lieux, charge réelle du patcheur & apport de PatchCenter Web",
         size=18, color=LIGHT_GRAY)

# Bottom info
add_rect(slide, 0, Inches(6.5), SW, Inches(1), fill=RGBColor(0x0a, 0x14, 0x24))
add_text(slide, Inches(0.8), Inches(6.65), Inches(6), Inches(0.4),
         "SANEF DSI / Sécurité Opérationnelle", size=14, bold=True, color=WHITE)
add_text(slide, Inches(0.8), Inches(7.0), Inches(6), Inches(0.3),
         "Présentation DSI — Avril 2026", size=11, color=LIGHT_GRAY)
add_text(slide, Inches(9), Inches(6.65), Inches(3.8), Inches(0.4),
         "PatchCenter Web v1", size=14, bold=True, color=ACCENT, align=PP_ALIGN.RIGHT)
add_text(slide, Inches(9), Inches(7.0), Inches(3.8), Inches(0.3),
         "Équipe SECOPS", size=11, color=LIGHT_GRAY, align=PP_ALIGN.RIGHT)


# ============================================================
# SLIDE 2 — Sommaire
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Sommaire", "Déroulé de la présentation")

items = [
    ("1.", "Vue d'ensemble du processus de patching"),
    ("2.", "Phase 1 — Affectation (COMEP jeudi)"),
    ("3.", "Phase 2 — Préparation (jeudi PM + vendredi)"),
    ("4.", "Phase 3 — Exécution Jour J (lundi → jeudi)"),
    ("5.", "Phase 4 — Nettoyage (vendredi)"),
    ("6.", "Synthèse — Temps par serveur"),
    ("7.", "La réalité du patcheur — budget 35 h / semaine"),
    ("8.", "Équation insoluble en mode manuel"),
    ("9.", "Conséquence sécurité — alertes S1 & Defender"),
    ("10.", "Apport des outils — SQATM .exe & PatchCenter Web"),
    ("11.", "Comparaison chiffrée et impact hebdomadaire"),
    ("12.", "Feuille de route & conclusion"),
]
for i, (num, txt) in enumerate(items):
    y = Inches(1.8 + 0.38 * i)
    add_text(slide, Inches(0.8), y, Inches(0.6), Inches(0.4), num,
             size=15, bold=True, color=ACCENT)
    add_text(slide, Inches(1.4), y, Inches(11), Inches(0.4), txt,
             size=15, color=DARK)
add_footer(slide, 2)


# ============================================================
# SLIDE 3 — Vue d'ensemble
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Vue d'ensemble", "4 phases étalées sur la semaine")

phases = [
    ("1. Affectation", "Jeudi 14h00\nCOMEP SECOPS", ACCENT),
    ("2. Préparation", "Jeudi PM + Vendredi\nQualification + validation", ORANGE),
    ("3. Exécution", "Lundi → Jeudi\nPatching + vérifications", RED_ALERT),
    ("4. Nettoyage", "Vendredi (J+3)\nSnapshots + kernels", GREEN),
]
x0 = Inches(0.6)
card_w = Inches(3.0)
card_h = Inches(3.5)
gap = Inches(0.15)
for i, (title, body, col) in enumerate(phases):
    x = x0 + (card_w + gap) * i
    y = Inches(2.2)
    # Card with colored top
    add_rect(slide, x, y, card_w, Inches(0.6), fill=col)
    add_rect(slide, x, y + Inches(0.6), card_w, card_h - Inches(0.6), fill=WHITE, line=LIGHT_GRAY)
    add_text(slide, x, y + Inches(0.1), card_w, Inches(0.5), title,
             size=18, bold=True, color=WHITE, align=PP_ALIGN.CENTER)
    # Number big
    add_text(slide, x, y + Inches(0.9), card_w, Inches(1.2), str(i + 1),
             size=72, bold=True, color=col, align=PP_ALIGN.CENTER)
    # Body
    add_text(slide, x + Inches(0.2), y + Inches(2.2), card_w - Inches(0.4), Inches(1.2),
             body, size=13, color=DARK, align=PP_ALIGN.CENTER)

add_callout(slide, Inches(0.6), Inches(5.9), Inches(12.1), Inches(0.8),
            "Règle de cadrage",
            "Pas de patching le vendredi — évite un incident qui courrait tout le weekend. "
            "Le vendredi = finalisation pré-patching + nettoyage snapshots + iTop / météo.")
add_footer(slide, 3)


# ============================================================
# SLIDE 4 — Affectation COMEP
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Phase 1 — Affectation", "Jeudi 14h00 — COMEP SECOPS")

add_bullets(slide, Inches(0.7), Inches(1.9), Inches(12), Inches(2.5), [
    "COMEP du jeudi après-midi : répartition des serveurs à patcher entre les intervenants",
    "Chaque intervenant reçoit sa liste pour la semaine suivante",
    "Objectif cible par intervenant : 20 serveurs patchés sur la semaine",
    "La phase de qualification démarre immédiatement après le COMEP (jeudi PM) puis toute la journée de vendredi",
], size=16)

add_callout(slide, Inches(0.7), Inches(5.0), Inches(12), Inches(1.5),
            "Livrable attendu",
            "Liste de 20 serveurs par intervenant, avec domaine, environnement, "
            "OS, responsable applicatif et criticité — prête pour la qualification.")
add_footer(slide, 4)


# ============================================================
# SLIDE 5 — Préparation (pré-patching)
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Phase 2 — Préparation (pré-patching)",
              "Jeudi après-midi + Vendredi — 13 étapes par serveur")

header = ["Étape", "Action", "Durée"]
rows = [
    ("1", "Recherche serveur dans console Qualys", "1 min"),
    ("2-3", "Lecture + analyse des vulnérabilités", "5 min"),
    ("4", "Connexion SSH (PuTTY)", "1 min"),
    ("5", "Vérification espace disque (df -h)", "1 min"),
    ("6", "Vérification connexion satellite", "1 min"),
    ("7", "Dry-run yum check-update", "2-3 min"),
    ("8", "Vérification accès vCenter", "2 min"),
    ("9", "Vérification backup Commvault (si physique)", "2 min"),
    ("10", "Vérification Centreon (si production)", "2 min"),
    ("11-12", "Contact responsable + proposition créneaux", "5-10 min"),
    ("13", "Attente validation du créneau (asynchrone)", "variable"),
]
add_table(slide, Inches(0.5), Inches(1.8), Inches(7.5), Inches(4.6),
          header, rows, font_size=11, header_size=12)

# KPI right
add_kpi(slide, Inches(8.3), Inches(2.0), Inches(4.5), Inches(1.6),
        "20-30 min", "Par serveur (travail actif)", color=ACCENT)
add_kpi(slide, Inches(8.3), Inches(3.8), Inches(4.5), Inches(1.6),
        "6h40 à 10h", "Sur 20 serveurs / semaine", color=ORANGE)

add_callout(slide, Inches(0.5), Inches(6.55), Inches(12.3), Inches(0.45),
            "Hors attente validation",
            "La validation asynchrone peut prendre 1 h à 48 h — non comptabilisée ici.")
add_footer(slide, 5)


# ============================================================
# SLIDE 6 — Exécution Jour J
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Phase 3 — Exécution (Jour J)",
              "Lundi → Jeudi — 13 étapes par serveur")

header = ["Étape", "Action", "Durée"]
rows = [
    ("1", "Information début d'intervention (Teams)", "1 min"),
    ("2", "Connexion vCenter + prise de snapshot", "3-5 min"),
    ("3", "Mise en maintenance Centreon (si prod)", "2 min"),
    ("4-5", "SSH + snap pré-patch (services/process/ports)", "6 min"),
    ("6", "Lancement de la mise à jour (yum update)", "5-15 min"),
    ("7-8", "Info Teams + reboot du serveur", "3-6 min"),
    ("9", "Attente + reconnexion", "3-5 min"),
    ("10", "Check post-patch + Centreon", "5-10 min"),
    ("11", "Demande validation responsable applicatif", "5-15 min"),
    ("12-13", "Marquage serveur patché + Teams fin", "3 min"),
]
add_table(slide, Inches(0.5), Inches(1.8), Inches(7.5), Inches(4.3),
          header, rows, font_size=11, header_size=12)

add_kpi(slide, Inches(8.3), Inches(2.0), Inches(4.5), Inches(1.6),
        "35-65 min", "Par serveur (travail actif)", color=RED_ALERT)
add_kpi(slide, Inches(8.3), Inches(3.8), Inches(4.5), Inches(1.6),
        "11h40 à 21h40", "Sur 20 serveurs / semaine", color=ORANGE)

add_callout(slide, Inches(0.5), Inches(6.25), Inches(12.3), Inches(0.75),
            "Pourquoi pas le vendredi ?",
            "Le Jour J est strictement limité à lundi → jeudi : au moins 24 h de recul "
            "avant le weekend pour détecter une régression post-patch.")
add_footer(slide, 6)


# ============================================================
# SLIDE 7 — Nettoyage Vendredi
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Phase 4 — Nettoyage", "Vendredi — traitement groupé de la semaine")

add_bullets(slide, Inches(0.7), Inches(1.9), Inches(12), Inches(2.5), [
    "Suppression des anciens snapshots vCenter de tous les serveurs patchés la semaine  (~ 2 min/serveur)",
    "Suppression des anciens kernels (package-cleanup --oldkernels)  (~ 3 min/serveur)",
    "Au total : ~ 1h40 par semaine pour le lot de 20 serveurs",
], size=16)

add_callout(slide, Inches(0.7), Inches(4.5), Inches(12), Inches(2.3),
            "Pourquoi regrouper le nettoyage le vendredi ?",
            "• Recul suffisant — le snapshot reste disponible au moins 24 h pour permettre un rollback "
            "si une anomalie est détectée après coup.\n\n"
            "• Contexte mental unique — le patcheur traite tous ses snapshots d'un bloc au lieu "
            "d'y revenir serveur par serveur, gain de concentration.")
add_footer(slide, 7)


# ============================================================
# SLIDE 8 — Synthèse temps par serveur
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Synthèse — Temps par serveur (manuel)",
              "Cumul des 3 phases actives + nettoyage")

header = ["Phase", "Temps actif", "Attente asynchrone"]
rows = [
    ("Préparation (qualif + validation)", "20-30 min", "1 à 48 h (async)"),
    ("Exécution Jour J", "35-65 min", "—"),
    ("Nettoyage J+3", "5 min", "—"),
    (("TOTAL PAR SERVEUR", ACCENT_DARK), ("60-100 min", ACCENT_DARK),
     ("≈ 1h15 moyenne", ACCENT_DARK)),
]
add_table(slide, Inches(0.7), Inches(2.0), Inches(11.9), Inches(2.6),
          header, rows, font_size=14, header_size=14)

# Big KPI row
add_kpi(slide, Inches(0.7), Inches(5.0), Inches(3.9), Inches(1.8),
        "1h15", "Par serveur (moyenne)", color=ACCENT)
add_kpi(slide, Inches(4.7), Inches(5.0), Inches(3.9), Inches(1.8),
        "20", "Serveurs / semaine / patcheur", color=ORANGE)
add_kpi(slide, Inches(8.7), Inches(5.0), Inches(3.9), Inches(1.8),
        "25h", "Charge hebdo patching seul", color=RED_ALERT)
add_footer(slide, 8)


# ============================================================
# SLIDE 9 — Réalité du patcheur
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "La réalité du patcheur", "Budget horaire hebdomadaire : 35 h")

# Budget visual
add_text(slide, Inches(0.7), Inches(1.8), Inches(12), Inches(0.4),
         "Répartition de la semaine (Lundi → Vendredi, 7h / jour)",
         size=14, bold=True, color=DARK)

# Stack bar
bar_y = Inches(2.3)
bar_h = Inches(0.9)
bar_x = Inches(0.7)
bar_w_total = Inches(11.9)
# Lun-Jeu
w1 = Inches(11.9 * 28 / 35)
add_rect(slide, bar_x, bar_y, w1, bar_h, fill=ACCENT)
add_text(slide, bar_x, bar_y, w1, bar_h, "Lundi → Jeudi — 28 h (Jour J + fin de prépa)",
         size=12, bold=True, color=WHITE, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# Jeu PM
w2 = Inches(11.9 * 3.5 / 35)
add_rect(slide, bar_x + w1 - Inches(11.9 * 3.5 / 35), bar_y, w2, bar_h, fill=ORANGE)
add_text(slide, bar_x + w1 - Inches(11.9 * 3.5 / 35), bar_y, w2, bar_h, "Jeu PM 3.5h",
         size=10, bold=True, color=WHITE, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# Vendredi
w3 = Inches(11.9 * 7 / 35)
add_rect(slide, bar_x + w1, bar_y, w3, bar_h, fill=GREEN)
add_text(slide, bar_x + w1, bar_y, w3, bar_h, "Vendredi — 7 h (prépa + cleanup)",
         size=12, bold=True, color=WHITE, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)

# Charge théorique table
add_text(slide, Inches(0.7), Inches(3.5), Inches(12), Inches(0.4),
         "Charge théorique patching — 20 serveurs",
         size=14, bold=True, color=DARK)

header = ["Poste", "Volume", "Temps unitaire", "Total hebdo"]
rows = [
    ("Préparation (Jeu PM + Vendredi)", "20", "20-30 min", "6h40 – 10h"),
    ("Exécution Jour J (Lun-Jeu)", "20", "35-65 min", "11h40 – 21h40"),
    ("Nettoyage snapshots (Vendredi)", "20", "5 min", "1h40"),
    (("TOTAL patching seul", ACCENT_DARK), "—", "—", ("20h – 33h", ACCENT_DARK)),
    (("Budget hebdo disponible", GREEN), "—", "—", ("35h (Lun-Ven)", GREEN)),
]
add_table(slide, Inches(0.7), Inches(3.95), Inches(11.9), Inches(2.8),
          header, rows, font_size=12, header_size=13)

add_text(slide, Inches(0.7), Inches(6.85), Inches(12), Inches(0.3),
         "Le patching SEUL consomme 57 à 95% du temps disponible.",
         size=13, bold=True, color=RED_ALERT, align=PP_ALIGN.CENTER)
add_footer(slide, 9)


# ============================================================
# SLIDE 10 — Missions annexes
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Mais le patcheur n'est pas que patcheur",
              "Autres missions SECOPS obligatoires")

header = ["Mission", "Fréquence", "Charge hebdo"]
rows = [
    ("Tickets iTop — sécurisation serveur", "Permanent", "2 à 5 h"),
    ("Mise à jour agent Qualys (ponctuelle)", "Hebdomadaire", "1 à 3 h"),
    ("Mise à jour agent SentinelOne", "Hebdomadaire", "1 à 3 h"),
    ("Tour de garde — alertes SentinelOne / Defender", "Rotation", "3 à 6 h (sur garde)"),
    ("Météo sécurité — veille + reporting hebdo", "Rotation", "2 à 4 h (sur météo)"),
    (("TOTAL missions annexes", ACCENT_DARK), "—", ("9 à 21 h / semaine", ACCENT_DARK)),
]
add_table(slide, Inches(0.7), Inches(2.0), Inches(11.9), Inches(3.3),
          header, rows, font_size=13, header_size=14)

add_callout(slide, Inches(0.7), Inches(5.6), Inches(11.9), Inches(1.2),
            "Équation insoluble",
            "Budget disponible : 35 h   |   Patching : 20-33 h   |   Missions annexes : 9-21 h   "
            "➜   Charge totale : 29 à 54 h / semaine.\n"
            "Surcharge structurelle dès que patching + garde + iTop se cumulent.",
            bg=RGBColor(0xFD, 0xEB, 0xEB), border=RED_ALERT,
            title_color=RED_ALERT)
add_footer(slide, 10)


# ============================================================
# SLIDE 11 — Conséquence sécurité
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Conséquence sécurité directe",
              "Le patcheur n'a plus le temps d'analyser les alertes S1 & Defender")

add_rect(slide, Inches(0.7), Inches(1.9), Inches(12), Inches(0.1), fill=RED_ALERT)
add_text(slide, Inches(0.7), Inches(2.1), Inches(12), Inches(0.5),
         "⚠  Impact opérationnel sur le tour de garde EDR",
         size=18, bold=True, color=RED_ALERT)

add_bullets(slide, Inches(0.7), Inches(2.7), Inches(12), Inches(3), [
    (RED_ALERT, "Alertes SentinelOne / Defender triées en surface — on coche la criticité sans investiguer"),
    (RED_ALERT, "Faux positifs non requalifiés — le bruit de fond masque les vrais incidents"),
    (RED_ALERT, "Vrais incidents détectés tardivement — ransomware, exfiltration, lateral movement"),
    (RED_ALERT, "Pas d'enrichissement de contexte (corrélation process / réseau / utilisateur)"),
    (RED_ALERT, "Les règles de détection ne sont pas affinées en retour d'expérience"),
], size=14)

add_callout(slide, Inches(0.7), Inches(5.7), Inches(12), Inches(1.4),
            "Coût potentiel",
            "Une alerte EDR ratée = incident de sécurité majeur, fuite de données, "
            "indisponibilité.\n"
            "Sans commune mesure avec le coût de l'automatisation du patching.",
            bg=RGBColor(0xFD, 0xEB, 0xEB), border=RED_ALERT, title_color=RED_ALERT)
add_footer(slide, 11)


# ============================================================
# SLIDE 12 — SQATM .exe
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Apport SQATM .exe", "Déjà en production — équipe SECOPS")

add_text(slide, Inches(0.7), Inches(1.9), Inches(12), Inches(0.5),
         "L'outil SANEF Qualys API Tags Management automatise la phase QUALIFICATION :",
         size=14, color=DARK)

header = ["Étape manuelle", "Automatisé par SQATM", "Gain"]
rows = [
    ("Recherche Qualys + lecture vulnérabilités", "Décodeur + API Qualys", "5 min → 30 s"),
    ("Check SSH disque / satellite / dry-run", "Audit global multi-serveurs", "5 min → 10 s"),
    ("Tagging ENV / OS / POS / EQT", "Tag Rules + décodeur nomenclature", "15 min → instantané"),
    ("Identification exposition internet", "Plan de patching + règles", "3 min → instantané"),
]
add_table(slide, Inches(0.7), Inches(2.5), Inches(11.9), Inches(2.5),
          header, rows, font_size=12, header_size=13)

add_kpi(slide, Inches(3.2), Inches(5.3), Inches(7), Inches(1.5),
        "60-70%", "Gain sur la phase préparation", color=ACCENT)
add_footer(slide, 12)


# ============================================================
# SLIDE 13 — PatchCenter Web capabilities
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Apport PatchCenter Web",
              "Orchestration end-to-end — en cours de déploiement")

# 3 colonnes
col_w = Inches(4.2)
col_y = Inches(2.0)
col_h = Inches(4.5)
titles = ["Préparation", "Jour J", "Gouvernance"]
colors = [ACCENT, ORANGE, GREEN]
contents = [
    [
        "Vue unifiée serveurs (Qualys + OS + resp.)",
        "Audit SSH parallélisé 100+ serveurs",
        "Correspondance prod ↔ hors-prod auto",
        "Workflow de validation créneau tracé",
        "Tags Qualys dynamiques auto-appliqués",
    ],
    [
        "Snapshot vCenter via API",
        "Maintenance Centreon via API",
        "Notifications Teams automatiques",
        "Snap pré/post archivé (services/ports)",
        "yum update en job asynchrone",
        "Reboot + reconnexion orchestrés",
        "Détection régression post-reboot",
    ],
    [
        "Validation prod ↔ hors-prod bloquante",
        "Historique complet par serveur",
        "Audit log exportable (conformité)",
        "Tableau de suivi temps réel",
        "Nettoyage snapshots + kernels auto",
        "Multi-profils (admin/coord/op/viewer)",
        "Authentification LDAP AD SANEF",
    ],
]
for i, (t, col, items) in enumerate(zip(titles, colors, contents)):
    x = Inches(0.5 + i * 4.3)
    add_rect(slide, x, col_y, col_w, Inches(0.6), fill=col)
    add_text(slide, x, col_y + Inches(0.1), col_w, Inches(0.45), t,
             size=18, bold=True, color=WHITE, align=PP_ALIGN.CENTER)
    add_rect(slide, x, col_y + Inches(0.6), col_w, col_h - Inches(0.6),
             fill=WHITE, line=LIGHT_GRAY)
    tb = slide.shapes.add_textbox(x + Inches(0.15), col_y + Inches(0.75),
                                   col_w - Inches(0.3), col_h - Inches(0.85))
    tf = tb.text_frame
    tf.word_wrap = True
    for j, it in enumerate(items):
        p = tf.paragraphs[0] if j == 0 else tf.add_paragraph()
        p.space_after = Pt(6)
        r1 = p.add_run(); r1.text = "✓ "
        r1.font.size = Pt(12); r1.font.bold = True; r1.font.color.rgb = col
        r2 = p.add_run(); r2.text = it
        r2.font.size = Pt(12); r2.font.color.rgb = DARK
        r2.font.name = "Calibri"

add_footer(slide, 13)


# ============================================================
# SLIDE 14 — Comparaison chiffrée par serveur
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Comparaison chiffrée", "Temps par serveur — manuel vs automatisé")

header = ["Étape", "Manuel", "SQATM .exe", "PatchCenter Web"]
rows = [
    ("Qualification (prep)", "20-30 min", "6-10 min", ("2-3 min", GREEN)),
    ("Snapshot + maintenance", "5-7 min", "5-7 min", ("30 s (auto)", GREEN)),
    ("Snap pré/post-patch", "10-15 min", "10-15 min", ("1 min (auto)", GREEN)),
    ("Update + reboot + reconnexion", "10-25 min", "10-25 min", "10-25 min"),
    ("Notifications + suivi", "5 min", "5 min", ("0 (auto)", GREEN)),
    ("Nettoyage J+3", "5 min", "5 min", ("1 min (auto)", GREEN)),
    (("TOTAL / SERVEUR", ACCENT_DARK),
     ("60-100 min", RED_ALERT),
     ("40-70 min", ORANGE),
     ("15-30 min", GREEN)),
    (("GAIN vs manuel", ACCENT_DARK), "—", ("-30%", ORANGE), ("-70 à -75%", GREEN)),
]
add_table(slide, Inches(0.5), Inches(1.9), Inches(12.3), Inches(4.8),
          header, rows, font_size=12, header_size=13)

add_text(slide, Inches(0.5), Inches(6.85), Inches(12.3), Inches(0.3),
         "Le gain vient surtout de la suppression des gestes répétitifs (snapshot, Centreon, notifs, suivi).",
         size=12, bold=True, color=ACCENT_DARK, align=PP_ALIGN.CENTER)
add_footer(slide, 14)


# ============================================================
# SLIDE 15 — Impact hebdo (20 serveurs)
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Impact hebdomadaire", "Objectif 20 serveurs/semaine — budget 35 h")

header = ["Scénario", "Patching seul", "Budget restant", "Missions annexes ?"]
rows = [
    (("Manuel", RED_ALERT), ("20h à 33h", RED_ALERT), ("+2h à +15h", ORANGE),
     ("Non — surcharge garantie en garde ou météo", RED_ALERT)),
    (("SQATM .exe", ORANGE), ("14h à 23h", ORANGE), ("+12h à +21h", ORANGE),
     ("Partiellement", ORANGE)),
    (("PatchCenter Web", GREEN), ("5h à 10h", GREEN), ("+25h à +30h", GREEN),
     ("Oui — marge pour tout absorber", GREEN)),
]
add_table(slide, Inches(0.5), Inches(2.0), Inches(12.3), Inches(2.7),
          header, rows, font_size=13, header_size=14)

# KPI summary
add_kpi(slide, Inches(0.7), Inches(5.0), Inches(3.9), Inches(1.8),
        "25-30h", "Libérées/semaine avec PC Web", color=GREEN)
add_kpi(slide, Inches(4.7), Inches(5.0), Inches(3.9), Inches(1.8),
        "x3 à x4", "Réduction du temps / serveur", color=ACCENT)
add_kpi(slide, Inches(8.7), Inches(5.0), Inches(3.9), Inches(1.8),
        "~0.5 ETP", "Équivalent sur 200 serv/mois", color=ACCENT_DARK)
add_footer(slide, 15)


# ============================================================
# SLIDE 16 — Bénéfices qualitatifs
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Bénéfices qualitatifs",
              "Au-delà du temps gagné — valeur stratégique")

benefits = [
    ("Fiabilité", "Plus d'oubli de snapshot, de maintenance Centreon ou de marquage statut", ACCENT),
    ("Traçabilité", "Historique complet par serveur, exportable pour audit", ACCENT),
    ("Gouvernance", "Workflow de validation prod ↔ hors-prod bloquant", ORANGE),
    ("Scalabilité", "1 intervenant pilote 20+ serveurs en parallèle", ORANGE),
    ("Onboarding", "Nouveau patcheur opérationnel en quelques jours", GREEN),
    ("Qualité", "Snap pré/post standardisé → détection fiable des régressions", GREEN),
    ("Conformité", "Tags Qualys automatiques → reporting KPI sans retraitement", ACCENT_DARK),
    ("Communication", "Notifications Teams standardisées → meilleure visibilité projet", ACCENT_DARK),
]

# Grid 2x4
cw = Inches(6.0); ch = Inches(1.1); gap = Inches(0.15)
for i, (t, b, col) in enumerate(benefits):
    row = i // 2
    colpos = i % 2
    x = Inches(0.5) + (cw + gap) * colpos
    y = Inches(1.9) + (ch + gap) * row
    add_rect(slide, x, y, Emu(80000), ch, fill=col)
    add_rect(slide, x + Emu(80000), y, cw - Emu(80000), ch, fill=WHITE, line=LIGHT_GRAY)
    add_text(slide, x + Inches(0.25), y + Inches(0.1), cw - Inches(0.35), Inches(0.4),
             t, size=14, bold=True, color=col)
    add_text(slide, x + Inches(0.25), y + Inches(0.45), cw - Inches(0.35), Inches(0.65),
             b, size=11, color=DARK)
add_footer(slide, 16)


# ============================================================
# SLIDE 17 — Feuille de route
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Feuille de route", "Avancement actuel & prochaines étapes")

# Two columns: done + to do
add_rect(slide, Inches(0.5), Inches(1.9), Inches(6.1), Inches(5), fill=LIGHT_BG, line=LIGHT_GRAY)
add_rect(slide, Inches(0.5), Inches(1.9), Inches(6.1), Inches(0.5), fill=GREEN)
add_text(slide, Inches(0.5), Inches(1.95), Inches(6.1), Inches(0.4),
         "✓  Déjà livré", size=16, bold=True, color=WHITE, align=PP_ALIGN.CENTER)

done_items = [
    "SQATM .exe v2.0.0 — en production (tagging + audit)",
    "PatchCenter Web : catalogue serveurs complet (1165)",
    "PatchCenter Web : correspondance prod ↔ hors-prod",
    "PatchCenter Web : exclusions patch par serveur",
    "PatchCenter Web : workflow validations",
    "PatchCenter Web : agents Qualys + déploiement",
    "Intégration iTop bidirectionnelle (serveurs/apps/statuts)",
    "Authentification LDAP AD SANEF multi-profils",
]
tb = slide.shapes.add_textbox(Inches(0.7), Inches(2.55), Inches(5.8), Inches(4.2))
tf = tb.text_frame; tf.word_wrap = True
for j, it in enumerate(done_items):
    p = tf.paragraphs[0] if j == 0 else tf.add_paragraph()
    p.space_after = Pt(8)
    r1 = p.add_run(); r1.text = "✓ "
    r1.font.size = Pt(12); r1.font.bold = True; r1.font.color.rgb = GREEN
    r2 = p.add_run(); r2.text = it
    r2.font.size = Pt(12); r2.font.color.rgb = DARK

# À venir
add_rect(slide, Inches(6.8), Inches(1.9), Inches(6.1), Inches(5), fill=RGBColor(0xFF, 0xFA, 0xEE), line=LIGHT_GRAY)
add_rect(slide, Inches(6.8), Inches(1.9), Inches(6.1), Inches(0.5), fill=ORANGE)
add_text(slide, Inches(6.8), Inches(1.95), Inches(6.1), Inches(0.4),
         "▶  À venir", size=16, bold=True, color=WHITE, align=PP_ALIGN.CENTER)

todo_items = [
    "Orchestration vCenter (snapshot automatique)",
    "Orchestration Centreon (maintenance automatique)",
    "Intégration Teams native (notifications)",
    "Exécution patching end-to-end en un clic",
    "Module reporting KPI & tableau de bord DSI",
    "Extension aux serveurs Windows (WSUS/MECM)",
]
tb = slide.shapes.add_textbox(Inches(7.0), Inches(2.55), Inches(5.8), Inches(4.2))
tf = tb.text_frame; tf.word_wrap = True
for j, it in enumerate(todo_items):
    p = tf.paragraphs[0] if j == 0 else tf.add_paragraph()
    p.space_after = Pt(8)
    r1 = p.add_run(); r1.text = "▶ "
    r1.font.size = Pt(12); r1.font.bold = True; r1.font.color.rgb = ORANGE
    r2 = p.add_run(); r2.text = it
    r2.font.size = Pt(12); r2.font.color.rgb = DARK

add_footer(slide, 17)


# ============================================================
# SLIDE 18 — Conclusion
# ============================================================
slide = prs.slides.add_slide(blank_layout)
add_title_bar(slide, "Conclusion", "Un investissement à double retour : opérationnel & sécurité")

add_text(slide, Inches(0.7), Inches(1.9), Inches(12), Inches(0.5),
         "Trois constats à retenir",
         size=18, bold=True, color=ACCENT)

bullets_data = [
    ("1.", ACCENT, "Le patching manuel est un coût caché — 60 à 100 min/serveur, 20-33 h/semaine/patcheur"),
    ("2.", ORANGE, "Le patcheur est structurellement en surcharge dès qu'il cumule patching + garde + iTop"),
    ("3.", RED_ALERT, "L'automatisation libère 25-30 h/semaine — du temps pour analyser correctement les alertes EDR"),
]
for i, (num, col, txt) in enumerate(bullets_data):
    y = Inches(2.7 + 0.9 * i)
    add_rect(slide, Inches(0.7), y, Inches(0.9), Inches(0.7), fill=col)
    add_text(slide, Inches(0.7), y + Inches(0.13), Inches(0.9), Inches(0.5),
             num, size=24, bold=True, color=WHITE, align=PP_ALIGN.CENTER)
    add_text(slide, Inches(1.8), y + Inches(0.15), Inches(11), Inches(0.5),
             txt, size=14, color=DARK)

add_callout(slide, Inches(0.7), Inches(5.8), Inches(12), Inches(1.1),
            "Message clé DSI",
            "PatchCenter Web n'est pas qu'un gain de productivité — c'est un levier de sécurité stratégique. "
            "Le temps libéré redonne au patcheur sa capacité d'analyste EDR. Automatiser, "
            "c'est renforcer la détection et la réponse aux incidents de SANEF.",
            bg=RGBColor(0xE6, 0xF6, 0xFB), border=ACCENT, title_color=ACCENT_DARK)
add_footer(slide, 18)


# ============================================================
# SLIDE 19 — Merci / Q&A
# ============================================================
slide = prs.slides.add_slide(blank_layout)
set_bg(slide, DARK)
add_rect(slide, 0, Inches(3.3), SW, Inches(0.08), fill=ACCENT)

add_text(slide, 0, Inches(2.2), SW, Inches(1.2),
         "Merci", size=96, bold=True, color=WHITE, align=PP_ALIGN.CENTER)
add_text(slide, 0, Inches(3.5), SW, Inches(0.6),
         "Questions & Discussion", size=26, color=ACCENT, align=PP_ALIGN.CENTER)
add_text(slide, 0, Inches(4.3), SW, Inches(0.5),
         "SANEF DSI — Équipe SECOPS", size=16, color=LIGHT_GRAY, align=PP_ALIGN.CENTER)
add_text(slide, 0, Inches(4.8), SW, Inches(0.4),
         "pc.mpcz.fr — PatchCenter Web", size=14, color=GRAY, align=PP_ALIGN.CENTER)

# Save
out = r"C:\Claude\sanef\docs\SANEF_Processus_Patching.pptx"
prs.save(out)
print(f"Generated: {out}")
