"""Seed the database with real data from Duniter community documents.

Sources:
  - Engagement Forgeron v2.0.0: https://forum.monnaie-libre.fr/t/33165
  - Engagement Certification (Licence G1 v0.3.0):
    https://git.duniter.org/documents/g1_monetary_license/-/raw/master/g1_monetary_license_fr.rst
  - Charte 1.0 (rejected): https://forum.monnaie-libre.fr/t/proposition-charte-1-0/31066
  - Licence v0.4.0 (in progress): https://forum.monnaie-libre.fr/t/32375
  - Runtime Upgrade process template

Genesis references:
  - Licence repo: https://git.duniter.org/documents/g1_monetary_license
  - g1vote: https://git.duniter.org/tools/g1vote-view
  - g1vote live: https://g1vote-view-237903.pages.duniter.org/

Idempotent: checks if data already exists before inserting.
"""

from __future__ import annotations

import asyncio
import hashlib
import json
import uuid
from datetime import datetime, timedelta, timezone

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import async_session, engine, Base, init_db
from app.models.protocol import FormulaConfig, VotingProtocol
from app.models.document import Document, DocumentItem
from app.models.decision import Decision, DecisionStep
from app.models.mandate import Mandate, MandateStep
from app.models.organization import Organization
from app.models.qualification import QualificationProtocol
from app.models.user import DuniterIdentity
from app.models.vote import VoteSession, Vote


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

async def get_or_create(
    session: AsyncSession,
    model,
    lookup_field: str,
    lookup_value,
    **kwargs,
):
    """Return existing row or create a new one."""
    stmt = select(model).where(getattr(model, lookup_field) == lookup_value)
    result = await session.execute(stmt)
    instance = result.scalar_one_or_none()
    if instance is not None:
        return instance, False
    instance = model(**{lookup_field: lookup_value}, **kwargs)
    session.add(instance)
    await session.flush()
    return instance, True


def fake_signature(payload: str) -> str:
    """Generate a deterministic fake Ed25519-like signature for seed data."""
    return hashlib.sha256(payload.encode()).hexdigest()


# ---------------------------------------------------------------------------
# Seed: FormulaConfigs
# ---------------------------------------------------------------------------

async def seed_formula_configs(session: AsyncSession) -> dict[str, FormulaConfig]:
    configs: dict[str, dict] = {
        "Standard Licence G1": {
            "description": "Formule standard pour la Licence G1 : vote binaire WoT. Inertie standard.",
            "duration_days": 30,
            "majority_pct": 50,
            "base_exponent": 0.1,
            "gradient_exponent": 0.2,
            "constant_base": 0.0,
        },
        "Inertie basse (Annexes)": {
            "description": (
                "Formule à inertie basse pour les annexes et recommandations. "
                "Gradient faible = plus facile à remplacer même à faible participation."
            ),
            "duration_days": 30,
            "majority_pct": 50,
            "base_exponent": 0.1,
            "gradient_exponent": 0.1,
            "constant_base": 0.0,
        },
        "Inertie haute (Formule)": {
            "description": (
                "Formule à inertie haute pour la section formule elle-même. "
                "Gradient élevé = nécessite une forte mobilisation pour changer."
            ),
            "duration_days": 30,
            "majority_pct": 60,
            "base_exponent": 0.1,
            "gradient_exponent": 0.4,
            "constant_base": 0.0,
        },
        "Inertie très haute (Méta-réglage)": {
            "description": (
                "Formule à inertie maximale pour le réglage de l'inertie elle-même. "
                "Quasi-unanimité requise à toute participation. Protection contre "
                "la modification des règles de modification."
            ),
            "duration_days": 30,
            "majority_pct": 66,
            "base_exponent": 0.1,
            "gradient_exponent": 0.6,
            "constant_base": 0.0,
        },
        "Forgeron avec Smith": {
            "description": "Vote forgeron avec critère Smith sub-WoT.",
            "duration_days": 30,
            "majority_pct": 50,
            "base_exponent": 0.1,
            "gradient_exponent": 0.2,
            "constant_base": 0.0,
            "smith_exponent": 0.1,
        },
        "Comité Tech": {
            "description": "Vote avec critère Comité Technique.",
            "duration_days": 30,
            "majority_pct": 50,
            "base_exponent": 0.1,
            "gradient_exponent": 0.2,
            "constant_base": 0.0,
            "techcomm_exponent": 0.1,
        },
        "Vote Nuance": {
            "description": "Vote nuancé à 6 niveaux (CONTRE..TOUT À FAIT).",
            "duration_days": 30,
            "majority_pct": 50,
            "base_exponent": 0.1,
            "gradient_exponent": 0.2,
            "constant_base": 0.0,
            "nuanced_min_participants": 59,
            "nuanced_threshold_pct": 80,
        },
    }

    result: dict[str, FormulaConfig] = {}
    for name, params in configs.items():
        instance, created = await get_or_create(
            session, FormulaConfig, "name", name, **params,
        )
        status = "created" if created else "exists"
        print(f"  FormulaConfig '{name}': {status}")
        result[name] = instance

    return result


# ---------------------------------------------------------------------------
# Seed: VotingProtocols
# ---------------------------------------------------------------------------

async def seed_voting_protocols(
    session: AsyncSession,
    formulas: dict[str, FormulaConfig],
    org_id: uuid.UUID | None = None,
) -> dict[str, VotingProtocol]:
    protocols: dict[str, dict] = {
        "Vote WoT standard": {
            "description": (
                "Vote binaire (pour/contre) ouvert à tous les membres de la "
                "toile de confiance. Le seuil d'adoption s'adapte au taux de "
                "participation via l'inertie WoT : quasi-unanimité à faible "
                "participation, majorité simple à forte mobilisation. "
                "Utilisé pour la Licence G1 et les textes fondateurs."
            ),
            "vote_type": "binary",
            "formula_config_id": formulas["Standard Licence G1"].id,
            "mode_params": "D30M50B.1G.2",
        },
        "Vote forgeron (Smith)": {
            "description": (
                "Vote binaire avec double critère : seuil WoT standard + "
                "seuil minimal de forgerons. Garantit que toute décision "
                "impliquant les validateurs soit soutenue par les "
                "opérateurs du réseau. Utilisé pour les engagements "
                "forgerons et les décisions d'infrastructure."
            ),
            "vote_type": "binary",
            "formula_config_id": formulas["Forgeron avec Smith"].id,
            "mode_params": "D30M50B.1G.2S.1",
        },
        "Vote Comité Technique": {
            "description": (
                "Vote binaire avec critère TechComm obligatoire. "
                "Réservé aux décisions techniques critiques : runtime "
                "upgrades, modifications de paramètres on-chain, "
                "approbation de code. Le Comité Technique doit atteindre "
                "un seuil minimal indépendamment du vote communautaire."
            ),
            "vote_type": "binary",
            "formula_config_id": formulas["Comité Tech"].id,
            "mode_params": "D30M50B.1G.2T.1",
        },
    }

    result: dict[str, VotingProtocol] = {}
    for name, params in protocols.items():
        instance, created = await get_or_create(
            session, VotingProtocol, "name", name, **params,
        )
        instance.organization_id = org_id
        status = "created" if created else "exists"
        print(f"  VotingProtocol '{name}': {status}")
        result[name] = instance

    return result


# ---------------------------------------------------------------------------
# Seed: Engagement Certification (Acte d'engagement Certification)
#
# Full structured document built from:
#   - Licence G1 v0.3.0 (in force)
#   - Charte 1.0 (rejected proposal, topic 31066)
#   - Licence v0.4.0 (in progress, topic 32375)
#   - Yvv's "Acte d'engagement" position
#   - Checklist certification (topic 32412)
# ---------------------------------------------------------------------------

GENESIS_CERTIFICATION = {
    "source_document": {
        "title": "Licence de la monnaie libre G1 v0.3.0",
        "url": "https://git.duniter.org/documents/g1_monetary_license/-/raw/master/g1_monetary_license_fr.rst",
        "repo": "https://git.duniter.org/documents/g1_monetary_license",
    },
    "référence_tools": {
        "g1vote_repo": "https://git.duniter.org/tools/g1vote-view",
        "g1vote_live": "https://g1vote-view-237903.pages.duniter.org/",
        "cesium": "https://g1.duniter.org",
        "gecko": "https://gecko.music-all.org",
    },
    "forum_synthesis": [
        {
            "title": "Proposition Charte 1.0 (rejetée)",
            "url": "https://forum.monnaie-libre.fr/t/proposition-charte-1-0/31066",
            "status": "rejected",
            "posts": 70,
        },
        {
            "title": "Préparation licence v0.4.0",
            "url": "https://forum.monnaie-libre.fr/t/preparation-dune-proposition-devolution-de-la-licence-1/32375",
            "status": "in_progress",
            "posts": 38,
        },
        {
            "title": "Checklist de certification (annexe licence)",
            "url": "https://forum.monnaie-libre.fr/t/prepa-checklist-de-certification-annexe-licence-1/32412",
            "status": "in_progress",
            "posts": 16,
        },
        {
            "title": "Règles de modifications (annexe licence)",
            "url": "https://forum.monnaie-libre.fr/t/prepa-regles-de-modifications-annexe-licence-1/32409",
            "status": "in_progress",
            "posts": 9,
        },
        {
            "title": "Vote nuancé licence",
            "url": "https://forum.monnaie-libre.fr/t/processus-de-validation-licence-par-vote-nuance/31729",
            "status": "référence",
        },
    ],
    "formula_trigger": (
        "Quand un item atteint le seuil d'adoption (formule WoT), "
        "le texte de remplacement est intégré au document officiel. "
        "Le hash IPFS du document mis à jour est ancré on-chain via system.remark. "
        "Les applications (Cesium, Gecko) pointent vers le dépôt git officiel "
        "qui est synchronisé avec l'état validé par les votes."
    ),
    "contributors": [
        {"name": "1000i100", "role": "Pilote principal, rédacteur"},
        {"name": "Natha", "role": "Co-rédactrice"},
        {"name": "Pini", "role": "Co-initiateur"},
        {"name": "Yvv", "role": "Contributions structurelles, concepteur 'Acte d'engagement'"},
        {"name": "elois", "role": "Dev g1vote, contributions techniques"},
    ],
}


