"""
CA Automator — Business Advisory computation engines.

Pure functions only (no DB access), mirrored line-for-line in
web/business_engines.js — same discipline as tp_engines.py / corporate_engines.py.
Fourteen named tools reduce to four real calculators:

  - profitability_analysis — Product-wise / Customer-wise / Branch-wise
    Profitability Tools are the SAME register read three ways (filter by
    dimension_type before calling this, or pass it all and group client-side);
    Profitability Analyzer is this function's per-dimension margin output;
    Cost Structure Analyzer is its aggregate direct-cost/overhead split.
  - pricing_recommendation — cost-plus pricing off a target margin, benchmarked
    against a competitor price and the current price. Plain arithmetic, not a
    market-research tool.
  - budget_variance — Budget Preparation Automator's numbers ARE these rows;
    Budget vs Actual Analyzer is the variance this function computes over them.
  - kpi_dashboard — KPI Dashboard Automator and Management Dashboard Generator
    both read this: latest value, trend against the prior period, and
    variance against target (direction-aware via higher_is_better).
"""

R2 = lambda x: round(float(x or 0), 2)


def _n(v, default=0.0):
    if v is None or v == "":
        return default
    try:
        return float(v)
    except (TypeError, ValueError):
        return default


def _safe_div(a, b):
    return (a / b) if b else 0.0


def profitability_analysis(lines):
    """Aggregates ba_profitability_line rows by (dimension_type, dimension_name) across all periods passed in."""
    groups = {}
    for r in lines or []:
        key = (r.get("dimension_type", "overall"), r.get("dimension_name", ""))
        g = groups.setdefault(key, {"dimension_type": key[0], "dimension_name": key[1],
                                    "revenue": 0.0, "direct_cost": 0.0, "allocated_overhead": 0.0})
        g["revenue"] += _n(r.get("revenue"))
        g["direct_cost"] += _n(r.get("direct_cost"))
        g["allocated_overhead"] += _n(r.get("allocated_overhead"))

    total_revenue = sum(g["revenue"] for g in groups.values())
    rows = []
    for g in groups.values():
        gross_margin = g["revenue"] - g["direct_cost"]
        net_margin = gross_margin - g["allocated_overhead"]
        rows.append({
            "dimension_type": g["dimension_type"], "dimension_name": g["dimension_name"],
            "revenue": R2(g["revenue"]), "direct_cost": R2(g["direct_cost"]),
            "allocated_overhead": R2(g["allocated_overhead"]),
            "gross_margin": R2(gross_margin), "gross_margin_pct": R2(_safe_div(gross_margin, g["revenue"]) * 100),
            "net_margin": R2(net_margin), "net_margin_pct": R2(_safe_div(net_margin, g["revenue"]) * 100),
            "revenue_share_pct": R2(_safe_div(g["revenue"], total_revenue) * 100),
        })
    rows.sort(key=lambda r: r["net_margin"], reverse=True)

    total_direct_cost = sum(g["direct_cost"] for g in groups.values())
    total_overhead = sum(g["allocated_overhead"] for g in groups.values())
    total_gross_margin = total_revenue - total_direct_cost
    total_net_margin = total_gross_margin - total_overhead
    cost_structure = {
        "total_revenue": R2(total_revenue), "total_direct_cost": R2(total_direct_cost),
        "total_overhead": R2(total_overhead),
        "direct_cost_pct_of_revenue": R2(_safe_div(total_direct_cost, total_revenue) * 100),
        "overhead_pct_of_revenue": R2(_safe_div(total_overhead, total_revenue) * 100),
        "total_gross_margin": R2(total_gross_margin), "total_gross_margin_pct": R2(_safe_div(total_gross_margin, total_revenue) * 100),
        "total_net_margin": R2(total_net_margin), "total_net_margin_pct": R2(_safe_div(total_net_margin, total_revenue) * 100),
    }
    loss_makers = [r for r in rows if r["net_margin"] < 0]
    best = rows[0] if rows else None
    worst = rows[-1] if rows else None

    return {"rows": rows, "cost_structure": cost_structure, "loss_makers": loss_makers,
           "best_performer": best, "worst_performer": worst}


