"""
CA Automator — IPO / Capital Market computation engines.

Pure functions only (no DB access), mirrored line-for-line in
web/ipo_engines.js — same discipline as business_engines.py / corporate_engines.py.
Twelve named tools reduce to five real calculators plus a checklist read
(readiness_summary), with three tools deliberately NOT re-derived here
because they are already-built modules elsewhere in this app:

  - IPO Financial Due Diligence Tool  -> the existing `dd` module
  - IPO Financial Projection Model    -> the existing `projection` module
  - IPO Valuation Model               -> the existing `valuation` module

None of the SEBON-specific numeric eligibility thresholds are hard-coded
anywhere in this file — readiness_summary only compares whatever
threshold_value the firm has itself entered against the actual_value on the
same row; if no threshold has been entered, that row is simply not judged,
never defaulted to a guessed figure.
"""

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 restatement_analysis(lines):
    """Previously-reported vs restated, per line and totalled by fiscal year."""
    rows = []
    for r in lines or []:
        prev = _n(r.get("previously_reported"))
        restated = _n(r.get("restated"))
        variance = restated - prev
        rows.append({
            "id": r.get("id"), "fy_label": r.get("fy_label", ""), "account": r.get("account", ""),
            "previously_reported": R2(prev), "restated": R2(restated), "variance": R2(variance),
            "variance_pct": R2(_safe_div(variance, prev) * 100) if prev else None,
            "reason": r.get("reason", ""),
        })
    by_fy = {}
    for r in rows:
        by_fy.setdefault(r["fy_label"], 0.0)
        by_fy[r["fy_label"]] += r["variance"]
    total_absolute_impact = sum(abs(r["variance"]) for r in rows)
    return {"rows": rows, "impact_by_fy": {k: R2(v) for k, v in by_fy.items()},
           "total_absolute_impact": R2(total_absolute_impact), "lines_restated": len(rows)}


def working_capital_adequacy(projections):
    """Per period: does available operating cash + allocated IPO proceeds cover the projected requirement?"""
    rows = []
    cumulative_gap = 0.0
    for r in sorted(projections or [], key=lambda x: x.get("period_label", "")):
        required = _n(r.get("projected_requirement"))
        available = _n(r.get("available_from_operations")) + _n(r.get("ipo_proceeds_allocated"))
        gap = required - available
        cumulative_gap += gap
        rows.append({
            "id": r.get("id"), "period_label": r.get("period_label", ""),
            "projected_requirement": R2(required), "available_from_operations": R2(_n(r.get("available_from_operations"))),
            "ipo_proceeds_allocated": R2(_n(r.get("ipo_proceeds_allocated"))), "total_available": R2(available),
            "gap": R2(gap), "adequate": gap <= 0, "cumulative_gap": R2(cumulative_gap),
        })
    shortfalls = [r for r in rows if not r["adequate"]]
    return {"rows": rows, "shortfall_periods": len(shortfalls), "max_cumulative_gap": R2(max((r["cumulative_gap"] for r in rows), default=0.0)),
           "overall_adequate": len(shortfalls) == 0}


