#!/usr/bin/env python3
"""
Moteur de prix du calculateur Support-Héros (volume-first, orienté résultat).

Aucune constante en dur: tout vient de pricing_config.json (éditable).
Principe: prix = heures standard estimées x taux cible, majoré selon la
complexité (type client, secteur), arrondi à la cinquantaine supérieure,
plancher au forfait minimum. Le sur-mesure (bloc Métier & Adaptation) n'est
jamais auto-chiffré: il porte un flag RDV. Toute estimation se termine sur un RDV.

Usage:
    from pricing_engine import load_config, estimate
    cfg = load_config()
    res = estimate({
        "nb_factures_vente": 30, "nb_depenses": 20,
        "nb_collaborateurs": 5, "nb_lignes_bancaires": 2,
        "type_client": 1, "secteur": 1,
        "services": ["ventes", "achats", "rh", "precompta"],
        "metier_adaptation": False,
    }, cfg)
"""
import json
import math
import os

HERE = os.path.dirname(os.path.abspath(__file__))
CONFIG_PATH = os.path.join(HERE, "pricing_config.json")


def load_config(path=CONFIG_PATH):
    with open(path, encoding="utf-8") as f:
        return json.load(f)


def _ceil_to(value, step):
    if step and step > 0:
        return int(math.ceil(value / step) * step)
    return int(math.ceil(value))


def estimate(inputs, cfg):
    """Compute a monthly forfait estimate from prospect inputs.

    inputs keys (all optional, default 0 / off):
      nb_factures_vente, nb_depenses, nb_collaborateurs, nb_lignes_bancaires
      type_client (1..5), secteur (1..9)
      services: list of active standard block keys (default: all with driver>0)
      metier_adaptation: bool (adds the RDV-scoped block)
    Returns a dict with the price, the per-block breakdown, and flags.
    """
    rate = cfg["taux_cible_eur_h"]
    blocks = cfg["blocs_standards"]

    # Which standard blocks are active: explicit selection, else any with volume.
    selected = inputs.get("services")
    breakdown = []
    total_hours = 0.0
    for key, b in blocks.items():
        driver_val = float(inputs.get(b["driver"], 0) or 0)
        active = (key in selected) if selected is not None else (driver_val > 0)
        if not active or driver_val <= 0:
            continue
        # Volume degressivity (smooth economies of scale from unit 1):
        # total_hours = (minutes/60) * q^(1 - elasticite).
        e = float(b.get("degressivite_elasticite", 0) or 0)
        hours = (b["minutes_par_unite"] / 60.0) * (driver_val ** (1 - e))
        total_hours += hours
        eff_min = hours * 60.0 / driver_val if driver_val else 0
        breakdown.append({
            "bloc": key,
            "label": b["label"],
            "driver": b["driver"],
            "quantite": driver_val,
            "minutes_par_unite": b["minutes_par_unite"],
            "minutes_effectives_par_unite": round(eff_min, 1),
            "heures": round(hours, 2),
            "montant_ht": round(hours * rate, 2),
        })

    # Add-on missions (options): 'heures' add priced time, 'rdv' flag scoping.
    options_out = []
    opts_rdv = False
    catalogue = {o["id"]: o for o in cfg.get("options_missions", {}).get("catalogue", [])}
    for oid in inputs.get("options", []) or []:
        o = catalogue.get(oid)
        if not o:
            continue
        if o["type"] == "heures":
            h = float(o.get("heures_mois", 0) or 0)
            total_hours += h
            options_out.append({"id": oid, "label": o["label"], "type": "heures",
                                 "heures": h, "montant_ht": round(h * rate, 2)})
        else:
            opts_rdv = True
            options_out.append({"id": oid, "label": o["label"], "type": "rdv",
                                "montant_ht": None})

    subtotal = total_hours * rate

    # Complexity majorations (applied to the standard subtotal).
    maj_type = cfg["majoration_type_client"].get(str(inputs.get("type_client", 1)), 0.0)
    maj_sect = cfg["majoration_secteur"].get(str(inputs.get("secteur", 1)), 0.0)

    # Optional CA modulator (off by default; CA proved a poor driver in Phase 0).
    ca_factor = 1.0
    mod = cfg.get("modulateur_ca", {})
    if mod.get("actif"):
        ca = float(inputs.get("ca", 0) or 0)
        for p in mod.get("paliers", []):
            if ca >= p.get("ca_min", 0):
                ca_factor = p.get("facteur", 1.0)

    price_raw = subtotal * (1 + maj_type + maj_sect) * ca_factor

    # Floor + round up.
    price_floored = max(price_raw, cfg.get("forfait_minimum_eur", 0)) if price_raw > 0 else 0
    price = _ceil_to(price_floored, cfg.get("arrondi_superieur_a", 1)) if price_floored > 0 else 0

    # Métier & Adaptation block: never auto-priced, flags a RDV.
    metier = bool(inputs.get("metier_adaptation"))
    mb = cfg["bloc_metier_adaptation"]

    return {
        "forfait_mensuel_ht": price,
        "heures_standard_estimees": round(total_hours, 2),
        "taux_implicite": round(price / total_hours, 1) if total_hours else None,
        "sous_total_ht": round(subtotal, 2),
        "majoration_type_client": maj_type,
        "majoration_secteur": maj_sect,
        "facteur_ca": ca_factor,
        "detail_blocs": breakdown,
        "options_missions": options_out,
        "bloc_metier_adaptation": {
            "selectionne": metier,
            "label": mb["label"],
            "auto_price": False,
            "action": mb["action"] if metier else None,
        },
        "necessite_rdv": True,  # toujours: l'estimation se valide en RDV
        "onboarding_ht": cfg.get("onboarding", {}).get("montant_eur", 0) if cfg.get("onboarding", {}).get("actif") else 0,
        "note": "Estimation indicative. À valider/ajuster en RDV.",
    }


if __name__ == "__main__":
    cfg = load_config()
    demo = estimate({
        "nb_factures_vente": 30, "nb_depenses": 20,
        "nb_collaborateurs": 5, "nb_lignes_bancaires": 2,
        "type_client": 4, "secteur": 8, "metier_adaptation": True,
    }, cfg)
    print(json.dumps(demo, ensure_ascii=False, indent=2))
