#!/usr/bin/env python3
"""
referral_roi_calculator.py — Calculates referral program ROI.

Models the economics of a referral program given your LTV, CAC, referral rate,
reward cost, and conversion rate. Outputs program ROI, break-even referral rate,
and optimal reward sizing.

Usage:
    python3 referral_roi_calculator.py                    # runs embedded sample
    python3 referral_roi_calculator.py params.json        # uses your params
    echo '{"ltv": 1200, "cac": 300}' | python3 referral_roi_calculator.py

JSON input format:
    {
        "ltv": 1200,               # Customer Lifetime Value ($)
        "cac": 300,                # Current avg CAC via paid channels ($)
        "active_users": 500,       # Active users who could refer
        "referral_rate": 0.05,     # % of active users who refer each month (0.05 = 5%)
        "referrals_per_referrer": 2.5,  # Avg referrals sent per active referrer
        "referral_conversion_rate": 0.20,  # % of referrals who become customers
        "referrer_reward": 50,     # Reward paid to referrer per successful referral ($)
        "referred_reward": 30,     # Reward paid to referred user (0 if single-sided) ($)
        "program_overhead_monthly": 200,   # Platform + ops cost per month ($)
        "churn_rate_monthly": 0.03,        # Monthly churn rate (used for LTV validation)
        "months_to_model": 12              # How many months to project
    }
"""

import json
import sys
from collections import OrderedDict


# ---------------------------------------------------------------------------
# Core calculation functions
# ---------------------------------------------------------------------------

def calculate_referrals_per_month(params):
    """How many successful referrals per month?"""
    active_users = params["active_users"]
    referral_rate = params["referral_rate"]
    referrals_per_referrer = params["referrals_per_referrer"]
    conversion_rate = params["referral_conversion_rate"]

    active_referrers = active_users * referral_rate
    referrals_sent = active_referrers * referrals_per_referrer
    conversions = referrals_sent * conversion_rate

    return {
        "active_referrers": round(active_referrers, 1),
        "referrals_sent": round(referrals_sent, 1),
        "new_customers_per_month": round(conversions, 1),
    }


def calculate_monthly_program_cost(params, new_customers_per_month):
    """Total cost of running the program for one month."""
    reward_per_conversion = params["referrer_reward"] + params["referred_reward"]
    reward_cost = reward_per_conversion * new_customers_per_month
    overhead = params["program_overhead_monthly"]
    return {
        "reward_cost": round(reward_cost, 2),
        "overhead_cost": round(overhead, 2),
        "total_cost": round(reward_cost + overhead, 2),
        "reward_per_conversion": round(reward_per_conversion, 2),
    }


def calculate_monthly_revenue(params, new_customers_per_month):
    """Revenue generated from referred customers in the first month."""
    # First-month value is LTV / (1 / monthly_churn) = LTV * monthly_churn
    # Simplified: use LTV * monthly_churn as first-month expected revenue contribution
    # More conservative: just count as one acquisition with full LTV expected
    ltv = params["ltv"]
    revenue = new_customers_per_month * ltv
    return round(revenue, 2)


def calculate_cac_via_referral(cost_data, new_customers_per_month):
    if new_customers_per_month == 0:
        return float('inf')
    return round(cost_data["total_cost"] / new_customers_per_month, 2)


def calculate_break_even_referral_rate(params):
    """
    What referral rate do we need so that CAC via referral equals
    reward_per_conversion + overhead_per_customer_amortized?
    
    We want: total_cost / new_customers = cac_target
    Solving for referral_rate where cac_target = 50% of paid CAC (our target)
    """
    target_cac = params["cac"] * 0.5  # goal: 50% of current CAC
    ltv = params["ltv"]
    active_users = params["active_users"]
    referrals_per_referrer = params["referrals_per_referrer"]
    conversion_rate = params["referral_conversion_rate"]
    reward_per_conversion = params["referrer_reward"] + params["referred_reward"]
    overhead = params["program_overhead_monthly"]

    # CAC_referral = (reward × conversions + overhead) / conversions
    #              = reward + overhead/conversions
    # Solve: target_cac = reward + overhead / (active_users × rate × referrals_per_referrer × conversion_rate)
    # conversions_needed = overhead / (target_cac - reward)

    if target_cac <= reward_per_conversion:
        return None  # impossible — reward alone exceeds target CAC

    conversions_needed = overhead / (target_cac - reward_per_conversion)
    referral_rate_needed = conversions_needed / (active_users * referrals_per_referrer * conversion_rate)

    return round(referral_rate_needed, 4)