ENGAGEMENT_CERTIFICATION_ITEMS: list[dict] = [
    # ===================================================================
    # INTRODUCTION
    # ===================================================================
    {
        "position": "I1",
        "item_type": "preamble",
        "title": "Préambule",
        "sort_order": 1,
        "section_tag": "introduction",
        "inertia_preset": "standard",
        "current_text": (
            "Le présent acte d'engagement définit les obligations réciproques "
            "des membres de la toile de confiance de la monnaie libre G1. "
            "Cet acte est de fait l'unique relation contractuelle de notre "
            "toile fiduciaire. Toute Certification doit s'accompagner de la"
            "transmission de ce document, dont le certificateur doit s'assurer "
            "qu'il a été étudié, compris et accepté par le certifié."
        ),
    },
    {
        "position": "I2",
        "item_type": "preamble",
        "title": "Les deux garanties réciproques",
        "sort_order": 2,
        "section_tag": "introduction",
        "inertia_preset": "standard",
        "current_text": (
            "La Certification repose sur deux garanties réciproques :\n\n"
            "**1.** Derrière une clé publique créatrice de monnaie "
            "se trouve un **être humain vivant**.\n\n"
            "**2.** Derrière cet être humain se trouve **une seule et unique** "
            "clé publique créatrice de monnaie.\n\n"
            "La Certification est un acte technique et fiduciaire, "
            "pas un acte d'adhésion morale ou de sympathie."
        ),
    },
    # ===================================================================
    # TITRE 1 : ENGAGEMENTS FONDAMENTAUX (sur l'honneur)
    # ===================================================================
    {
        "position": "T1",
        "item_type": "section",
        "title": "Engagements fondamentaux",
        "sort_order": 3,
        "section_tag": "fondamental",
        "inertia_preset": "standard",
        "current_text": (
            "Les engagements fondamentaux ci-dessous constituent le socle "
            "irréductible de l'acte d'engagement. Ils sont pris sur l'honneur "
            "par tout membre de la toile de confiance."
        ),
    },
    {
        "position": "E1",
        "item_type": "clause",
        "title": "Unicité du compte",
        "sort_order": 4,
        "section_tag": "fondamental",
        "inertia_preset": "standard",
        "current_text": (
            "Je m'engage sur l'honneur à n'avoir et n'avoir jamais "
            "qu'un seul et unique compte cocréateur de monnaie G1."
        ),
    },
    {
        "position": "E2",
        "item_type": "clause",
        "title": "Réciprocité",
        "sort_order": 5,
        "section_tag": "fondamental",
        "inertia_preset": "standard",
        "current_text": (
            "Je m'engage sur l'honneur à ne certifier que des personnes "
            "physiques qui respectent scrupuleusement ces deux présents "
            "engagements fondamentaux."
        ),
    },
    # ===================================================================
    # TITRE 2 : ENGAGEMENTS TECHNIQUES
    # ===================================================================
    {
        "position": "T2",
        "item_type": "section",
        "title": "Engagements techniques",
        "sort_order": 6,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Les engagements techniques définissent les obligations "
            "pratiques et vérifiables du certificateur pour garantir "
            "la qualité de la toile de confiance."
        ),
    },
    {
        "position": "E3",
        "item_type": "clause",
        "title": "Connaissance suffisante",
        "sort_order": 7,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Je me suis assuré de connaître suffisamment la personne "
            "qui gère cette clé publique. Connaître suffisamment ne signifie "
            "pas « avoir vu » ; c'est assurer à la communauté G1 que je "
            "pourrai la contacter facilement et être en mesure de repérer "
            "un double-compte ou tout autre problème."
        ),
    },
    {
        "position": "E4",
        "item_type": "clause",
        "title": "Vérification personnelle de la clé",
        "sort_order": 8,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai personnellement vérifié que c'est bien cette clé publique "
            "que je m'apprête à certifier, en la comparant avec la personne "
            "concernée et non par un intermédiaire."
        ),
    },
    {
        "position": "E5",
        "item_type": "clause",
        "title": "Joignabilité réciproque",
        "sort_order": 9,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Je suis joignable rapidement et facilement par mes certifieurs, "
            "et je peux joindre rapidement et facilement les personnes que "
            "je certifie, par plusieurs moyens de communication différents "
            "et indépendants (physique, téléphone, email, messagerie, etc.)."
        ),
    },
    {
        "position": "E6",
        "item_type": "clause",
        "title": "Document de révocation",
        "sort_order": 10,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai vérifié avec la personne certifiée qu'elle a bien généré "
            "son document de révocation de compte et qu'elle le conserve "
            "en lieu sûr."
        ),
    },
    {
        "position": "E7",
        "item_type": "clause",
        "title": "Rencontre physique ou vérification multi-canaux",
        "sort_order": 11,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai rencontré la personne physiquement (préférable), **OU** "
            "j'ai vérifié à distance le lien personne / clé publique par "
            "plusieurs moyens de communication différents et indépendants : "
            "courrier + réseau social + forum + email + visio + téléphone."
        ),
    },
    # ===================================================================
    # TITRE 3 : CONSEILS ET BONNES PRATIQUES
    # ===================================================================
    {
        "position": "T3",
        "item_type": "section",
        "title": "Conseils et bonnes pratiques",
        "sort_order": 12,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Les pratiques suivantes sont fortement recommandées pour "
            "garantir la qualité et la sécurité de la toile de confiance."
        ),
    },
    {
        "position": "E8",
        "item_type": "clause",
        "title": "Ne jamais certifier seul",
        "sort_order": 13,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Ne certifiez jamais seul, mais accompagné d'au moins un autre "
            "membre de la toile de confiance G1, pour garantir un double "
            "contrôle."
        ),
    },
    {
        "position": "E9",
        "item_type": "clause",
        "title": "Vérification des certifications existantes",
        "sort_order": 14,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Avant toute Certification, vérifiez si le compte a déjà "
            "reçu des certifications et de qui elles proviennent. "
            "Contactez les certifieurs existants en cas de doute. "
            "Si un certifieurs existant ne connaît pas la personne, "
            "alertez immédiatement les experts de la communauté."
        ),
    },
    {
        "position": "E10",
        "item_type": "clause",
        "title": "Vérification de maîtrise du compte",
        "sort_order": 15,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Vérifiez la maîtrise du compte par un transfert-test : "
            "envoyez quelques G1 et demandez le renvoi, afin de vous "
            "assurer que la personne contrôle bien sa clé privée."
        ),
    },
    {
        "position": "E11",
        "item_type": "clause",
        "title": "Transmission et compréhension du document",
        "sort_order": 16,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Vérifiez que vos contacts ont bien étudié et compris "
            "le présent acte d'engagement dans sa version à jour "
            "avant de procéder à la Certification."
        ),
    },
    # ===================================================================
    # CONCLUSION
    # ===================================================================
    {
        "position": "K1",
        "item_type": "preamble",
        "title": "Règles abrégées de la toile de confiance",
        "sort_order": 17,
        "section_tag": "conclusion",
        "inertia_preset": "standard",
        "current_text": (
            "**Paramètres protocolaires en vigueur :**\n\n"
            "- Stock de **100 certifications** possibles\n"
            "- 1 certification émissible tous les **5 jours**\n"
            "- Nouveau compte valide si **>= 5 certifications** reçues en **2 mois**\n"
            "- Condition de distance : **<= 5 pas** de **80% des sentinelles**\n"
            "- Sentinelle : membre ayant reçu et émis >= Y[N] certifs "
            "(Y = ceil(N^(1/5)))\n"
            "- Certifications actives valables **2 ans**\n"
            "- Renouvellement de l'accord tous les **12 mois**\n\n"
            "*Note : le vote porte sur l'inclusion de ces règles dans le document, "
            "pas sur les valeurs des variables protocolaires elles-mêmes, "
            "qui sont fixées par le protocole Duniter.*"
        ),
    },
    {
        "position": "K2",
        "item_type": "preamble",
        "title": "Monnaie G1",
        "sort_order": 18,
        "section_tag": "conclusion",
        "inertia_preset": "standard",
        "current_text": (
            "**Paramètres monétaires :**\n\n"
            "- 1 Dividende Universel (DU) par personne par jour\n"
            "- Réévaluation à chaque équinoxe : "
            "`DU(n+1) = DU(n) + c² × (M/N) / 182.625` avec c = 4.88%\n"
            "- DU(0) = 10.00 G1\n\n"
            "*Note : le vote porte sur l'inclusion de ces paramètres dans le document, "
            "pas sur les valeurs monétaires elles-mêmes, "
            "qui découlent de la TRM et du bloc 0.*"
        ),
    },
    # ===================================================================
    # ANNEXE 1 : INTÉGRATION LOGICIELLE
    # ===================================================================
    {
        "position": "X1",
        "item_type": "section",
        "title": "Annexe 1 : Intégration logicielle",
        "sort_order": 19,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "Les obligations pour les logiciels implémentant la Certification G1."
        ),
    },
    {
        "position": "X1.1",
        "item_type": "clause",
        "title": "Dépôt de référence",
        "sort_order": 20,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "Le texte de référence de l'acte d'engagement Certification est "
            "hébergé dans le dépôt git officiel : "
            "https://git.duniter.org/documents/g1_monetary_license\n\n"
            "Les applications Cesium, Gecko et toute application de "
            "Certification doivent pointer vers ce dépôt pour afficher "
            "la version en vigueur."
        ),
    },
    {
        "position": "X1.2",
        "item_type": "clause",
        "title": "Obligation de transmission",
        "sort_order": 21,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "Tout logiciel G1 permettant la Certification doit transmettre "
            "le présent acte d'engagement au certifié et en afficher les "
            "paramètres du bloc 0 de la blockchain Duniter."
        ),
    },
    {
        "position": "X1.3",
        "item_type": "clause",
        "title": "Logiciel auditable",
        "sort_order": 22,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "Tout logiciel utilisé pour la Certification ou la création "
            "monétaire doit être publié sous licence libre, afin de "
            "permettre son audit par la communauté."
        ),
    },
    # ===================================================================
    # ANNEXE 2 : QUESTIONS A LA CERTIFICATION (checklist logicielle)
    # ===================================================================
    {
        "position": "X2",
        "item_type": "section",
        "title": "Annexe 2 : Questions à la Certification",
        "sort_order": 23,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "Liste de questions à présenter dans les logiciels de Certification."
            "Inspirée de la checklist de la Charte 1.0 et des discussions "
            "forum (topic 32412)."
        ),
    },
    {
        "position": "X2.1",
        "item_type": "verification",
        "title": "Questions piège (réponse attendue : NON)",
        "sort_order": 24,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "**Si OUI à l'une de ces questions, la Certification doit être refusée.**\n\n"
            "- La personne m'a contacté uniquement pour obtenir ma Certification\n"
            "- Je certifie sous la pression ou pour faire plaisir\n"
            "- Je n'ai aucun moyen de vérifier l'identité de la personne\n"
            "- La personne refuse de me fournir plusieurs moyens de contact\n"
            "- Je soupçonne que la personne possède déjà un compte membre\n"
            "- Je ne connais aucun de ses autres certifieurs\n"
            "- La personne ne comprend pas ce qu'est la monnaie libre\n"
            "- La personne n'a pas lu le présent acte d'engagement"
        ),
    },
    {
        "position": "X2.2",
        "item_type": "verification",
        "title": "Questions identité (réponse attendue : OUI)",
        "sort_order": 25,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "**Si NON à l'une de ces questions, la Certification doit être refusée.**\n\n"
            "- Je connais cette personne suffisamment pour la recontacter\n"
            "- J'ai vérifié personnellement le lien personne / clé publique\n"
            "- La personne est une personne physique vivante (pas une entité morale)\n"
            "- Je pourrais reconnaître cette personne si je la croisais\n"
            "- J'ai au moins 2 moyens de contact différents pour cette personne"
        ),
    },
    {
        "position": "X2.3",
        "item_type": "verification",
        "title": "Questions sécurité (réponse attendue : OUI)",
        "sort_order": 26,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "**Si NON, la Certification est déconseillée.**\n\n"
            "- La personne a généré son document de révocation\n"
            "- La personne maîtrise effectivement son compte "
            "(test de transfert effectué)\n"
            "- La personne sait où retrouver le présent acte d'engagement "
            "dans sa version à jour"
        ),
    },
    # ===================================================================
    # SECTION SPÉCIALE : FORMULE DE VOTE
    # ===================================================================
    {
        "position": "F1",
        "item_type": "section",
        "title": "Formule de vote",
        "sort_order": 27,
        "section_tag": "formule",
        "inertia_preset": "high",
        "current_text": (
            "La formule qui régit l'adoption ou le rejet de chaque modification "
            "du présent document. Référence : g1vote "
            "(https://g1vote-view-237903.pages.duniter.org/)."
        ),
    },
    {
        "position": "F1.1",
        "item_type": "rule",
        "title": "Formule du seuil WoT",
        "sort_order": 28,
        "section_tag": "formule",
        "inertia_preset": "high",
        "current_text": (
            "**Seuil = C + B^W + (M + (1-M) × (1 - (T/W)^G)) × max(0, T-C)**\n\n"
            "Où :\n"
            "- **C** = constante de base (plancher fixe)\n"
            "- **B** = exposant de base (B^W tend vers 0)\n"
            "- **W** = taille de la toile de confiance (électeurs éligibles)\n"
            "- **T** = total des votes exprimés (pour + contre)\n"
            "- **M** = ratio de majorité cible (M = majority_pct / 100)\n"
            "- **G** = exposant du gradient d'inertie\n\n"
            "**Comportement :** Faible participation → quasi-unanimité requise. "
            "Forte participation → majorité simple M suffit."
        ),
    },
    {
        "position": "F1.2",
        "item_type": "rule",
        "title": "Paramètres par défaut",
        "sort_order": 29,
        "section_tag": "formule",
        "inertia_preset": "high",
        "current_text": (
            "**Seuil = M + (1-M) × (1 - (T/W)^G)**\n\n"
            "- **T** = votes exprimés (pour + contre)\n"
            "- **W** = taille de la toile de confiance\n"
            "- **M** = majorité cible (50% par défaut)\n"
            "- **G** = gradient d'inertie (0.2 par défaut)\n\n"
            "**Valeurs par défaut (inertie standard) :**\n"
            "- Durée : 30 jours (vote permanent)\n"
            "- Majorité cible M = 50%\n"
            "- Gradient G = 0.2\n"
            "- Base B = 0.1, Constante C = 0\n\n"
            "**Lecture du curseur d'inertie :**\n"
            "Le curseur affiché sur chaque section représente le niveau G. "
            "Plus le curseur est à droite, plus le remplacement est difficile. "
            "La courbe associée montre le seuil requis en fonction de la participation.\n\n"
            "Mode compact : **D30M50B.1G.2**"
        ),
    },
    {
        "position": "F1.3",
        "item_type": "rule",
        "title": "Processus de dépôt officiel",
        "sort_order": 30,
        "section_tag": "formule",
        "inertia_preset": "high",
        "current_text": (
            "Lorsqu'une proposition alternative atteint le seuil d'adoption :\n\n"
            "1. Le texte de remplacement est intégré au document officiel\n"
            "2. Le hash IPFS du document mis à jour est calculé\n"
            "3. Le hash est ancré on-chain via `system.remark`\n"
            "4. Le dépôt git officiel est synchronisé\n"
            "5. Les applications (Cesium, Gecko) mettent à jour automatiquement"
        ),
    },
    # ===================================================================
    # SECTION SPÉCIALE : RÉGLAGE DE L'INERTIE
    # ===================================================================
    {
        "position": "N1",
        "item_type": "section",
        "title": "Réglage de l'inertie",
        "sort_order": 31,
        "section_tag": "inertie",
        "inertia_preset": "very_high",
        "current_text": (
            "Le réglage de l'inertie définit la difficulté de remplacement "
            "de chaque section du document. Ce réglage est lui-même soumis "
            "à l'inertie la plus élevée, pour empêcher la modification "
            "des règles de modification."
        ),
    },
    {
        "position": "N1.1",
        "item_type": "rule",
        "title": "Niveaux d'inertie",
        "sort_order": 32,
        "section_tag": "inertie",
        "inertia_preset": "very_high",
        "current_text": (
            "**Formule simplifiée :** `Seuil = M + (1-M) × (1 - (T/W)^G)`\n\n"
            "Le **gradient G** contrôle la courbe d'inertie : plus G est élevé, "
            "plus le seuil reste haut même avec une participation croissante.\n\n"
            "**Basse** — G=0.1, M=50%\n"
            "- La courbe descend très vite vers 50%\n"
            "- Dès 10% de participation, le seuil est proche de la majorité simple\n"
            "- *Usage : annexes, recommandations, conseils*\n\n"
            "**Standard** — G=0.2, M=50%\n"
            "- Descente progressive, équilibre entre stabilité et réactivité\n"
            "- À 25% de participation, le seuil avoisine 65%\n"
            "- *Usage : engagements fondamentaux et techniques*\n\n"
            "**Haute** — G=0.4, M=60%\n"
            "- Courbe résistante, le seuil reste élevé longtemps\n"
            "- Super-majorité de 60% même à pleine participation\n"
            "- *Usage : formule de vote elle-même*\n\n"
            "**Très haute** — G=0.6, M=66%\n"
            "- Quasi-unanimité requise à tout niveau de participation\n"
            "- Seul un consensus massif (66%+) peut modifier ce réglage\n"
            "- *Usage : réglage de l'inertie (méta-protection)*\n\n"
            "Le curseur d'inertie associé à chaque section du document "
            "reflète visuellement ces niveaux."
        ),
    },
    # ===================================================================
    # SECTION SPÉCIALE : ORDONNANCEMENT
    # ===================================================================
    {
        "position": "O1",
        "item_type": "section",
        "title": "Ordonnancement du document",
        "sort_order": 33,
        "section_tag": "ordonnancement",
        "inertia_preset": "standard",
        "current_text": (
            "L'ordre de présentation des items dans le document est "
            "lui-même soumis au vote. Toute proposition de réorganisation "
            "doit atteindre le seuil d'adoption avec l'inertie standard."
        ),
    },
]


