"""Rendu Markdown du dossier bancable (une langue) + manifeste agrégé.

Le rendu place toujours en tête la bannière CONFIDENTIEL (le Portail Bancables
est privé · `PORTAIL_BANCABLES_4BIG.md`) et, pour une fixture synthétique, la
bannière « ne jamais publier » (#6). Toute figure absente est rendue en
placeholder `{{…}}` (jamais un chiffre inventé).
"""

from __future__ import annotations

from typing import Any, Optional

from . import deps, finance, i18n

CANONICAL = deps.CANONICAL


# --------------------------------------------------------------------------- #
# Helpers de rendu de valeurs.
# --------------------------------------------------------------------------- #
def _money(devise: str, n: Optional[float], placeholder: str) -> str:
    """« USD 250,000 » (format Letter US · milliers séparés). Placeholder si None."""
    if n is None:
        return "{{" + placeholder + "}}"
    entier = f"{n:,.0f}" if n == int(n) else f"{n:,.2f}"
    return f"{devise} {entier}"


def _int(n: Optional[float], placeholder: str) -> str:
    if n is None:
        return "{{" + placeholder + "}}"
    return f"{int(n):,}" if n == int(n) else f"{n:,.2f}"


def _pct(n: Optional[float], placeholder: str) -> str:
    if n is None:
        return "{{" + placeholder + "}}"
    return f"{n:g} %"


def _txt(value: Any, placeholder: str) -> str:
    if value is None:
        return "{{" + placeholder + "}}"
    return str(value).strip()


# --------------------------------------------------------------------------- #
# Rendu d'une langue.
# --------------------------------------------------------------------------- #
def render_report(brief: dict, lang: str) -> str:
    """Retourne le Markdown du dossier bancable pour `lang` ∈ {fr,en,es}."""
    L = lambda k: i18n.t(lang, k)  # noqa: E731 — raccourci local lisible
    code = brief["projet"]
    nom = _txt(brief.get("nom"), "nom")

    src = finance.sourced(brief)
    rows = finance.typologies(brief)
    figs = {f["cle"]: f for f in finance.derived(brief)}
    manquants = finance.missing_fields(brief)

    parts: list[str] = []

    # En-tête + bannières.
    parts.append(f"# {nom} · {code}\n")
    parts.append(f"**{L('doc_title')}** · {i18n.LANG_FLAG[lang]} "
                 f"{i18n.LANG_NOM[lang]}\n")
    parts.append(f"> {L('confidential')}\n")
    if brief.get("synthetique"):
        parts.append(f"> {L('synthetic')}\n")

    # 1. Positionnement (chaîne libre du brief, langue par langue).
    com = brief.get("commercial") or {}
    positionnement = com.get(f"positionnement_{lang}")
    pos_txt = (str(positionnement).strip()
               if deps.is_filled(positionnement)
               else "{{positionnement_" + lang + "}}")
    parts.append(f"## {L('sec_positioning')}\n\n{pos_txt}\n")

    # 2. Données financières sourcées (verbatim).
    parts.append(f"## {L('sec_sourced')}\n")
    parts.append(
        f"- **{L('lbl_cout_construction')}** : "
        f"{_money('USD', src['cout_construction_usd'], 'cout_construction')}\n"
        f"- **{L('lbl_revenu_brut')}** : "
        f"{_money('USD', src['revenu_brut_usd'], 'revenu_brut')}\n"
        f"- **{L('lbl_marge')}** : {_pct(src['marge_pct'], 'marge')}\n"
        f"- **{L('lbl_regime_fiscal')}** : "
        f"{_txt(src['regime_fiscal'], 'regime_fiscal')}\n"
    )
    taux = src["taux_conversion"]
    taux_val = _int(taux["taux"], "taux")
    parts.append(
        f"- **{L('lbl_taux')}** : {taux_val} "
        f"({_txt(taux['date'], 'taux_date')} · {L('note_taux_source')} "
        f"{_txt(taux['source'], 'taux_source')})\n"
    )

    # 3. Grille tarifaire par typologie (sourcé + sous-total tracé).
    parts.append(f"## {L('sec_typologies')}\n")
    header = (f"| {L('th_typologie')} | {L('th_quantite')} | {L('th_prix_usd')} "
              f"| {L('th_prix_dop')} | {L('th_soustotal')} |\n"
              "|---|---|---|---|---|\n")
    body = ""
    if rows:
        for i, r in enumerate(rows):
            body += (
                f"| {_txt(r['nom'], f'typo_{i+1}_nom')} "
                f"| {_int(r['quantite'], f'typo_{i+1}_qte')} "
                f"| {_money('USD', r['prix_usd'], f'typo_{i+1}_usd')} "
                f"| {_money('DOP', r['prix_dop'], f'typo_{i+1}_dop')} "
                f"| {_money('USD', r['sous_total_usd'], f'typo_{i+1}_st')} |\n"
            )
    else:
        body = ("| {{typo_1_nom}} | {{typo_1_qte}} | {{typo_1_usd}} "
                "| {{typo_1_dop}} | {{typo_1_st}} |\n")
    parts.append(header + body + "\n")

    # 4. Agrégats & emplois — calculs traçables (formule affichée).
    parts.append(f"## {L('sec_derived')}\n")
    tu = figs["total_unites"]
    vcu = figs["valeur_catalogue_usd"]
    vcd = figs["valeur_catalogue_dop"]
    pe = figs["point_equilibre_unites"]
    parts.append(
        f"- **{L('lbl_total_unites')}** : {_int(tu['valeur'], 'total_unites')} "
        f"_({L('note_calcule')} : {tu['formule']})_\n"
        f"- **{L('lbl_valeur_catalogue_usd')}** : "
        f"{_money('USD', vcu['valeur'], 'valeur_catalogue_usd')} "
        f"_({L('note_calcule')} : {vcu['formule']})_\n"
        f"- **{L('lbl_valeur_catalogue_dop')}** : "
        f"{_money('DOP', vcd['valeur'], 'valeur_catalogue_dop')} "
        f"_({L('note_calcule')} : {vcd['formule']})_\n"
    )
    pe_val = (f"{_int(pe['valeur'], 'point_equilibre_unites')} "
              f"{L('word_units')} ({L('word_rounded')})"
              if pe["valeur"] is not None
              else "{{point_equilibre_unites}}")
    parts.append(
        f"- **{L('lbl_point_equilibre_unites')}** : {pe_val} "
        f"_({L('note_calcule')} : {pe['formule']})_\n"
    )

    # 5. Paramètres canoniques OTO (verbatim · #9/#10 · base non multipliée).
    parts.append(f"## {L('sec_canonical')}\n")
    parts.append(
        f"- **{L('lbl_edition')}** : {CANONICAL['frais_edition_pct']} "
        f"({L('note_fixe')} · {L('word_base')})\n"
        f"- **{L('lbl_marketing')}** : {CANONICAL['marketing_pct']} "
        f"({L('note_fixe')} · {L('word_base')})\n"
        f"- **{L('lbl_point_equilibre_pct')}** : "
        f"{CANONICAL['point_equilibre_pct']} ({L('note_fixe')})\n"
        f"- **{L('lbl_devises')}** : {CANONICAL['devises']}\n"
        f"- **{L('lbl_paiement')}** : {CANONICAL['paiement']}\n"
        f"- **{L('lbl_format_doc')}** : {CANONICAL['format_doc']}\n"
    )

    # Champs manquants (transparence anti-invention).
    if manquants:
        parts.append(f"## {L('note_missing')}\n")
        parts.append("".join(f"- {m}\n" for m in manquants))

    parts.append(f"\n---\n_{L('note_generated')} {code}._\n")
    return "\n".join(parts)