def peer_comparison(peers, subject):
    """Peer P/E and P/BV (from market price / EPS / book value per peer row), subject's implied price at peer averages."""
    rows = []
    for p in peers or []:
        price = _n(p.get("market_price"))
        eps = _n(p.get("eps"))
        bvps = _n(p.get("book_value_per_share"))
        pe = _safe_div(price, eps) if eps else None
        pbv = _safe_div(price, bvps) if bvps else None
        rows.append({"id": p.get("id"), "peer_name": p.get("peer_name", ""), "listed": bool(p.get("listed", True)),
                    "market_price": R2(price), "eps": R2(eps), "book_value_per_share": R2(bvps),
                    "roe_pct": p.get("roe_pct"), "pe_ratio": R2(pe) if pe is not None else None,
                    "pbv_ratio": R2(pbv) if pbv is not None else None})

    pes = [r["pe_ratio"] for r in rows if r["pe_ratio"] is not None]
    pbvs = [r["pbv_ratio"] for r in rows if r["pbv_ratio"] is not None]
    avg_pe = R2(sum(pes) / len(pes)) if pes else None
    avg_pbv = R2(sum(pbvs) / len(pbvs)) if pbvs else None
    median_pe = R2(sorted(pes)[len(pes) // 2]) if pes else None

    subject = subject or {}
    subject_eps = _n(subject.get("eps"))
    subject_bvps = _n(subject.get("book_value_per_share"))
    subject_price = subject.get("proposed_price")
    implied_price_at_avg_pe = R2(avg_pe * subject_eps) if (avg_pe is not None and subject_eps) else None
    implied_price_at_avg_pbv = R2(avg_pbv * subject_bvps) if (avg_pbv is not None and subject_bvps) else None

    out = {"peers": rows, "avg_pe": avg_pe, "median_pe": median_pe, "avg_pbv": avg_pbv,
          "implied_price_at_avg_pe": implied_price_at_avg_pe, "implied_price_at_avg_pbv": implied_price_at_avg_pbv}
    if subject_price not in (None, ""):
        sp = _n(subject_price)
        out["proposed_price"] = R2(sp)
        out["premium_vs_avg_pe_price_pct"] = R2(_safe_div(sp - implied_price_at_avg_pe, implied_price_at_avg_pe) * 100) \
            if implied_price_at_avg_pe else None
    return out


def pricing_analysis(scenario):
    """Implied P/E at the proposed price, and premium/discount against the valuation-per-share figure the preparer carried over."""
    face_value = _n(scenario.get("face_value"), 100)
    proposed_price = _n(scenario.get("proposed_price"))
    eps = scenario.get("subject_eps")
    valuation_per_share = scenario.get("valuation_per_share")

    out = {"face_value": R2(face_value), "proposed_price": R2(proposed_price),
          "premium_to_face_value": R2(proposed_price - face_value),
          "premium_to_face_value_pct": R2(_safe_div(proposed_price - face_value, face_value) * 100) if face_value else None}
    if eps not in (None, ""):
        eps_v = _n(eps)
        out["subject_eps"] = R2(eps_v)
        out["implied_pe"] = R2(_safe_div(proposed_price, eps_v)) if eps_v else None
    if valuation_per_share not in (None, ""):
        vps = _n(valuation_per_share)
        out["valuation_per_share"] = R2(vps)
        out["gap_vs_valuation"] = R2(proposed_price - vps)
        out["gap_vs_valuation_pct"] = R2(_safe_div(proposed_price - vps, vps) * 100) if vps else None
    return out


def readiness_summary(checklist_items):
    """Completion % by area; eligibility rows additionally judged met/not-met wherever both threshold and actual are numeric."""
    by_area = {}
    for c in checklist_items or []:
        area = c.get("area", "Readiness")
        g = by_area.setdefault(area, {"total": 0, "done": 0, "not_met": 0})
        g["total"] += 1
        if c.get("status") == "done":
            g["done"] += 1
        if c.get("status") == "not_met":
            g["not_met"] += 1

    eligibility_rows = []
    for c in checklist_items or []:
        if c.get("area") != "Eligibility":
            continue
        threshold, actual = c.get("threshold_value"), c.get("actual_value")
        met = None
        try:
            if threshold not in (None, "") and actual not in (None, ""):
                met = float(actual) >= float(threshold)
        except (TypeError, ValueError):
            met = None
        eligibility_rows.append({"id": c.get("id"), "criterion": c.get("criterion", ""),
                                 "threshold_value": threshold, "actual_value": actual, "met": met})

    areas = {}
    for area, g in by_area.items():
        areas[area] = {"total": g["total"], "done": g["done"], "not_met": g["not_met"],
                       "completion_pct": R2(_safe_div(g["done"], g["total"]) * 100)}
    eligibility_not_met = [r for r in eligibility_rows if r["met"] is False]
    eligibility_unconfirmed = [r for r in eligibility_rows if r["met"] is None]
    return {"areas": areas, "eligibility": eligibility_rows,
           "eligibility_not_met_count": len(eligibility_not_met),
           "eligibility_unconfirmed_count": len(eligibility_unconfirmed)}
