#!/usr/bin/env python3
"""Visuels article « Contrat de maintenance PrestaShop 2026 » : featured + 3 inline."""

from __future__ import annotations

from pathlib import Path

from PIL import Image, ImageDraw, ImageFilter, ImageFont

ROOT = Path(__file__).resolve().parents[1]
ASSETS = ROOT / "assets"

ORANGE = (233, 128, 48)
ORANGE_SOFT = (255, 244, 235)
INK = (17, 17, 17)
MUTED = (102, 102, 102)
WHITE = (255, 255, 255)
NAVY = (18, 32, 56)
CREAM = (255, 252, 248)
LIGHT = (248, 246, 242)
GRID = (230, 226, 218)
RED = (200, 62, 52)
GREEN = (149, 191, 71)


def load_font(size: int, *, bold: bool = False, black: bool = False) -> ImageFont.ImageFont:
    if black:
        candidates = [
            "/System/Library/Fonts/Supplemental/Arial Black.ttf",
            "/System/Library/Fonts/Supplemental/Arial Bold.ttf",
        ]
    elif bold:
        candidates = [
            "/System/Library/Fonts/Supplemental/Arial Bold.ttf",
            "/Library/Fonts/Arial Bold.ttf",
        ]
    else:
        candidates = [
            "/System/Library/Fonts/Supplemental/Arial.ttf",
            "/Library/Fonts/Arial.ttf",
        ]
    for path in candidates:
        if Path(path).exists():
            return ImageFont.truetype(path, size)
    return ImageFont.load_default()