def calculate_optimal_reward(params):
    """
    What's the maximum reward you can afford while keeping CAC via referral
    under 60% of paid CAC?
    
    max_total_reward = 0.60 × paid_CAC (using conversion-amortized overhead)
    """
    target_cac = params["cac"] * 0.60
    overhead_amortized = params["program_overhead_monthly"] / max(
        calculate_referrals_per_month(params)["new_customers_per_month"], 1
    )
    max_reward = target_cac - overhead_amortized

    # Split recommendation: 60% referrer, 40% referred (double-sided)
    referrer_portion = round(max_reward * 0.60, 2)
    referred_portion = round(max_reward * 0.40, 2)

    return {
        "max_total_reward": round(max(max_reward, 0), 2),
        "recommended_referrer_reward": max(referrer_portion, 0),
        "recommended_referred_reward": max(referred_portion, 0),
        "reward_as_pct_ltv": round((max_reward / params["ltv"]) * 100, 1) if params["ltv"] > 0 else 0,
    }


def calculate_roi(params):
    """
    Program ROI over the modeling period.
    ROI = (Revenue from referred customers - Program costs) / Program costs
    """
    months = params["months_to_model"]
    monthly = calculate_referrals_per_month(params)
    new_customers = monthly["new_customers_per_month"]
    costs = calculate_monthly_program_cost(params, new_customers)

    total_cost = costs["total_cost"] * months
    total_ltv_generated = new_customers * params["ltv"] * months
    net_benefit = total_ltv_generated - total_cost
    roi = (net_benefit / total_cost * 100) if total_cost > 0 else 0

    return {
        "total_cost": round(total_cost, 2),
        "total_ltv_generated": round(total_ltv_generated, 2),
        "net_benefit": round(net_benefit, 2),
        "roi_pct": round(roi, 1),
    }


def build_monthly_projection(params):
    """Build a month-by-month projection table."""
    months = params["months_to_model"]
    monthly = calculate_referrals_per_month(params)
    new_per_month = monthly["new_customers_per_month"]
    costs = calculate_monthly_program_cost(params, new_per_month)
    ltv = params["ltv"]

    rows = []
    cumulative_customers = 0
    cumulative_cost = 0
    cumulative_revenue = 0

    for m in range(1, months + 1):
        cumulative_customers += new_per_month
        month_cost = costs["total_cost"]
        month_revenue = new_per_month * ltv
        cumulative_cost += month_cost
        cumulative_revenue += month_revenue
        cumulative_net = cumulative_revenue - cumulative_cost

        rows.append({
            "month": m,
            "new_customers": round(new_per_month, 1),
            "cumulative_customers": round(cumulative_customers, 1),
            "monthly_cost": round(month_cost, 2),
            "cumulative_cost": round(cumulative_cost, 2),
            "monthly_ltv": round(month_revenue, 2),
            "cumulative_net": round(cumulative_net, 2),
        })

    return rows


def find_break_even_month(projection):
    for row in projection:
        if row["cumulative_net"] >= 0:
            return row["month"]
    return None


# ---------------------------------------------------------------------------
# Formatting
# ---------------------------------------------------------------------------

def format_currency(value):
    return f"${value:,.2f}"


def format_pct(value):
    return f"{value:.1f}%"