async def seed_document_engagement_certification(
    session: AsyncSession,
    protocols: dict[str, VotingProtocol],
    org_id: uuid.UUID | None = None,
) -> Document:
    genesis = json.dumps(GENESIS_CERTIFICATION, ensure_ascii=False, indent=2)

    doc, created = await get_or_create(
        session,
        Document,
        "slug",
        "engagement-certification",
        title="Acte d'engagement Certification",
        doc_type="engagement",
        version="1.0.0",
        status="active",
        description=(
            "Acte d'engagement des certificateurs de la toile de confiance G1. "
            "Document modulaire sous vote permanent : chaque item peut être "
            "remplacé par une alternative qui atteint le seuil d'adoption. "
            "Construit à partir de la Licence G1 v0.3.0, des discussions "
            "communautaires et de la position 'Acte d'engagement' (Yvv)."
        ),
        genesis_json=genesis,
    )
    doc.organization_id = org_id
    print(f"  Document 'Acte d'engagement Certification': {'created' if created else 'exists'}")

    if created:
        # Map inertia presets to voting protocols
        inertia_protocol_map = {
            "low": protocols.get("Vote WoT standard"),
            "standard": protocols.get("Vote WoT standard"),
            "high": protocols.get("Vote WoT standard"),
            "very_high": protocols.get("Vote WoT standard"),
        }

        for item_data in ENGAGEMENT_CERTIFICATION_ITEMS:
            preset = item_data.get("inertia_preset", "standard")
            protocol = inertia_protocol_map.get(preset)
            item = DocumentItem(
                document_id=doc.id,
                voting_protocol_id=protocol.id if protocol else None,
                **item_data,
            )
            session.add(item)
        await session.flush()
        print(f"    -> {len(ENGAGEMENT_CERTIFICATION_ITEMS)} items created")

    return doc


# ---------------------------------------------------------------------------
# Seed: Engagement Forgeron v2.0.0 (real content from forum topic 33165)
#
# Full structured document built from:
#   - Engagement Forgeron v2.0.0-fr, adopted 2026-02-06
#   - Original text by 1000i100: https://forum.monnaie-libre.fr/t/33165/3
#   - Vote results: 97 pour / 23 contre, WoT 7224, Smith 8/3
#   - Git repo: https://git.duniter.org/documents/g1_monetary_license
#   - Working group: ML#32603 (39 posts)
# ---------------------------------------------------------------------------