def add_blur_halo(base, center, radius, color, blur):
    layer = Image.new("RGBA", base.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(layer)
    cx, cy = center
    draw.ellipse((cx - radius, cy - radius, cx + radius, cy + radius), fill=color)
    layer = layer.filter(ImageFilter.GaussianBlur(radius=blur))
    return Image.alpha_composite(base.convert("RGBA"), layer)


def cream_bg(w: int, h: int) -> Image.Image:
    img = Image.new("RGB", (w, h), CREAM)
    img = add_blur_halo(img, (int(w * 0.82), int(h * 0.16)), int(w * 0.30), (233, 128, 48, 66), 120)
    img = add_blur_halo(img, (int(w * 0.10), int(h * 0.62)), int(w * 0.24), (255, 190, 130, 54), 105)
    img = add_blur_halo(img, (int(w * 0.60), int(h * 0.95)), int(w * 0.22), (236, 192, 89, 40), 90)
    return img.convert("RGB")


def shadow(base, box, radius):
    x0, y0, x1, y1 = box
    layer = Image.new("RGBA", base.size, (0, 0, 0, 0))
    ImageDraw.Draw(layer).rounded_rectangle(
        (x0 + 8, y0 + 18, x1 + 8, y1 + 18), radius=radius, fill=(20, 24, 36, 70)
    )
    layer = layer.filter(ImageFilter.GaussianBlur(20))
    return Image.alpha_composite(base.convert("RGBA"), layer)


# --------------------------------------------------------------------------- featured
NAVY_DEEP = (11, 21, 38)
PAPER = (250, 248, 245)


def tracked_text(draw, xy, text, font, fill, tracking=6, anchor_left=True):
    """Texte avec interlettrage, faute de support natif dans Pillow."""
    x, y = xy
    if not anchor_left:
        width = sum(draw.textlength(c, font=font) + tracking for c in text) - tracking
        x -= width
    for char in text:
        draw.text((x, y), char, fill=fill, font=font)
        x += draw.textlength(char, font=font) + tracking
    return x


def contract_sheet(w: int, h: int) -> Image.Image:
    """Feuille de contrat annotée, en RGBA pour rotation."""
    sheet = Image.new("RGBA", (w, h), PAPER + (255,))
    d = ImageDraw.Draw(sheet)

    d.rectangle((0, 0, w, 12), fill=NAVY)
    d.text((56, 60), "CONTRAT DE MAINTENANCE", fill=INK, font=load_font(34, black=True))
    d.text((56, 108), "Boutique PrestaShop 8.2.x", fill=MUTED, font=load_font(24))
    d.line([(56, 158), (w - 56, 158)], fill=(224, 220, 214), width=2)

    clauses = [
        ("Art. 3 - Mises à jour de sécurité", "sous 48 h après publication", "ok"),
        ("Art. 4 - Sauvegarde quotidienne", "stockage hors serveur, 30 j", "ok"),
        ("Art. 5 - Test de restauration réel", "absent du contrat", "ko"),
        ("Art. 6 - SLA incident bloquant", "« dans les meilleurs délais »", "warn"),
    ]

    y = 200
    for title, detail, state in clauses:
        box = (56, y, 96, y + 40)
        if state == "ok":
            d.rounded_rectangle(box, radius=8, fill=ORANGE)
            d.line([(66, y + 21), (74, y + 30), (88, y + 11)], fill=WHITE, width=5)
            title_fill, detail_fill = INK, MUTED
        elif state == "ko":
            d.rounded_rectangle(box, radius=8, outline=RED, width=4)
            d.line([(66, y + 11), (86, y + 31)], fill=RED, width=5)
            d.line([(86, y + 11), (66, y + 31)], fill=RED, width=5)
            title_fill, detail_fill = RED, RED
        else:
            d.rounded_rectangle(box, radius=8, outline=(190, 186, 180), width=4)
            title_fill, detail_fill = INK, MUTED

        d.text((124, y - 4), title, fill=title_fill, font=load_font(28, bold=True))
        d.text((124, y + 32), detail, fill=detail_fill, font=load_font(23))

        if state == "ko":
            tw = d.textlength(title, font=load_font(28, bold=True))
            d.line([(124, y + 12), (124 + tw, y + 12)], fill=RED, width=3)
        y += 108

    # lignes de texte simulées, bas de page
    for i in range(5):
        yy = y + 24 + i * 26
        width = w - 112 if i % 3 else int((w - 112) * 0.62)
        d.rounded_rectangle((56, yy, 56 + width, yy + 10), radius=5, fill=(230, 226, 220))

    return sheet


def rubber_stamp(size: int = 340) -> Image.Image:
    stamp = Image.new("RGBA", (size, size), (0, 0, 0, 0))
    d = ImageDraw.Draw(stamp)
    pad = 14
    d.ellipse((pad, pad, size - pad, size - pad), outline=RED + (235,), width=9)
    d.ellipse((pad + 20, pad + 20, size - pad - 20, size - pad - 20), outline=RED + (235,), width=4)
    f_big = load_font(52, black=True)
    f_small = load_font(23, bold=True)
    d.text((size // 2, size // 2 - 34), "JAMAIS", fill=RED + (235,), font=f_big, anchor="mm")
    d.text((size // 2, size // 2 + 22), "TESTÉE", fill=RED + (235,), font=f_big, anchor="mm")
    d.text((size // 2, size // 2 + 74), "SAUVEGARDE", fill=RED + (200,), font=f_small, anchor="mm")
    return stamp.rotate(-13, resample=Image.Resampling.BICUBIC, expand=True)


def render_featured() -> Image.Image:
    w, h = 1536, 1024
    img = Image.new("RGB", (w, h), NAVY_DEEP)
    d = ImageDraw.Draw(img)

    # dégradé vertical discret
    for y in range(h):
        t = y / h
        d.line(
            [(0, y), (w, y)],
            fill=(int(11 + t * 9), int(21 + t * 13), int(38 + t * 20)),
        )
    img = add_blur_halo(img, (250, 180), 420, (233, 128, 48, 46), 150)
    img = add_blur_halo(img, (1240, 940), 380, (233, 128, 48, 34), 140)
    img = img.convert("RGB")
    d = ImageDraw.Draw(img)

    # filet vertical orange, ancrage de la colonne de texte
    d.rectangle((96, 150, 102, 470), fill=ORANGE)

    tracked_text(d, (140, 152), "MAINTENANCE PRESTASHOP", load_font(22, bold=True), ORANGE, tracking=7)

    title_f = load_font(88, black=True)
    d.text((136, 210), "Ce que ton", fill=WHITE, font=title_f)
    d.text((136, 306), "contrat", fill=WHITE, font=title_f)
    x_end = 136 + d.textlength("contrat ", font=title_f)
    d.text((x_end, 306), "ne dit", fill=ORANGE, font=title_f)
    d.text((136, 402), "pas.", fill=ORANGE, font=title_f)

    d.text(
        (136, 540),
        "Prix réels 2026, les 14 lignes d'un contrat",
        fill=(196, 204, 218),
        font=load_font(29),
    )
    d.text(
        (136, 582),
        "sérieux, et les 9 clauses qui coûtent cher.",
        fill=(196, 204, 218),
        font=load_font(29),
    )

    # trois chiffres clés, en ligne
    stats = [("50-600 €", "par mois"), ("14", "clauses clés"), ("9", "pièges")]
    x = 136
    for i, (big, small) in enumerate(stats):
        if i:
            d.line([(x - 34, 672), (x - 34, 740)], fill=(48, 62, 88), width=2)
        d.text((x, 660), big, fill=WHITE, font=load_font(44, black=True))
        d.text((x, 716), small, fill=(140, 152, 172), font=load_font(21))
        x += max(d.textlength(big, font=load_font(44, black=True)), d.textlength(small, font=load_font(21))) + 78

    d.line([(136, 838), (600, 838)], fill=(48, 62, 88), width=2)
    d.text((136, 866), "Arnaud Mérigeau", fill=WHITE, font=load_font(28, bold=True))
    d.text((136, 906), "Freelance PrestaShop · arnaud-merigeau.fr", fill=ORANGE, font=load_font(23))

    # la feuille de contrat, inclinée
    sheet = contract_sheet(720, 880)
    sheet = sheet.rotate(-4.5, resample=Image.Resampling.BICUBIC, expand=True)

    glow = Image.new("RGBA", img.size, (0, 0, 0, 0))
    gd = ImageDraw.Draw(glow)
    gd.rectangle((830, 130, 1500, 1010), fill=(0, 0, 0, 150))
    glow = glow.filter(ImageFilter.GaussianBlur(38))
    img = Image.alpha_composite(img.convert("RGBA"), glow)

    img.paste(sheet, (792, 92), sheet)

    stamp = rubber_stamp(268)
    img.paste(stamp, (1096, 664), stamp)

    return img.convert("RGB")


# --------------------------------------------------------------------------- paliers
def render_paliers() -> Image.Image:
    w, h = 1280, 760
    img = Image.new("RGB", (w, h), LIGHT)
    draw = ImageDraw.Draw(img)

    draw.text((56, 40), "Ce que coûte la maintenance PrestaShop en 2026", fill=INK, font=load_font(36, bold=True))
    draw.text(
        (56, 90),
        "Relevé d'offres publiques du marché français, septembre 2026 · prix HT mensuels",
        fill=MUTED,
        font=load_font(21),
    )

    cols = [
        ("VEILLE", "50 - 120 €", ["Mises à jour mineures", "Sauvegarde hebdo", "Monitoring uptime", "Support best effort"], MUTED),
        ("STANDARD", "120 - 300 €", ["Sauvegarde quotidienne", "Tests post-update", "1 à 2 h incluses", "Rapport mensuel"], ORANGE),
        ("AVANCÉ", "300 - 600 €", ["Correctifs illimités", "Perf mensuelle", "Réponse sous 4 h", "Interlocuteur dédié"], NAVY),
        ("CRITIQUE", "600 € et +", ["Astreinte étendue", "Staging permanent", "Roadmap trimestrielle", "SLA contractuel"], RED),
    ]

    x = 56
    cw, gap = 284, 20
    for name, price, items, color in cols:
        box = (x, 150, x + cw, 620)
        draw.rounded_rectangle(box, radius=22, fill=WHITE, outline=GRID, width=2)
        draw.rounded_rectangle((x, 150, x + cw, 226), radius=22, fill=color)
        draw.rectangle((x, 200, x + cw, 226), fill=color)
        draw.text((x + cw // 2, 188), name, fill=WHITE, font=load_font(26, bold=True), anchor="mm")
        draw.text((x + cw // 2, 286), price, fill=INK, font=load_font(38, black=True), anchor="mm")
        draw.text((x + cw // 2, 328), "par mois", fill=MUTED, font=load_font(19), anchor="mm")
        draw.line([(x + 28, 360), (x + cw - 28, 360)], fill=GRID, width=1)
        for i, it in enumerate(items):
            y = 392 + i * 56
            draw.ellipse((x + 30, y + 6, x + 42, y + 18), fill=color)
            draw.text((x + 56, y), it, fill=MUTED, font=load_font(20))
        x += cw + gap

    draw.rounded_rectangle((56, 650, 1224, 724), radius=18, fill=ORANGE_SOFT)
    draw.text(
        (640, 687),
        "En régie : 60 à 120 €/h. Moins cher sur le papier, imprévisible le jour où ça casse.",
        fill=ORANGE,
        font=load_font(23, bold=True),
        anchor="mm",
    )
    return img


# --------------------------------------------------------------------------- coût panne
def render_cout_panne() -> Image.Image:
    w, h = 1280, 720
    img = Image.new("RGB", (w, h), LIGHT)
    draw = ImageDraw.Draw(img)

    draw.text((56, 40), "Ce que coûte une journée de boutique à l'arrêt", fill=INK, font=load_font(36, bold=True))
    draw.text(
        (56, 90),
        "Calcul : chiffre d'affaires annuel ÷ 365. Hors perte de position SEO et de confiance client.",
        fill=MUTED,
        font=load_font(21),
    )

    data = [("100 k€", 274), ("300 k€", 822), ("500 k€", 1370), ("1 M€", 2740)]
    top, bottom = 190, 520
    left = 150
    max_val = 3000
    scale = (bottom - top) / max_val

    draw.line([(left - 40, bottom), (1200, bottom)], fill=GRID, width=2)
    for tick in (0, 1000, 2000, 3000):
        y = bottom - int(tick * scale)
        draw.line([(left - 40, y), (1200, y)], fill=GRID, width=1)
        draw.text((70, y - 11), f"{tick} €", fill=MUTED, font=load_font(19))

    bar_w = 150
    for i, (label, val) in enumerate(data):
        cx = left + 130 + i * 250
        bh = int(val * scale)
        color = ORANGE if val < 1000 else RED
        draw.rounded_rectangle((cx - bar_w // 2, bottom - bh, cx + bar_w // 2, bottom), radius=14, fill=color)
        draw.text((cx, bottom - bh - 32), f"{val} €", fill=INK, font=load_font(27, bold=True), anchor="mm")
        draw.text((cx, bottom + 30), label, fill=INK, font=load_font(23, bold=True), anchor="mm")
        draw.text((cx, bottom + 60), "de CA annuel", fill=MUTED, font=load_font(18), anchor="mm")

    draw.rounded_rectangle((56, 610, 1224, 686), radius=18, fill=ORANGE_SOFT)
    draw.text(
        (640, 648),
        "Une boutique à 300 k€ perd en 3 jours d'arrêt le prix d'un an de contrat standard.",
        fill=ORANGE,
        font=load_font(23, bold=True),
        anchor="mm",
    )
    return img


# --------------------------------------------------------------------------- cycle
def render_cycle() -> Image.Image:
    w, h = 1280, 700
    img = Image.new("RGB", (w, h), LIGHT)
    draw = ImageDraw.Draw(img)

    draw.text((56, 40), "Le cycle réel d'un contrat de maintenance", fill=INK, font=load_font(36, bold=True))
    draw.text((56, 90), "Ce qui se passe, et quand. Le reste, c'est de la facturation à l'heure.", fill=MUTED, font=load_font(21))

    blocks = [
        ("CHAQUE JOUR", ["Sauvegarde base + fichiers", "Monitoring uptime et checkout", "Alerte erreurs 500"], ORANGE),
        ("CHAQUE MOIS", ["Mises à jour modules", "Purge logs et cache", "Rapport d'activité"], NAVY),
        ("CHAQUE TRIMESTRE", ["Test de restauration réel", "Revue perf mobile", "Revue des accès"], GREEN),
        ("CHAQUE ANNÉE", ["Plan de version (8.2 → 9.x)", "Revue licences modules", "Audit sécurité complet"], RED),
    ]

    x = 56
    cw, gap = 284, 20
    for name, items, color in blocks:
        draw.rounded_rectangle((x, 160, x + cw, 560), radius=22, fill=WHITE, outline=GRID, width=2)
        draw.rounded_rectangle((x + 24, 190, x + cw - 24, 244), radius=14, fill=color)
        head_f = load_font(22 if len(name) <= 12 else 19, bold=True)
        draw.text((x + cw // 2, 217), name, fill=WHITE, font=head_f, anchor="mm")
        for i, it in enumerate(items):
            y = 290 + i * 78
            draw.ellipse((x + 30, y + 6, x + 42, y + 18), fill=color)
            words = it.split()
            line1, line2 = it, ""
            if len(it) > 22:
                mid = len(words) // 2
                line1, line2 = " ".join(words[:mid]), " ".join(words[mid:])
            draw.text((x + 56, y), line1, fill=MUTED, font=load_font(20))
            if line2:
                draw.text((x + 56, y + 26), line2, fill=MUTED, font=load_font(20))
        x += cw + gap

    draw.rounded_rectangle((56, 590, 1224, 664), radius=18, fill=ORANGE_SOFT)
    draw.text(
        (640, 627),
        "Le test de restauration trimestriel : la ligne que presque personne ne met dans son contrat.",
        fill=ORANGE,
        font=load_font(23, bold=True),
        anchor="mm",
    )
    return img


# --------------------------------------------------------------------------- social 16:9
def render_social() -> Image.Image:
    w, h = 1920, 1080
    img = Image.new("RGB", (w, h), NAVY_DEEP)
    d = ImageDraw.Draw(img)
    for y in range(h):
        t = y / h
        d.line([(0, y), (w, y)], fill=(int(11 + t * 9), int(21 + t * 13), int(38 + t * 20)))
    img = add_blur_halo(img, (300, 200), 460, (233, 128, 48, 48), 150)
    img = add_blur_halo(img, (1500, 1000), 400, (233, 128, 48, 32), 140)
    img = img.convert("RGB")
    d = ImageDraw.Draw(img)

    d.rectangle((110, 178, 117, 520), fill=ORANGE)
    tracked_text(d, (158, 180), "MAINTENANCE PRESTASHOP", load_font(24, bold=True), ORANGE, tracking=8)

    title_f = load_font(96, black=True)
    d.text((152, 244), "Ce que ton", fill=WHITE, font=title_f)
    d.text((152, 350), "contrat", fill=WHITE, font=title_f)
    x_end = 152 + d.textlength("contrat ", font=title_f)
    d.text((x_end, 350), "ne dit", fill=ORANGE, font=title_f)
    d.text((152, 456), "pas.", fill=ORANGE, font=title_f)

    d.text((152, 610), "Prix réels 2026, les 14 lignes d'un contrat sérieux,", fill=(196, 204, 218), font=load_font(31))
    d.text((152, 654), "et les 9 clauses qui coûtent cher.", fill=(196, 204, 218), font=load_font(31))

    stats = [("50-600 €", "par mois"), ("14", "clauses clés"), ("9", "pièges")]
    x = 152
    big_f, small_f = load_font(48, black=True), load_font(22)
    for i, (big, small) in enumerate(stats):
        if i:
            d.line([(x - 38, 748), (x - 38, 822)], fill=(48, 62, 88), width=2)
        d.text((x, 736), big, fill=WHITE, font=big_f)
        d.text((x, 796), small, fill=(140, 152, 172), font=small_f)
        x += max(d.textlength(big, font=big_f), d.textlength(small, font=small_f)) + 84

    d.line([(152, 900), (660, 900)], fill=(48, 62, 88), width=2)
    d.text((152, 928), "Arnaud Mérigeau", fill=WHITE, font=load_font(30, bold=True))
    d.text((152, 970), "Freelance PrestaShop · arnaud-merigeau.fr", fill=ORANGE, font=load_font(24))

    sheet = contract_sheet(760, 900).rotate(-4.5, resample=Image.Resampling.BICUBIC, expand=True)
    glow = Image.new("RGBA", img.size, (0, 0, 0, 0))
    ImageDraw.Draw(glow).rectangle((1080, 130, 1900, 1060), fill=(0, 0, 0, 150))
    glow = glow.filter(ImageFilter.GaussianBlur(38))
    img = Image.alpha_composite(img.convert("RGBA"), glow)
    img.paste(sheet, (1080, 110), sheet)

    stamp = rubber_stamp(280)
    img.paste(stamp, (1420, 700), stamp)
    return img.convert("RGB")


def main() -> int:
    ASSETS.mkdir(parents=True, exist_ok=True)
    jobs = [
        ("contrat-maintenance-prestashop-2026-featured.png", render_featured),
        ("maintenance-prestashop-2026-paliers.png", render_paliers),
        ("maintenance-prestashop-2026-cout-panne.png", render_cout_panne),
        ("maintenance-prestashop-2026-cycle.png", render_cycle),
    ]
    for name, fn in jobs:
        path = ASSETS / name
        fn().save(path, "PNG", optimize=True)
        print(f"OK {name}")

    kit = ROOT / "www/propositions/kit-social-maintenance-prestashop-2026/visuels"
    kit.mkdir(parents=True, exist_ok=True)
    social = render_social()
    social.save(kit / "visuel-maintenance-prestashop-2026.png", "PNG", optimize=True)
    social.save(kit / "visuel-maintenance-prestashop-2026.jpg", "JPEG", quality=92, optimize=True)
    for name in ("maintenance-prestashop-2026-cout-panne.png", "maintenance-prestashop-2026-cycle.png"):
        (kit / name).write_bytes((ASSETS / name).read_bytes())
    print(f"OK kit social -> {kit}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