# --------------------------------------------------------------------------- #
# Manifeste agrégé (une seule vue machine-lisible des 3 langues).
# --------------------------------------------------------------------------- #
def _publiable(brief: dict, manquants: list[str]) -> bool:
    """Publiable ssi : non synthétique ET cœur financier sourcé complet.

    On n'exige PAS zéro champ manquant (un taux daté peut manquer sans bloquer),
    mais le trio coût/revenu/marge + au moins une typologie tarifée est requis —
    sans quoi le dossier n'a pas de substance bancable.
    """
    if brief.get("synthetique"):
        return False
    src = finance.sourced(brief)
    if src["cout_construction_usd"] is None or src["revenu_brut_usd"] is None \
            or src["marge_pct"] is None:
        return False
    rows = finance.typologies(brief)
    tarifs_ok = any(
        r["quantite"] is not None and r["prix_usd"] is not None for r in rows
    )
    return bool(rows) and tarifs_ok


def build_manifest(brief: dict, generated_at: Optional[str] = None) -> dict:
    """Manifeste agrégé (contrat bancable.schema.json)."""
    code = brief["projet"]
    src = finance.sourced(brief)
    manquants = finance.missing_fields(brief)
    figs = finance.derived(brief)

    manifest: dict[str, Any] = {
        "projet": code,
        "template_version": deps.TEMPLATE_VERSION,
        "langues": list(i18n.LANGS),
        "synthetique": bool(brief.get("synthetique")),
        "publiable": _publiable(brief, manquants),
        "figures_sourcees": {
            "cout_construction_usd": src["cout_construction_usd"],
            "revenu_brut_usd": src["revenu_brut_usd"],
            "marge_pct": src["marge_pct"],
            "taux_conversion": {
                "taux": src["taux_conversion"]["taux"],
                "date": src["taux_conversion"]["date"],
                "source": src["taux_conversion"]["source"],
            },
        },
        "figures_calculees": [
            {
                "cle": f["cle"],
                "libelle": i18n.t("fr", f"lbl_{f['cle']}"),
                "formule": f["formule"],
                "valeur": f["valeur"],
                "unite": f["unite"],
            }
            for f in figs
        ],
        "typologies_count": len(finance.typologies(brief)),
        "champs_manquants": manquants,
        "fichiers": [f"50_financier_bancable/{lang}.md" for lang in i18n.LANGS],
    }
    if generated_at is not None:
        manifest["generated_at"] = generated_at
    return manifest


def render_all(brief: dict, generated_at: Optional[str] = None) -> dict[str, str]:
    """Retourne {chemin_relatif: contenu} pour les 3 langues + le manifeste."""
    import json

    files: dict[str, str] = {}
    for lang in i18n.LANGS:
        files[f"50_financier_bancable/{lang}.md"] = render_report(brief, lang)
    files["50_financier_bancable/manifest.json"] = json.dumps(
        build_manifest(brief, generated_at), ensure_ascii=False, indent=2
    ) + "\n"
    return files