GENESIS_FORGERON = {
    "source_document": {
        "title": "Engagement forgeron Ğ1 v2.0.0-fr",
        "url": "https://forum.monnaie-libre.fr/t/vote-engagement-forgeron-v2-0-0/33165/3",
        "repo": "https://git.duniter.org/documents/g1_monetary_license/-/tree/master/smith_commitment",
        "date": "2026-01-07",
        "version": "2.0.0-fr",
    },
    "vote_record": {
        "topic_url": "https://forum.monnaie-libre.fr/t/vote-engagement-forgeron-v2-0-0/33165",
        "g1vote_url": "https://g1vote-view-237903.pages.duniter.org/#/vote/33165",
        "mode_params": "D30M50B.1G.2S.1",
        "period": "2026-01-07 → 2026-02-06",
        "result": {
            "pour": 97,
            "contre": 23,
            "nuls_invalides": 19,
            "smith_pour": 8,
            "smith_contre": 3,
            "wot_size": 7224,
            "threshold_required": 97,
            "status": "adopté",
        },
        "addresses": {
            "pour": "5UkkPtV7TEbVL11ztGHQkYKeobnMhh4k3FJXsdSaX84S:7G1",
            "contre": "Cwpmov6D2XZsiRya5ZVsYFVWy94sjatcgwAntQndzLz:786",
        },
    },
    "référence_tools": {
        "g1vote_repo": "https://git.duniter.org/tools/g1vote-view",
        "g1vote_live": "https://g1vote-view-237903.pages.duniter.org/",
        "pad_source": "https://pad.p2p.legal/g1-v2-charte-forgeron",
    },
    "forum_synthesis": [
        {
            "title": "Vote : Engagement Forgeron v2.0.0",
            "url": "https://forum.monnaie-libre.fr/t/vote-engagement-forgeron-v2-0-0/33165",
            "status": "adopté",
            "posts": 15,
        },
        {
            "title": "Vers un engagement forgeron : Groupe de réflexion",
            "url": "https://forum.monnaie-libre.fr/t/vers-un-engagement-forgeron-groupe-de-reflexion/32603",
            "status": "référence",
            "posts": 39,
        },
        {
            "title": "Vers une charte forgeron : Sondage formulation du sujet",
            "url": "https://forum.monnaie-libre.fr/t/vers-une-charte-forgeron-sondage-formulation-du-sujet/32318",
            "status": "référence",
        },
        {
            "title": "License forgeron (proposition initiale 2022)",
            "url": "https://forum.duniter.org/t/license-forgeron/9997",
            "status": "référence",
            "posts": 48,
        },
        {
            "title": "Traduction espagnole",
            "url": "https://foro.moneda-libre.org/t/votacion-compromiso-de-forjadores-v2-0-0/3608",
            "status": "référence",
        },
    ],
    "formula_trigger": (
        "Quand un item atteint le seuil d'adoption (formule WoT + critère Smith), "
        "le texte de remplacement est intégré au document officiel. "
        "Le hash IPFS du document mis à jour est ancré on-chain via system.remark. "
        "Le dépôt git officiel est synchronisé."
    ),
    "implementation_actions": [
        "Ajouter l'engagement forgeron au dépôt charte/licence (MR avec 1 validation min)",
        "Ajouter les checklist smith et certSmith dans g1cli",
        "Ajouter les checklist smith et certSmith dans duniter-vue",
        "Ajouter les checklist smith et certSmith dans cesium2",
        "Développer l'UI de checklist lors des certif smith",
    ],
    "contributors": [
        {"name": "1000i100", "role": "Pilote principal, rédacteur"},
        {"name": "sarahmagicienne", "role": "Animation vote, résultats officiels"},
        {"name": "BuLmA", "role": "Groupe de réflexion"},
        {"name": "Moul", "role": "Review technique, retours"},
        {"name": "kapis", "role": "Traduction espagnole"},
    ],
}