def pricing_recommendation(inputs):
    """Cost-plus price off a target margin (defined as % of selling price, the standard convention), vs competitor and current price."""
    unit_cost = _n(inputs.get("unit_cost"))
    target_margin_pct = _n(inputs.get("target_margin_pct"))
    competitor_price = inputs.get("competitor_price")
    current_price = inputs.get("current_price")

    denom = 1 - (target_margin_pct / 100)
    recommended_price = (unit_cost / denom) if denom > 0 else None
    markup_pct = R2(_safe_div(recommended_price - unit_cost, unit_cost) * 100) if recommended_price is not None else None

    out = {"unit_cost": R2(unit_cost), "target_margin_pct": target_margin_pct,
          "recommended_price": R2(recommended_price) if recommended_price is not None else None,
          "markup_pct": markup_pct}
    if competitor_price not in (None, ""):
        cp = _n(competitor_price)
        out["competitor_price"] = R2(cp)
        out["gap_vs_competitor"] = R2((recommended_price or 0) - cp)
        out["gap_vs_competitor_pct"] = R2(_safe_div((recommended_price or 0) - cp, cp) * 100) if cp else None
    if current_price not in (None, ""):
        cur = _n(current_price)
        out["current_price"] = R2(cur)
        out["current_margin_pct"] = R2(_safe_div(cur - unit_cost, cur) * 100) if cur else None
        out["price_change_needed"] = R2((recommended_price or 0) - cur)
    return out


def budget_variance(lines):
    """Per-line and aggregate budget-vs-actual variance. Favorable direction depends on line_type."""
    rows = []
    for r in lines or []:
        line_type = r.get("line_type", "expense")
        budgeted = _n(r.get("budgeted_amount"))
        actual = r.get("actual_amount")
        has_actual = actual not in (None, "")
        actual_v = _n(actual)
        variance = actual_v - budgeted if has_actual else None
        variance_pct = R2(_safe_div(variance, budgeted) * 100) if (has_actual and budgeted) else None
        favorable = None
        if has_actual:
            favorable = (variance >= 0) if line_type == "revenue" else (variance <= 0)
        rows.append({
            "id": r.get("id"), "category": r.get("category", ""), "line_type": line_type,
            "period_label": r.get("period_label", ""), "budgeted_amount": R2(budgeted),
            "actual_amount": R2(actual_v) if has_actual else None,
            "variance": R2(variance) if has_actual else None, "variance_pct": variance_pct,
            "favorable": favorable,
        })

    def total(line_type, key):
        return sum(_n(r[key]) for r in rows if r["line_type"] == line_type and r[key] is not None)

    budgeted_revenue = total("revenue", "budgeted_amount")
    actual_revenue = total("revenue", "actual_amount")
    budgeted_expense = total("expense", "budgeted_amount")
    actual_expense = total("expense", "actual_amount")
    summary = {
        "budgeted_revenue": R2(budgeted_revenue), "actual_revenue": R2(actual_revenue),
        "budgeted_expense": R2(budgeted_expense), "actual_expense": R2(actual_expense),
        "budgeted_profit": R2(budgeted_revenue - budgeted_expense), "actual_profit": R2(actual_revenue - actual_expense),
    }
    unfavorable = [r for r in rows if r["favorable"] is False]
    return {"rows": rows, "summary": summary, "unfavorable_count": len(unfavorable), "unfavorable_lines": unfavorable}


def kpi_dashboard(kpis, values_by_kpi):
    """kpis: [{id, name, category, unit, target_value, higher_is_better}]
       values_by_kpi: {kpi_id: [{period_label, actual_value}, ...]} — any period ordering, sorted here."""
    out = []
    for k in kpis or []:
        vals = sorted(values_by_kpi.get(k["id"], []) or [], key=lambda v: v.get("period_label", ""))
        latest = vals[-1] if vals else None
        previous = vals[-2] if len(vals) >= 2 else None
        latest_value = latest["actual_value"] if latest else None
        previous_value = previous["actual_value"] if previous else None
        trend = None
        if latest_value is not None and previous_value is not None:
            trend = "up" if latest_value > previous_value else ("down" if latest_value < previous_value else "flat")
        target = k.get("target_value")
        higher_is_better = k.get("higher_is_better", 1)
        on_target = None
        variance_vs_target = None
        if latest_value is not None and target not in (None, ""):
            variance_vs_target = R2(latest_value - _n(target))
            on_target = (latest_value >= _n(target)) if higher_is_better else (latest_value <= _n(target))
        out.append({
            "id": k["id"], "name": k.get("name", ""), "category": k.get("category", ""), "unit": k.get("unit", ""),
            "target_value": target, "higher_is_better": bool(higher_is_better),
            "latest_period": latest["period_label"] if latest else None,
            "latest_value": R2(latest_value) if latest_value is not None else None,
            "previous_value": R2(previous_value) if previous_value is not None else None,
            "trend": trend, "variance_vs_target": variance_vs_target, "on_target": on_target,
            "history": [{"period_label": v["period_label"], "actual_value": R2(v["actual_value"])} for v in vals],
        })
    off_target = [k for k in out if k["on_target"] is False]
    return {"kpis": out, "off_target_count": len(off_target)}