def print_report(params, results):
    monthly = results["monthly_referrals"]
    costs = results["monthly_costs"]
    cac = results["cac_via_referral"]
    roi = results["roi"]
    break_even_rate = results["break_even_referral_rate"]
    optimal_reward = results["optimal_reward"]
    projection = results["monthly_projection"]
    break_even_month = results["break_even_month"]

    paid_cac = params["cac"]
    ltv = params["ltv"]

    print("\n" + "=" * 60)
    print("REFERRAL PROGRAM ROI CALCULATOR")
    print("=" * 60)

    print("\n📊 INPUT PARAMETERS")
    print(f"  LTV per customer:           {format_currency(ltv)}")
    print(f"  Current paid CAC:           {format_currency(paid_cac)}")
    print(f"  Active users:               {params['active_users']:,}")
    print(f"  Referral rate (monthly):    {format_pct(params['referral_rate'] * 100)}")
    print(f"  Referrals per referrer:     {params['referrals_per_referrer']}")
    print(f"  Referral conversion rate:   {format_pct(params['referral_conversion_rate'] * 100)}")
    print(f"  Referrer reward:            {format_currency(params['referrer_reward'])}")
    print(f"  Referred user reward:       {format_currency(params['referred_reward'])}")
    print(f"  Program overhead/month:     {format_currency(params['program_overhead_monthly'])}")

    print("\n📈 MONTHLY PERFORMANCE (STEADY STATE)")
    print(f"  Active referrers/month:     {monthly['active_referrers']}")
    print(f"  Referrals sent/month:       {monthly['referrals_sent']}")
    print(f"  New customers/month:        {monthly['new_customers_per_month']}")
    print(f"  Monthly program cost:       {format_currency(costs['total_cost'])}")
    print(f"    ↳ Reward cost:            {format_currency(costs['reward_cost'])}")
    print(f"    ↳ Overhead:               {format_currency(costs['overhead_cost'])}")
    print(f"  CAC via referral:           {format_currency(cac)}")
    print(f"  Paid CAC:                   {format_currency(paid_cac)}")
    savings_pct = ((paid_cac - cac) / paid_cac * 100) if paid_cac > 0 else 0
    savings_label = f"{savings_pct:.0f}% cheaper than paid" if cac < paid_cac else "⚠️  More expensive than paid"
    print(f"  CAC comparison:             {savings_label}")

    print(f"\n💰 ROI OVER {params['months_to_model']} MONTHS")
    print(f"  Total program cost:         {format_currency(roi['total_cost'])}")
    print(f"  Total LTV generated:        {format_currency(roi['total_ltv_generated'])}")
    print(f"  Net benefit:                {format_currency(roi['net_benefit'])}")
    print(f"  Program ROI:                {format_pct(roi['roi_pct'])}")

    if break_even_month:
        print(f"  Break-even:                 Month {break_even_month}")
    else:
        print(f"  Break-even:                 Not reached in {params['months_to_model']} months")

    print("\n🎯 OPTIMIZATION INSIGHTS")
    if break_even_rate:
        current_rate = params["referral_rate"]
        rate_gap = break_even_rate - current_rate
        if rate_gap > 0:
            print(f"  Break-even referral rate:   {format_pct(break_even_rate * 100)} "
                  f"(you're at {format_pct(current_rate * 100)} — need +{format_pct(rate_gap * 100)})")
        else:
            print(f"  Break-even referral rate:   {format_pct(break_even_rate * 100)} ✅ Already above break-even")
    else:
        print(f"  Break-even referral rate:   ⚠️  Reward alone exceeds target CAC — reduce reward or increase LTV")

    print(f"\n  Optimal reward sizing (to keep CAC at ≤60% of paid CAC):")
    print(f"    Max total reward/referral:  {format_currency(optimal_reward['max_total_reward'])}")
    print(f"    Recommended referrer:       {format_currency(optimal_reward['recommended_referrer_reward'])}")
    print(f"    Recommended referred user:  {format_currency(optimal_reward['recommended_referred_reward'])}")
    print(f"    Reward as % of LTV:         {format_pct(optimal_reward['reward_as_pct_ltv'])}")

    current_total_reward = params["referrer_reward"] + params["referred_reward"]
    if current_total_reward > optimal_reward["max_total_reward"] and optimal_reward["max_total_reward"] > 0:
        print(f"  ⚠️  Your current reward ({format_currency(current_total_reward)}) "
              f"exceeds optimal ({format_currency(optimal_reward['max_total_reward'])})")
    elif optimal_reward["max_total_reward"] > 0:
        print(f"  ✅ Your current reward ({format_currency(current_total_reward)}) is within optimal range")

    print(f"\n📅 MONTHLY PROJECTION (first {min(6, len(projection))} months)")
    print(f"  {'Month':>5}  {'New Cust':>9}  {'Cumul Cust':>11}  {'Monthly Cost':>13}  {'Cumul Net':>11}")
    print(f"  {'-'*5}  {'-'*9}  {'-'*11}  {'-'*13}  {'-'*11}")
    for row in projection[:6]:
        net_str = format_currency(row["cumulative_net"])
        if row["cumulative_net"] < 0:
            net_str = f"({format_currency(abs(row['cumulative_net']))})"
        print(f"  {row['month']:>5}  {row['new_customers']:>9.1f}  {row['cumulative_customers']:>11.1f}  "
              f"{format_currency(row['monthly_cost']):>13}  {net_str:>11}")

    print("\n" + "=" * 60)