ENGAGEMENT_FORGERON_ITEMS: list[dict] = [
    # ===================================================================
    # INTRODUCTION — Intention, enjeux, compétences requises
    # ===================================================================
    {
        "position": "P1",
        "item_type": "preamble",
        "title": "Préambule",
        "sort_order": 1,
        "section_tag": "introduction",
        "inertia_preset": "standard",
        "current_text": (
            "Le présent acte d'engagement définit les obligations réciproques "
            "des forgerons (validateurs de blocs) de la blockchain Duniter V2. "
            "L'engagement forgeron est une certification de qualification "
            "et d'aptitudes techniques, à distinguer de l'acte d'engagement "
            "Certification qui porte sur l'identité unique des membres. "
            "Toute certification forgeron doit s'accompagner de la "
            "vérification de chaque clause, présentée dans un ordre aléatoire."
        ),
    },
    {
        "position": "P2",
        "item_type": "preamble",
        "title": "Intention et enjeux",
        "sort_order": 2,
        "section_tag": "introduction",
        "inertia_preset": "standard",
        "current_text": (
            "S'assurer en tant que communauté que les forgerons qui gèrent "
            "l'écosystème technique le font avec : compétence, rigueur, "
            "sécurité et réactivité.\n\n"
            "La majorité des nœuds forgeron déclarés « online » doivent "
            "être effectivement en bon état de fonctionnement et accessibles "
            "en ligne.\n\n"
            "Pour résister aux attaques, les nœuds doivent éviter au maximum "
            "les dépendances centralisées, qu'elles soient techniques ou "
            "humaines. Les dépendances centralisées tolérées devraient l'être "
            "uniquement si leur altération nuit suffisamment à l'attaquant "
            "pour le dissuader d'attaquer par ces dépendances."
        ),
    },
    # ===================================================================
    # ENGAGEMENTS FONDAMENTAUX — Protocoles concrets, pas de morale
    # Reframé depuis les "savoirs-être" : rigueur, réactivité,
    # responsabilité deviennent des engagements évaluables.
    # Cf. Yvv, ML#32603 post #31 : "Remplacer savoir-être par
    # engagements changerait tout, y compris la façon de les formuler."
    # ===================================================================
    {
        "position": "EF0",
        "item_type": "section",
        "title": "Engagements fondamentaux",
        "sort_order": 3,
        "section_tag": "fondamental",
        "inertia_preset": "standard",
        "current_text": (
            "Engagements de conduite et de rigueur auxquels chaque forgeron "
            "s'engage concrètement. Chaque engagement est formulé comme un "
            "protocole évaluable, non comme un trait de personnalité."
        ),
    },
    {
        "position": "EF1",
        "item_type": "clause",
        "title": "Rigueur : protocole de doute",
        "sort_order": 4,
        "section_tag": "fondamental",
        "inertia_preset": "standard",
        "current_text": (
            "Au moindre doute sur la sécurité ou le fonctionnement "
            "de mon nœud, je m'engage au protocole suivant :\n\n"
            "1. **Documenter** le problème (logs, contexte, symptômes)\n"
            "2. **Poster sur le forum** Duniter (catégorie technique) "
            "pour bénéficier de l'expertise collective\n"
            "3. **Me déclarer offline** si le doute persiste après 24h\n\n"
            "Je ne lance pas mon nœud sans comprendre sa configuration. "
            "Une fausse sécurité érode la confiance et met le réseau en danger."
        ),
    },
    {
        "position": "EF2",
        "item_type": "clause",
        "title": "Réactivité : délais d'intervention",
        "sort_order": 5,
        "section_tag": "fondamental",
        "inertia_preset": "standard",
        "current_text": (
            "En cas d'interruption de service ou de problème signalé, "
            "je m'engage aux délais suivants :\n\n"
            "- **J1** : Alerte — je préviens les forgerons concernés "
            "(forum, Telegram, contact direct)\n"
            "- **J2 à J5** : Rétablissement de service ou escalade "
            "(appel à l'aide d'autres forgerons)\n"
            "- Si je ne suis pas en mesure de tenir ces délais, "
            "je me déclare offline."
        ),
    },
    {
        "position": "EF3",
        "item_type": "clause",
        "title": "Certification : garantie de compétences",
        "sort_order": 6,
        "section_tag": "fondamental",
        "inertia_preset": "standard",
        "current_text": (
            "Certifier un forgeron, c'est garantir ses compétences "
            "vérifiées. Je m'engage à :\n\n"
            "- **Évaluer** les savoir-faire du candidat avant de certifier\n"
            "- **Ne pas laisser sans réponse** ses questions techniques\n"
            "- **Vérifier** que le certifié respecte les protocoles en vigueur\n"
            "- **Compenser les lacunes** ou ne pas certifier\n\n"
            "Il ne s'agit pas d'être « responsable » au sens moral, mais de "
            "s'engager sur des vérifications concrètes et documentées."
        ),
    },
    # ===================================================================
    # ENGAGEMENTS TECHNIQUES — Compétences évaluables
    # Reframé depuis les "savoirs-faire" : chaque compétence doit être
    # testable, pas décrite avec des termes vagues.
    # Cf. Yvv, ML#32603 post #31 : "Je m'attends à ce que chaque
    # savoir-faire formulé soit évaluable."
    # ===================================================================
    {
        "position": "ET0",
        "item_type": "section",
        "title": "Engagements techniques",
        "sort_order": 7,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Compétences techniques requises pour opérer un nœud forgeron. "
            "Chaque compétence est formulée de manière évaluable lors du "
            "parcours de qualification (Embarquement Forgeron)."
        ),
    },
    {
        "position": "ET1",
        "item_type": "clause",
        "title": "Administration système Linux",
        "sort_order": 8,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Je suis capable d'administrer un serveur Linux en ligne de "
            "commande :\n\n"
            "- Installer et configurer un service (paquet ou Docker)\n"
            "- Configurer réseau et pare-feu, exposer un nœud en ligne\n"
            "- Diagnostiquer un problème à partir des logs système\n\n"
            "**Évaluation** : démontrer lors de l'embarquement la capacité "
            "à déployer et maintenir un nœud miroir fonctionnel."
        ),
    },
    {
        "position": "ET2",
        "item_type": "clause",
        "title": "Sécurité et gestion de clés",
        "sort_order": 9,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Je maîtrise les pratiques de sécurité nécessaires :\n\n"
            "- Génération et stockage sécurisé de phrases de récupération\n"
            "- Modèle de menaces : identifier ma surface d'attaque\n"
            "- Ne pas exposer d'API unsafe publiquement\n"
            "- Séparer compte membre et compte de transactions\n\n"
            "**Évaluation** : décrire son modèle de menaces et démontrer "
            "ses pratiques de gestion de clés au certificateur."
        ),
    },
    {
        "position": "ET3",
        "item_type": "clause",
        "title": "Mécanismes de consensus Duniter",
        "sort_order": 10,
        "section_tag": "technique",
        "inertia_preset": "standard",
        "current_text": (
            "Je comprends les mécanismes de consensus de la blockchain "
            "Duniter V2 :\n\n"
            "- Toile de confiance Smith (sous-toile des forgerons)\n"
            "- Autorités online, epochs, production de blocs (BABE)\n"
            "- Finalisation (GRANDPA) et tolérance aux fautes byzantines\n"
            "- Offenses et leurs conséquences (slash, exclusion)\n\n"
            "**Évaluation** : poser 2 questions pertinentes sur le forum "
            "Duniter, démontrant la compréhension des enjeux de consensus."
        ),
    },
    # ===================================================================
    # QUALIFICATION — Lien explicite avec le protocole Embarquement
    # Cf. Yvv, ML#32603 post #3 : "compétence sécu et réactivité sont
    # davantage les sujets de l'enrôlement et accompagnement (onboarding)"
    # Cf. Yvv, ML#32960 post #12 : "compétences requises et protocoles
    # à respecter à chaque jalon du onboarding"
    # ===================================================================
    {
        "position": "Q0",
        "item_type": "section",
        "title": "Qualification : Embarquement forgeron",
        "sort_order": 11,
        "section_tag": "qualification",
        "inertia_preset": "standard",
        "current_text": (
            "La qualification d'un forgeron repose sur un parcours "
            "d'embarquement structuré en jalons progressifs. Chaque "
            "jalon introduit de nouvelles obligations dans l'acte "
            "d'engagement. Ce parcours est défini par le protocole "
            "« Embarquement Forgeron »."
        ),
    },
    {
        "position": "Q1",
        "item_type": "clause",
        "title": "Jalons du parcours de qualification",
        "sort_order": 12,
        "section_tag": "qualification",
        "inertia_preset": "standard",
        "current_text": (
            "Le parcours d'embarquement forgeron comprend les jalons "
            "suivants, chacun conditionnant le passage au suivant :\n\n"
            "1. **Candidature** — Déclaration d'intention, premier contact "
            "avec un forgeron référent\n"
            "2. **Nœud miroir** — Déployer et maintenir un nœud miroir "
            "synchronisé pendant une période probatoire\n"
            "3. **Évaluation technique** — Vérification des compétences "
            "techniques par un forgeron certificateur (checklist aléatoire)\n"
            "4. **Certification Smith** — Réception de 3 certifications "
            "forgeron après validation de l'ensemble des engagements\n"
            "5. **Mise en ligne** — Passage du nœud validateur en mode "
            "online, début de la forge effective\n\n"
            "→ Protocole détaillé : **Embarquement Forgeron** "
            "(voir section Protocoles)"
        ),
    },
    # ===================================================================
    # ASPIRANT FORGERON — Clauses d'engagement (texte intégral v2.0.0-fr)
    # Checklist présentée dans un ordre aléatoire lors de la certification
    # ===================================================================
    {
        "position": "T1",
        "item_type": "section",
        "title": "Clauses d'engagement de l'aspirant forgeron",
        "sort_order": 13,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "Les clauses ci-dessous constituent le parcours d'onboarding "
            "du futur forgeron. Chaque clause est présentée dans un ordre "
            "aléatoire lors de la certification ; l'aspirant doit répondre "
            "correctement à chacune d'elles."
        ),
    },
    # --- Aspirant : Sécurité et conformité (11 clauses) ---
    {
        "position": "A1",
        "item_type": "clause",
        "title": "Intention et motivation",
        "sort_order": 14,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai clarifié ce qui me motive à devenir forgeron, j'en "
            "assume les raisons et les indiquerai à qui me les demande."
        ),
    },
    {
        "position": "A2",
        "item_type": "clause",
        "title": "Veille sécurité",
        "sort_order": 15,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "Je fais de la veille pour maintenir mes pratiques de sécurité "
            "système et réseau à la hauteur des enjeux Duniter actuels."
        ),
    },
    {
        "position": "A3",
        "item_type": "clause",
        "title": "Notifications forum",
        "sort_order": 16,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai activé les notifications sur forum.duniter.org pour être "
            "alerté des sollicitations perso et smiths."
        ),
    },
    {
        "position": "A4",
        "item_type": "verification",
        "title": "Phrase de récupération aléatoire",
        "sort_order": 17,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "Je confirme que ma phrase de récupération a été générée "
            "aléatoirement et que je l'utilise uniquement pour mon "
            "compte membre."
        ),
    },
    {
        "position": "A5",
        "item_type": "verification",
        "title": "Compte séparé pour les transactions",
        "sort_order": 18,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "J'utilise un autre compte pour mes transactions courantes. "
            "Je m'authentifie sur mon compte membre seulement pour les "
            "opérations nécessitant les droits non délégables, et depuis "
            "mon propre matériel."
        ),
    },
    {
        "position": "A6",
        "item_type": "verification",
        "title": "Sauvegarde phrase de récupération",
        "sort_order": 19,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai stocké ma phrase de récupération sur plusieurs supports "
            "physiques (numérique ou non), récupérables indépendamment. "
            "En cas de vol, incendie, panne, oubli… j'aurai accès à au "
            "moins une des copies pour reprendre la main."
        ),
    },
    {
        "position": "A7",
        "item_type": "verification",
        "title": "Nœud à jour et synchronisé",
        "sort_order": 20,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "Je gère déjà un nœud à jour (version logiciel), correctement "
            "synchronisé (état blockchain) et joignable (peer réseau)."
        ),
    },
    {
        "position": "A8",
        "item_type": "verification",
        "title": "API unsafe non exposée",
        "sort_order": 21,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai veillé à ne pas exposer publiquement l'api unsafe "
            "de mon nœud smith."
        ),
    },
    {
        "position": "A9",
        "item_type": "clause",
        "title": "Transparence technique",
        "sort_order": 22,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "Je fournis à la demande d'un autre forgeron, mes choix "
            "techniques et si je change d'approche, je préviens les "
            "forgerons qui m'ont certifié (pour être alerté des "
            "éventuels soucis associés)."
        ),
    },
    {
        "position": "A10",
        "item_type": "clause",
        "title": "Déclaration offline en cas de doute",
        "sort_order": 23,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "Je me déclare offline en cas de doute sur la sécurité "
            "ou la continuité de service de mon nœud."
        ),
    },
    {
        "position": "A11",
        "item_type": "clause",
        "title": "Réactivité 24h",
        "sort_order": 24,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "Je m'engage à répondre en moins de 24h aux forgerons "
            "quand je suis déclaré online. Sinon, je me déclare offline."
        ),
    },
    # --- Aspirant : Contact (1 clause) ---
    {
        "position": "A12",
        "item_type": "clause",
        "title": "Contact multi-canal",
        "sort_order": 25,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "Je sais joindre efficacement et rapidement 3 des forgerons "
            "qui comptent me certifier (cela par au moins 2 canaux : "
            "tél/sms + email, xmpp, matrix…)."
        ),
    },
    # --- Aspirant : Connaissances (3 clauses) ---
    {
        "position": "A13",
        "item_type": "clause",
        "title": "Acceptation des engagements",
        "sort_order": 26,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai lu et j'accepte de respecter l'ensemble des "
            "engagements forgerons et j'en comprends les implications "
            "et raisons d'être."
        ),
    },
    {
        "position": "A14",
        "item_type": "clause",
        "title": "Règles de la TdC forgeron",
        "sort_order": 27,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai pris connaissance des règles et délais associés au "
            "fonctionnement de la TdC forgeron."
        ),
    },
    {
        "position": "A15",
        "item_type": "clause",
        "title": "Fonctionnement blockchain Duniter",
        "sort_order": 28,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai bien compris le fonctionnement d'un réseau blockchain, "
            "en particulier les enjeux de sécurité liés aux mécanismes "
            "de consensus de la blockchain Duniter."
        ),
    },
    # --- Aspirant : Pièges (3 clauses — réponse attendue : NON) ---
    {
        "position": "A16",
        "item_type": "rule",
        "title": "Piège : harcèlement",
        "sort_order": 29,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "[Piège — réponse attendue : NON]\n"
            "J'insiste, harcèle ou fais pression d'une autre manière "
            "pour être certifié."
        ),
    },
    {
        "position": "A17",
        "item_type": "rule",
        "title": "Piège : gloire et pouvoir",
        "sort_order": 30,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "[Piège — réponse attendue : NON]\n"
            "Je veux être forgeron pour la gloire et le pouvoir."
        ),
    },
    {
        "position": "A18",
        "item_type": "rule",
        "title": "Piège : nuisance",
        "sort_order": 31,
        "section_tag": "aspirant",
        "inertia_preset": "standard",
        "current_text": (
            "[Piège — réponse attendue : NON]\n"
            "Je cherche à nuire à l'écosystème Ğ1 en m'infiltrant "
            "parmi les forgerons."
        ),
    },
    # ===================================================================
    # CERTIFICATEUR FORGERON — Clauses d'engagement (texte intégral)
    # ===================================================================
    {
        "position": "T2",
        "item_type": "section",
        "title": "Clauses d'engagement du forgeron certificateur",
        "sort_order": 32,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "Les clauses ci-dessous s'adressent au forgeron qui certifie "
            "un aspirant. Elles engagent sa responsabilité de garant des "
            "compétences et pratiques du certifié."
        ),
    },
    # --- Certificateur : Sécurité et conformité (8 clauses) ---
    {
        "position": "C1",
        "item_type": "clause",
        "title": "Intention du certifié questionnée",
        "sort_order": 33,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai questionné l'intention du certifié à rejoindre les "
            "forgerons et je reste prêt à le certifier."
        ),
    },
    {
        "position": "C2",
        "item_type": "verification",
        "title": "Pratiques de sécurité du certifié",
        "sort_order": 34,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai demandé au certifié quelles étaient ses pratiques de "
            "sécurité et je les estime suffisantes pour le réseau actuel."
        ),
    },
    {
        "position": "C3",
        "item_type": "verification",
        "title": "Phrase aléatoire du certifié",
        "sort_order": 35,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "Le certifié m'assure que son compte est issu d'une phrase "
            "de récupération générée aléatoirement."
        ),
    },
    {
        "position": "C4",
        "item_type": "verification",
        "title": "Sauvegarde du certifié",
        "sort_order": 36,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "Le certifié m'assure avoir stocké sa phrase de récupération "
            "sur plusieurs supports physiques (numérique ou non), "
            "récupérables indépendamment. En cas de vol, incendie, oubli "
            "de mot de passe… au moins une des versions lui est accessible."
        ),
    },
    {
        "position": "C5",
        "item_type": "verification",
        "title": "Nœud du certifié vérifié",
        "sort_order": 37,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai vérifié que le certifié gère déjà un nœud à jour "
            "qui est correctement synchronisé."
        ),
    },
    {
        "position": "C6",
        "item_type": "clause",
        "title": "Configuration du certifié notée",
        "sort_order": 38,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai noté le style de configuration (paquet debian, "
            "docker-compose…) que le certifié utilise pour son nœud "
            "(cette configuration n'a pas de faille connue, notamment "
            "n'expose pas l'api unsafe publiquement)."
        ),
    },
    {
        "position": "C7",
        "item_type": "clause",
        "title": "Engagement d'information du certifié",
        "sort_order": 39,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "Le certifié s'est engagé auprès de moi à m'informer de "
            "tout changement significatif de sa config (pour pouvoir "
            "transmettre les infos de manière ciblée en cas de problème)."
        ),
    },
    {
        "position": "C8",
        "item_type": "verification",
        "title": "Risques offline connus du certifié",
        "sort_order": 40,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai vérifié avec le certifié qu'il connaît les risques "
            "pour lui et le réseau d'être offline sans l'avoir annoncé, "
            "et qu'il se considère en mesure d'assurer un uptime suffisant."
        ),
    },
    # --- Certificateur : Contact (3 clauses) ---
    {
        "position": "C9",
        "item_type": "clause",
        "title": "Joindre les certifiés",
        "sort_order": 41,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "Je sais joindre efficacement et rapidement les forgerons "
            "que j'ai certifiés (téléphone généralement)."
        ),
    },
    {
        "position": "C10",
        "item_type": "clause",
        "title": "Deux canaux de contact",
        "sort_order": 42,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "Je peux les joindre par au moins 2 canaux "
            "(sms, email, xmpp, matrix…)."
        ),
    },
    {
        "position": "C11",
        "item_type": "clause",
        "title": "Contact sous 24h en cas de défaut",
        "sort_order": 43,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "Je m'engage à contacter sous 24h max ce forgeron si "
            "j'apprends qu'un défaut concerne son nœud validateur "
            "(désynchronisé, pas à jour, inaccessible…)."
        ),
    },
    # --- Certificateur : Connaissances (3 clauses) ---
    {
        "position": "C12",
        "item_type": "verification",
        "title": "Engagements acceptés par le certifié",
        "sort_order": 44,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai vérifié que le certifié a accepté les engagements "
            "forgerons dans son intégralité."
        ),
    },
    {
        "position": "C13",
        "item_type": "verification",
        "title": "Règles consultables par le certifié",
        "sort_order": 45,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai vérifié que le certifié savait où consulter les "
            "règles détaillées de la TdC forgeron."
        ),
    },
    {
        "position": "C14",
        "item_type": "verification",
        "title": "Délais connus du certifié",
        "sort_order": 46,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "J'ai vérifié que le certifié connaît les délais de passage "
            "en ligne/hors ligne de son nœud validateur."
        ),
    },
    # --- Certificateur : Pièges (2 clauses — réponse attendue : NON) ---
    {
        "position": "C15",
        "item_type": "rule",
        "title": "Piège : certification sous pression",
        "sort_order": 47,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "[Piège — réponse attendue : NON]\n"
            "Je certifie sous la menace ou autre forme de pression."
        ),
    },
    {
        "position": "C16",
        "item_type": "rule",
        "title": "Piège : avantage personnel",
        "sort_order": 48,
        "section_tag": "certificateur",
        "inertia_preset": "standard",
        "current_text": (
            "[Piège — réponse attendue : NON]\n"
            "Je tire un avantage personnel en échange de ma certification."
        ),
    },
    # ===================================================================
    # ANNEXES — Non contractuels, modifiables sans vote
    # ===================================================================
    {
        "position": "X1",
        "item_type": "section",
        "title": "Annexes",
        "sort_order": 49,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "Non contractuels, modifiables sans vote pour clarifier "
            "le contenu de l'engagement."
        ),
    },
    {
        "position": "X1.1",
        "item_type": "preamble",
        "title": "Lexique",
        "sort_order": 50,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "- **Phrase de récupération** : séquence de mots générée "
            "aléatoirement (mnemonic) dont est dérivée la graine "
            "cryptographique (seed, clef privée)\n\n"
            "- **Nœud validateur, smith node** : nœud autorisé et "
            "configuré pour écrire des blocs dans la blockchain. "
            "Chaque compte forgeron peut autoriser un seul nœud "
            "simultanément à être validateur.\n\n"
            "- **Seuil unani-majoritaire** : détermine combien de votes "
            "POUR sont nécessaires pour qu'une proposition soit adoptée."
        ),
    },
    {
        "position": "X1.2",
        "item_type": "rule",
        "title": "Règles de la TdC forgeron",
        "sort_order": 51,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "Pour devenir forgeron, outre respecter les engagements "
            "du présent document, il faut :\n\n"
            "- Être membre de la toile de confiance principale\n"
            "- Être invité par un forgeron\n"
            "- Accepter l'invitation\n"
            "- Recevoir 3 certifications forgeron\n\n"
            "Le rôle forgeron (comme les certifications associées) "
            "n'a pas de péremption directe mais se perd dans les "
            "cas suivants :\n\n"
            "- Perte du statut de membre de la TdC principale\n"
            "- Nœud validateur (forgeron) hors ligne pendant 6 mois"
        ),
    },
    {
        "position": "X1.3",
        "item_type": "rule",
        "title": "Notice d'implémentation",
        "sort_order": 52,
        "section_tag": "annexe",
        "inertia_preset": "low",
        "current_text": (
            "À destination des développeurs de logiciels permettant "
            "de certifier :\n\n"
            "À chaque certification, l'ensemble des clauses d'engagements "
            "forgeron sont présentées une à une au certificateur, dans "
            "un **ordre aléatoire**.\n\n"
            "- Il peut répondre *← non* ou *oui →* à chaque clause\n"
            "- Le document de certification n'est émis qu'après avoir "
            "répondu correctement à chaque clause\n"
            "- Les clauses cochées [x] doivent recevoir pour réponse "
            "*oui →* pour que le document de certification soit émis\n"
            "- Les clauses non cochées [ ] doivent recevoir pour réponse "
            "*← non* pour que le document de certification soit émis\n"
            "- Toute réponse invalide interrompt la procédure et présente "
            "le message : « Attention vous n'avez pas répondu correctement "
            "à la question. Nous vous invitons à relire l'ensemble des "
            "engagements forgerons et à en discuter avec d'autres forgerons "
            "avant de retenter de certifier. »"
        ),
    },
    # ===================================================================
    # FORMULE DE VOTE — Double critère WoT + Smith
    # ===================================================================
    {
        "position": "F1",
        "item_type": "section",
        "title": "Conditions d'adoption et gouvernance",
        "sort_order": 53,
        "section_tag": "formule",
        "inertia_preset": "high",
        "current_text": (
            "Les conditions qui régissent l'adoption ou le rejet de chaque "
            "modification du présent document. Vote à seuil unani-majoritaire "
            "avec double critère : WoT complète + forgerons."
        ),
    },
    {
        "position": "F1.1",
        "item_type": "rule",
        "title": "Formule du seuil WoT + Smith",
        "sort_order": 54,
        "section_tag": "formule",
        "inertia_preset": "high",
        "current_text": (
            "**Double critère d'adoption :**\n\n"
            "```\n"
            "votesPour >= ceil(WotSize^0.1 + (0.5 + (1 - 0.5)\n"
            "  × (1 - (TotalVotes/WotSize)^0.2)) × TotalVotes)\n"
            "```\n"
            "**ET**\n"
            "```\n"
            "votesSmithPour >= ceil(SmithWotSize^0.1)\n"
            "```\n\n"
            "Le premier critère (WoT) assure un soutien communautaire large. "
            "Le second critère (Smith) garantit que les opérateurs du réseau "
            "soutiennent la décision. Les deux doivent être satisfaits."
        ),
    },
    {
        "position": "F1.2",
        "item_type": "rule",
        "title": "Paramètres adoptés",
        "sort_order": 55,
        "section_tag": "formule",
        "inertia_preset": "high",
        "current_text": (
            "**Mode compact : D30M50B.1G.2S.1**\n\n"
            "- **D30** : durée de vote 30 jours (vote permanent)\n"
            "- **M50** : majorité cible 50%\n"
            "- **B.1** : exposant de base 0.1 (plancher WotSize^0.1)\n"
            "- **G.2** : gradient d'inertie 0.2\n"
            "- **S.1** : critère Smith (SmithWotSize^0.1)\n\n"
            "**Comportement :** Faible participation → quasi-unanimité requise. "
            "Forte participation → majorité simple 50% suffit. "
            "Dans tous les cas, un minimum de forgerons doit voter POUR."
        ),
    },
    {
        "position": "F1.3",
        "item_type": "rule",
        "title": "Processus de dépôt officiel",
        "sort_order": 56,
        "section_tag": "formule",
        "inertia_preset": "high",
        "current_text": (
            "Lorsqu'une proposition alternative atteint le seuil d'adoption :\n\n"
            "1. Le texte de remplacement est intégré au document officiel\n"
            "2. Le hash IPFS du document mis à jour est calculé\n"
            "3. Le hash est ancré on-chain via `system.remark`\n"
            "4. Le dépôt git officiel est synchronisé\n"
            "5. Les applications implémentant la checklist mettent à jour"
        ),
    },
    # ===================================================================
    # RÉGLAGE DE L'INERTIE — Méta-protection
    # ===================================================================
    {
        "position": "N1",
        "item_type": "section",
        "title": "Réglage de l'inertie",
        "sort_order": 57,
        "section_tag": "inertie",
        "inertia_preset": "very_high",
        "current_text": (
            "Le réglage de l'inertie définit la difficulté de remplacement "
            "de chaque section du document. Ce réglage est lui-même soumis "
            "à l'inertie la plus élevée, pour empêcher la modification "
            "des règles de modification."
        ),
    },
    {
        "position": "N1.1",
        "item_type": "rule",
        "title": "Niveaux d'inertie",
        "sort_order": 58,
        "section_tag": "inertie",
        "inertia_preset": "very_high",
        "current_text": (
            "**Basse** — G=0.1, M=50%\n"
            "- Descend vite vers 50%. Dès 10% de participation, "
            "seuil proche de la majorité simple\n"
            "- *Usage : annexes (non contractuelles)*\n\n"
            "**Standard** — G=0.2, M=50%\n"
            "- Descente progressive, équilibre stabilité/réactivité\n"
            "- *Usage : clauses d'engagement (aspirant + certificateur)*\n\n"
            "**Haute** — G=0.4, M=60%\n"
            "- Résistante, super-majorité même à pleine participation\n"
            "- *Usage : formule de vote et conditions d'adoption*\n\n"
            "**Très haute** — G=0.6, M=66%\n"
            "- Quasi-unanimité requise à tout niveau\n"
            "- *Usage : réglage de l'inertie (méta-protection)*"
        ),
    },
    # ===================================================================
    # ORDONNANCEMENT
    # ===================================================================
    {
        "position": "O1",
        "item_type": "section",
        "title": "Ordonnancement du document",
        "sort_order": 59,
        "section_tag": "ordonnancement",
        "inertia_preset": "standard",
        "current_text": (
            "L'ordre de présentation des items dans le document est "
            "lui-même soumis au vote. Toute proposition de réorganisation "
            "doit atteindre le seuil d'adoption avec l'inertie standard."
        ),
    },
]


async def seed_document_engagement_forgeron(
    session: AsyncSession,
    protocols: dict[str, VotingProtocol],
    org_id: uuid.UUID | None = None,
) -> Document:
    genesis = json.dumps(GENESIS_FORGERON, ensure_ascii=False, indent=2)

    doc, created = await get_or_create(
        session,
        Document,
        "slug",
        "engagement-forgeron",
        title="Acte d'engagement forgeron",
        doc_type="engagement",
        version="2.0.0",
        status="active",
        description=(
            "Acte d'engagement des forgerons (validateurs) Duniter V2. "
            "Certification de qualification et d'aptitudes techniques. "
            "Adopté en février 2026 (97 pour / 23 contre, WoT 7224). "
            "Document modulaire sous vote permanent avec critère Smith : "
            "59 items — introduction (2) + fondamentaux (4) + techniques (4) "
            "+ qualification (2) + aspirant (19) + certificateur (17) "
            "+ annexes (4) + formule (4) + inertie (2) + ordonnancement (1)."
        ),
        genesis_json=genesis,
    )
    doc.organization_id = org_id
    print(f"  Document 'Acte d'engagement forgeron': {'created' if created else 'exists'}")

    if created:
        # All items use the Smith protocol (double threshold: WoT + Smith)
        smith_protocol = protocols.get("Vote forgeron (Smith)")

        for item_data in ENGAGEMENT_FORGERON_ITEMS:
            item = DocumentItem(
                document_id=doc.id,
                voting_protocol_id=smith_protocol.id if smith_protocol else None,
                **item_data,
            )
            session.add(item)
        await session.flush()
        print(f"    -> {len(ENGAGEMENT_FORGERON_ITEMS)} items created")

    return doc