# ---------------------------------------------------------------------------
# Default parameters + sample
# ---------------------------------------------------------------------------

DEFAULT_PARAMS = {
    "ltv": 1200,
    "cac": 350,
    "active_users": 800,
    "referral_rate": 0.06,
    "referrals_per_referrer": 2.0,
    "referral_conversion_rate": 0.20,
    "referrer_reward": 50,
    "referred_reward": 30,
    "program_overhead_monthly": 200,
    "churn_rate_monthly": 0.04,
    "months_to_model": 12,
}


def run(params):
    monthly = calculate_referrals_per_month(params)
    new_customers = monthly["new_customers_per_month"]
    costs = calculate_monthly_program_cost(params, new_customers)
    cac = calculate_cac_via_referral(costs, new_customers)
    break_even_rate = calculate_break_even_referral_rate(params)
    optimal_reward = calculate_optimal_reward(params)
    roi = calculate_roi(params)
    projection = build_monthly_projection(params)
    break_even_month = find_break_even_month(projection)

    results = {
        "monthly_referrals": monthly,
        "monthly_costs": costs,
        "cac_via_referral": cac,
        "break_even_referral_rate": break_even_rate,
        "optimal_reward": optimal_reward,
        "roi": roi,
        "monthly_projection": projection,
        "break_even_month": break_even_month,
    }

    return results


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

def main():
    import argparse

    parser = argparse.ArgumentParser(
        description="Calculates referral program ROI. "
                    "Models economics given LTV, CAC, referral rate, reward cost, "
                    "and conversion rate."
    )
    parser.add_argument(
        "file", nargs="?", default=None,
        help="Path to a JSON file with referral program parameters. "
             "If omitted, reads from stdin or runs embedded sample."
    )
    args = parser.parse_args()

    params = None

    if args.file:
        try:
            with open(args.file) as f:
                params = json.load(f)
        except Exception as e:
            print(f"Error reading file: {e}", file=sys.stderr)
            sys.exit(1)
    elif not sys.stdin.isatty():
        raw = sys.stdin.read().strip()
        if raw:
            try:
                params = json.loads(raw)
            except Exception as e:
                print(f"Error reading stdin: {e}", file=sys.stderr)
                sys.exit(1)
        else:
            print("No input provided — running with sample parameters.\n")
            params = DEFAULT_PARAMS
    else:
        print("No input provided — running with sample parameters.\n")
        params = DEFAULT_PARAMS

    # Fill in defaults for any missing keys
    for k, v in DEFAULT_PARAMS.items():
        params.setdefault(k, v)

    results = run(params)
    print_report(params, results)

    # JSON output
    json_output = {
        "inputs": params,
        "results": {
            "monthly_new_customers": results["monthly_referrals"]["new_customers_per_month"],
            "cac_via_referral": results["cac_via_referral"],
            "program_roi_pct": results["roi"]["roi_pct"],
            "break_even_month": results["break_even_month"],
            "break_even_referral_rate": results["break_even_referral_rate"],
            "optimal_total_reward": results["optimal_reward"]["max_total_reward"],
            "net_benefit_12mo": results["roi"]["net_benefit"],
        }
    }

    print("\n--- JSON Output ---")
    print(json.dumps(json_output, indent=2))


if __name__ == "__main__":
    main()