# ---------------------------------------------------------------------------
# Seed: Decision - Runtime Upgrade
# ---------------------------------------------------------------------------

RUNTIME_UPGRADE_STEPS: list[dict] = [
    {
        "step_order": 1,
        "step_type": "qualification",
        "title": "Qualification",
        "description": (
            "Définir le changement : spécification technique, impact sur le "
            "réseau, justification. Identifier les risques et dépendances."
        ),
    },
    {
        "step_order": 2,
        "step_type": "review",
        "title": "Revue",
        "description": (
            "Audit technique par le Comité Technique et les forgerons. "
            "Revue du code, tests sur testnet, validation de la compatibilité."
        ),
    },
    {
        "step_order": 3,
        "step_type": "vote",
        "title": "Vote",
        "description": (
            "Vote communautaire selon le protocole de vote en vigueur. "
            "Le quorum et le seuil d'adoption dépendent de la formule configurée."
        ),
    },
    {
        "step_order": 4,
        "step_type": "execution",
        "title": "Exécution",
        "description": (
            "Mise à jour on-chain via un extrinsic autorisé. "
            "Coordination avec les forgerons pour la synchronisation des noeuds."
        ),
    },
    {
        "step_order": 5,
        "step_type": "reporting",
        "title": "Suivi",
        "description": (
            "Surveillance post-upgrade : monitoring des métriques réseau, "
            "détection d'anomalies, rapport de stabilité sous 7 jours."
        ),
    },
]


async def seed_decision_runtime_upgrade(session: AsyncSession, org_id: uuid.UUID | None = None) -> Decision:
    decision, created = await get_or_create(
        session,
        Decision,
        "title",
        "Runtime Upgrade",
        description=(
            "Décision on-chain — Mise à jour du runtime Duniter V2. "
            "Requiert le seuil multi-critères : WoT + Smith + TechComm. "
            "Protocole lié : Soumission Runtime Upgrade."
        ),
        context=(
            "Chaque mise à jour du runtime Duniter V2 est une décision "
            "on-chain qui nécessite l'approbation simultanée de trois "
            "critères : le vote communautaire WoT, le quorum des "
            "forgerons Smith, et la validation du Comité Technique. "
            "Le seuil d'adoption est calculé selon la formule multi-critères."
        ),
        decision_type="runtime_upgrade",
        status="draft",
    )
    decision.organization_id = org_id
    print(f"  Decision 'Runtime Upgrade': {'created' if created else 'exists'}")

    if created:
        for step_data in RUNTIME_UPGRADE_STEPS:
            step = DecisionStep(decision_id=decision.id, **step_data)
            session.add(step)
        await session.flush()
        print(f"    -> {len(RUNTIME_UPGRADE_STEPS)} steps created")

    return decision



# ---------------------------------------------------------------------------
# Seed: Simulated voters + votes on first 3 engagement items
# ---------------------------------------------------------------------------

VOTER_NAMES = [
    "Moul", "Poka", "Hugo", "Elois", "Cgeek",
    "Galuel", "Tortue", "Inso", "Tuxmain", "Matograine",
    "Maaltir",
]


async def seed_voters(session: AsyncSession) -> list[DuniterIdentity]:
    """Create 11 simulated Duniter identities for voting demo."""
    voters: list[DuniterIdentity] = []
    for i, name in enumerate(VOTER_NAMES):
        # Deterministic address from name
        addr_hash = hashlib.sha256(name.encode()).hexdigest()[:32]
        address = f"5{addr_hash[:47]}"

        voter, created = await get_or_create(
            session,
            DuniterIdentity,
            "address",
            address,
            display_name=name,
            wot_status="member",
            is_smith=(i < 5),  # First 5 are smiths
        )
        if created:
            print(f"    Voter '{name}': created")
        voters.append(voter)

    return voters


async def seed_votes_on_items(
    session: AsyncSession,
    doc: Document,
    protocol: VotingProtocol,
    voters: list[DuniterIdentity],
):
    """Create vote sessions on first 3 items with 10 for + 1 against."""
    # Fetch items for this document
    stmt = select(DocumentItem).where(
        DocumentItem.document_id == doc.id
    ).order_by(DocumentItem.sort_order).limit(3)
    result = await session.execute(stmt)
    items = result.scalars().all()

    if not items:
        print("    No items found to vote on")
        return

    now = datetime.now(timezone.utc)

    for item in items:
        # Check if a session already exists for this item
        existing_stmt = select(VoteSession).where(
            VoteSession.item_version_id == None,
            VoteSession.voting_protocol_id == protocol.id,
        )
        # We'll use a simpler idempotency check
        session_id = uuid.uuid5(uuid.NAMESPACE_URL, f"seed-vote-{item.id}")
        check_stmt = select(VoteSession).where(VoteSession.id == session_id)
        check_result = await session.execute(check_stmt)
        if check_result.scalar_one_or_none() is not None:
            print(f"    VoteSession for '{item.title}': exists")
            continue

        vote_session = VoteSession(
            id=session_id,
            decision_id=None,
            item_version_id=None,
            voting_protocol_id=protocol.id,
            wot_size=7224,
            smith_size=23,
            techcomm_size=5,
            starts_at=now - timedelta(days=15),
            ends_at=now + timedelta(days=15),
            status="open",
            votes_for=10,
            votes_against=1,
            votes_total=11,
            threshold_required=97.0,
        )
        session.add(vote_session)
        await session.flush()

        # 10 votes "for"
        for voter in voters[:10]:
            payload = f"vote:{vote_session.id}:{voter.id}:for"
            vote = Vote(
                session_id=vote_session.id,
                voter_id=voter.id,
                vote_value="for",
                comment="Oui c'est mieux que l'existant",
                signature=fake_signature(payload),
                signed_payload=payload,
                voter_wot_status="member",
                voter_is_smith=voter.is_smith,
            )
            session.add(vote)

        # 1 vote "against"
        against_voter = voters[10]
        payload = f"vote:{vote_session.id}:{against_voter.id}:against"
        vote = Vote(
            session_id=vote_session.id,
            voter_id=against_voter.id,
            vote_value="against",
            comment="Non, on ne remplace pas tel quel",
            signature=fake_signature(payload),
            signed_payload=payload,
            voter_wot_status="member",
            voter_is_smith=False,
        )
        session.add(vote)

        await session.flush()
        print(f"    VoteSession for '{item.title}': created (10 pour, 1 contre)")


# ---------------------------------------------------------------------------
# Seed: Additional decisions (demo content)
# ---------------------------------------------------------------------------

async def seed_decision_licence_evolution(session: AsyncSession, org_id: uuid.UUID | None = None) -> Decision:
    """Seed a community decision: evolution of the G1 monetary license."""
    decision, created = await get_or_create(
        session,
        Decision,
        "title",
        "Évolution Licence G1 v0.4.0",
        description=(
            "Proposition d'évolution de la Licence G1 vers la version 0.4.0. "
            "Intègre les retours du forum, clarifie les engagements de certification "
            "et précise le processus de vote nuancé."
        ),
        context=(
            "La Licence G1 v0.3.0 est en vigueur depuis l'origine de la monnaie libre. "
            "Des discussions communautaires approfondies (topics 31066, 32375, 32409, 32412) "
            "ont permis d'identifier des clarifications nécessaires. "
            "Cette décision lance le processus de vote communautaire pour l'adoption "
            "de la v0.4.0 selon le protocole Vote WoT standard."
        ),
        decision_type="community",
        status="draft",
    )
    decision.organization_id = org_id
    print(f"  Decision 'Évolution Licence G1 v0.4.0': {'created' if created else 'exists'}")

    if created:
        steps = [
            {
                "step_order": 1,
                "step_type": "qualification",
                "title": "Rédaction de la proposition",
                "description": (
                    "Co-rédaction de la v0.4.0 en intégrant les retours des discussions "
                    "forum. Coordination entre 1000i100, Natha, Pini et la communauté."
                ),
            },
            {
                "step_order": 2,
                "step_type": "review",
                "title": "Période de commentaires",
                "description": (
                    "Publication de la proposition sur le forum pendant 30 jours. "
                    "Recueil des amendements et objections. Intégration des retours "
                    "dans la version finale soumise au vote."
                ),
            },
            {
                "step_order": 3,
                "step_type": "vote",
                "title": "Vote WoT (nuancé)",
                "description": (
                    "Vote nuancé à 6 niveaux ouvert à tous les membres de la WoT. "
                    "Durée : 30 jours. Protocole : Vote WoT standard avec formule inertie."
                ),
            },
            {
                "step_order": 4,
                "step_type": "execution",
                "title": "Mise à jour du dépôt officiel",
                "description": (
                    "Si le seuil est atteint : mise à jour du dépôt git officiel, "
                    "calcul du hash IPFS, ancrage on-chain via system.remark."
                ),
            },
        ]
        for step_data in steps:
            step = DecisionStep(decision_id=decision.id, **step_data)
            session.add(step)
        await session.flush()
        print(f"    -> {len(steps)} steps created")

    return decision


# ---------------------------------------------------------------------------
# Seed: Mandates (Comité Technique + Admin Forgerons)
# ---------------------------------------------------------------------------

async def seed_mandates(session: AsyncSession, voters: list[DuniterIdentity], org_id: uuid.UUID | None = None) -> None:
    """Seed example mandates: TechComm and Smith Admin."""
    now = datetime.now(timezone.utc)

    # Find Charlie (techcomm voter) as mandatee, or use first voter
    mandatee_techcomm = next(
        (v for v in voters if "Cgeek" in v.display_name or "Elois" in v.display_name),
        voters[0] if voters else None,
    )
    mandatee_smith = next(
        (v for v in voters if "Moul" in v.display_name or "Tuxmain" in v.display_name),
        voters[1] if len(voters) > 1 else None,
    )

    mandates_data = [
        {
            "title": "Mandat Comité Technique — Session 2025-2026",
            "description": (
                "Le Comité Technique (ComTech) est mandaté par la communauté pour "
                "assurer la validation technique des propositions on-chain : "
                "runtime upgrades, modifications de paramètres réseau, audits de code. "
                "Composition : 5 membres élus pour 12 mois."
            ),
            "mandate_type": "techcomm",
            "status": "active",
            "mandatee_id": mandatee_techcomm.id if mandatee_techcomm else None,
            "starts_at": now - timedelta(days=90),
            "ends_at": now + timedelta(days=275),
            "steps": [
                {
                    "step_order": 1,
                    "step_type": "candidacy",
                    "title": "Appel à candidatures",
                    "status": "completed",
                    "description": "Période de candidatures ouvertes sur le forum Duniter. Durée : 14 jours.",
                    "outcome": "5 candidats retenus : Elois, Cgeek, Maaltir, Hugo, Tuxmain",
                },
                {
                    "step_order": 2,
                    "step_type": "vote",
                    "title": "Élection par vote nuancé",
                    "status": "completed",
                    "description": "Vote WoT ouvert à tous les membres. Protocole Vote Nuancé (6 niveaux).",
                    "outcome": "Quorum atteint. 5 membres élus à plus de 70% de soutien.",
                },
                {
                    "step_order": 3,
                    "step_type": "assignment",
                    "title": "Prise de fonction",
                    "status": "completed",
                    "description": "Mise en place du ComTech, définition des processus internes de décision.",
                    "outcome": "ComTech opérationnel depuis le 2025-09-15.",
                },
                {
                    "step_order": 4,
                    "step_type": "reporting",
                    "title": "Rapport de mi-mandat",
                    "status": "active",
                    "description": "Rapport public d'activité à mi-parcours du mandat.",
                },
                {
                    "step_order": 5,
                    "step_type": "completion",
                    "title": "Fin de mandat et bilan",
                    "status": "pending",
                    "description": "Rapport final, transmission aux successeurs, renouvellement ou dissolution.",
                },
            ],
        },
        {
            "title": "Mandat Administrateur des Forgerons — Rotation 2026-Q1",
            "description": (
                "L'Administrateur des Forgerons coordonne l'onboarding des nouveaux "
                "forgerons, maintient la liste des nœuds actifs et anime les "
                "discussions techniques de la sous-WoT Smith. "
                "Mandat tournant de 6 mois, renouvelable une fois."
            ),
            "mandate_type": "smith",
            "status": "voting",
            "mandatee_id": mandatee_smith.id if mandatee_smith else None,
            "starts_at": None,
            "ends_at": None,
            "steps": [
                {
                    "step_order": 1,
                    "step_type": "formulation",
                    "title": "Définition du rôle",
                    "status": "completed",
                    "description": "Rédaction de la fiche de rôle et des responsabilités de l'Administrateur.",
                    "outcome": "Fiche de rôle validée par consensus sur le forum Duniter (topic 34201).",
                },
                {
                    "step_order": 2,
                    "step_type": "candidacy",
                    "title": "Appel à candidatures forgerons",
                    "status": "completed",
                    "description": "Candidatures ouvertes aux forgerons actifs depuis plus de 6 mois.",
                    "outcome": "2 candidats : Moul (Forgeron senior), Tuxmain (Forgeron actif).",
                },
                {
                    "step_order": 3,
                    "step_type": "vote",
                    "title": "Vote forgeron (Smith)",
                    "status": "active",
                    "description": (
                        "Vote Smith à double critère : quorum WoT + quorum forgerons. "
                        "Durée : 30 jours. En cours."
                    ),
                },
                {
                    "step_order": 4,
                    "step_type": "assignment",
                    "title": "Prise de fonction",
                    "status": "pending",
                    "description": "Passation avec l'administrateur sortant. Accès aux outils de coordination.",
                },
            ],
        },
        {
            "title": "Mandat Modération Forum — Duniter V2",
            "description": (
                "Équipe de modération élue pour maintenir la qualité des discussions "
                "sur forum.duniter.org et forum.monnaie-libre.fr. "
                "3 modérateurs, mandat de 12 mois."
            ),
            "mandate_type": "custom",
            "status": "draft",
            "mandatee_id": None,
            "starts_at": None,
            "ends_at": None,
            "steps": [
                {
                    "step_order": 1,
                    "step_type": "formulation",
                    "title": "Élaboration de la charte de modération",
                    "status": "active",
                    "description": "Définition des règles, outils et périmètre de la modération communautaire.",
                },
                {
                    "step_order": 2,
                    "step_type": "candidacy",
                    "title": "Appel à candidatures",
                    "status": "pending",
                    "description": "Ouvert à tous les membres de la WoT G1.",
                },
                {
                    "step_order": 3,
                    "step_type": "vote",
                    "title": "Élection",
                    "status": "pending",
                    "description": "Vote WoT standard.",
                },
                {
                    "step_order": 4,
                    "step_type": "assignment",
                    "title": "Prise de fonction",
                    "status": "pending",
                    "description": "Formation aux outils Discourse et mise en place de la rotation.",
                },
            ],
        },
    ]

    for m_data in mandates_data:
        steps_data = m_data.pop("steps")
        mandate, created = await get_or_create(
            session,
            Mandate,
            "title",
            m_data["title"],
            **{k: v for k, v in m_data.items() if k != "title"},
        )
        mandate.organization_id = org_id
        status_str = "created" if created else "exists"
        print(f"  Mandate '{mandate.title[:50]}': {status_str}")

        if created:
            for step_data in steps_data:
                step = MandateStep(mandate_id=mandate.id, **step_data)
                session.add(step)
            await session.flush()
            print(f"    -> {len(steps_data)} steps created")


# ---------------------------------------------------------------------------
# Seed: Organizations
# ---------------------------------------------------------------------------

async def seed_organizations(session: AsyncSession) -> dict[str, Organization]:
    """Create the two base transparent organizations (idempotent)."""
    orgs_data = [
        {
            "slug": "duniter-g1",
            "name": "Duniter G1",
            "org_type": "community",
            "is_transparent": True,
            "color": "#22c55e",
            "icon": "i-lucide-globe",
            "description": "Communauté Duniter — monnaie libre G1. Accessible à tous les membres authentifiés.",
        },
        {
            "slug": "axiom-team",
            "name": "Axiom Team",
            "org_type": "collective",
            "is_transparent": True,
            "color": "#3b82f6",
            "icon": "i-lucide-users",
            "description": "Équipe Axiom — développement et gouvernance des outils communs.",
        },
    ]

    orgs: dict[str, Organization] = {}
    for data in orgs_data:
        org, created = await get_or_create(session, Organization, "slug", data["slug"], **{k: v for k, v in data.items() if k != "slug"})
        status_str = "created" if created else "exists"
        print(f"  Organisation '{org.name}': {status_str}")
        orgs[org.slug] = org

    return orgs


# ---------------------------------------------------------------------------
# Main seed runner
# ---------------------------------------------------------------------------

async def seed_qualification_protocol(session: AsyncSession) -> QualificationProtocol:
    """Seed the default qualification protocol (thresholds for the Décider tunnel)."""
    stmt = select(QualificationProtocol).where(QualificationProtocol.is_active == True)  # noqa: E712
    result = await session.execute(stmt)
    existing = result.scalar_one_or_none()
    if existing is not None:
        print("  [skip] Protocole de qualification déjà présent")
        return existing

    proto = QualificationProtocol(
        name="Protocole de qualification par défaut",
        description=(
            "Seuils utilisés par le tunnel Décider pour router vers "
            "individual/collective et proposer les modalités de vote."
        ),
        small_group_max=5,
        collective_wot_min=50,
        default_modalities_json=json.dumps([
            "vote_wot",
            "vote_smith",
            "consultation_avis",
            "election",
        ]),
        is_active=True,
    )
    session.add(proto)
    await session.flush()
    print("  [ok] Protocole de qualification créé")
    return proto


async def run_seed():
    print("=" * 60)
    print("libreDecision - Seed Database")
    print("=" * 60)

    # Ensure tables exist
    await init_db()
    print("[0/10] Tables created.\n")

    async with async_session() as session:
        async with session.begin():
            print("\n[0/10] Organizations...")
            orgs = await seed_organizations(session)
            duniter_g1_id = orgs["duniter-g1"].id

            print("\n[0b] Protocole de qualification...")
            await seed_qualification_protocol(session)

            print("\n[1/10] Formula Configs...")
            formulas = await seed_formula_configs(session)

            print("\n[2/10] Voting Protocols...")
            protocols = await seed_voting_protocols(session, formulas, org_id=duniter_g1_id)

            print("\n[3/10] Document: Acte d'engagement Certification...")
            await seed_document_engagement_certification(session, protocols, org_id=duniter_g1_id)

            print("\n[4/10] Document: Acte d'engagement forgeron v2.0.0...")
            doc_forgeron = await seed_document_engagement_forgeron(session, protocols, org_id=duniter_g1_id)

            print("\n[5/10] Decision: Runtime Upgrade...")
            await seed_decision_runtime_upgrade(session, org_id=duniter_g1_id)

            print("\n[6/10] Decision: Évolution Licence G1 v0.4.0...")
            await seed_decision_licence_evolution(session, org_id=duniter_g1_id)

            print("\n[7/10] Simulated voters...")
            voters = await seed_voters(session)

            print("\n[8/10] Votes on first 3 engagements forgeron...")
            await seed_votes_on_items(
                session,
                doc_forgeron,
                protocols["Vote forgeron (Smith)"],
                voters,
            )

            print("\n[9/10] Mandates...")
            await seed_mandates(session, voters, org_id=duniter_g1_id)

            print("\n[10/10] Done.")

        print("\n" + "=" * 60)
        print("Seed complete.")
        print("=" * 60)


if __name__ == "__main__":
    asyncio.run(run_seed())
