"""
NutriHub Monte Carlo Profit Simulation
======================================
Reproducible Python version of the portfolio model.

- 5,000 trials per product line
- Demand ~ Normal(μ, σ) truncated at 0
- Variable cost ~ Uniform(low, high)
- Package price ~ discrete distribution
- All figures in EGP

Requirements: numpy, scipy, pandas (optional for tables)
"""

import numpy as np
from scipy.stats import norm
from dataclasses import dataclass
from typing import List, Tuple

# ----------------------------------------------------------
# Configuration
# ----------------------------------------------------------
N_TRIALS = 5000
RNG_SEED = 42
CURRENCY = "EGP"

@dataclass
class ProductConfig:
    name: str
    fixed_cost: float
    var_cost_low: float
    var_cost_high: float
    demand_mean: float
    demand_std: float
    royalty_rate: float
    # list of (cum_prob_upper, price)
    price_bands: List[Tuple[float, float]]


PRODUCTS = [
    ProductConfig(
        name="Subscriptions",
        fixed_cost=1375,
        var_cost_low=200,
        var_cost_high=5000,
        demand_mean=100,
        demand_std=35,
        royalty_rate=0.037,
        price_bands=[(0.70, 14300), (0.90, 15900), (1.00, 17300)],
    ),
    ProductConfig(
        name="Short-term Meals",
        fixed_cost=1375,
        var_cost_low=200,
        var_cost_high=5000,
        demand_mean=50,
        demand_std=18,
        royalty_rate=0.02,
        price_bands=[(0.70, 3940), (0.90, 8400), (1.00, 14300)],
    ),
    ProductConfig(
        name="Nutritions",
        fixed_cost=1375,
        var_cost_low=10,
        var_cost_high=300,
        demand_mean=50,
        demand_std=30,
        royalty_rate=0.06,
        price_bands=[
            (0.11, 4423), (0.17, 552), (0.27, 394), (0.29, 2733),
            (0.32, 3444), (0.40, 2435), (0.47, 3622), (0.52, 2363),
            (0.58, 3977), (0.69, 4204), (0.72, 1563), (0.84, 3973),
            (0.85, 1219), (0.97, 3650), (1.00, 1646),
        ],
    ),
]


def sample_price(u: float, bands: List[Tuple[float, float]]) -> float:
    for upper, price in bands:
        if u <= upper:
            return price
    return bands[-1][1]


def run_simulation(cfg: ProductConfig, n_trials: int = N_TRIALS, seed: int = RNG_SEED):
    rng = np.random.default_rng(seed)
    u_cost = rng.uniform(0, 1, n_trials)
    u_price = rng.uniform(0, 1, n_trials)
    u_demand = rng.uniform(0, 1, n_trials)
    u_pre = rng.uniform(0.10, 0.50, n_trials)  # pre-website haircut

    var_cost = cfg.var_cost_low + u_cost * (cfg.var_cost_high - cfg.var_cost_low)
    demand = np.maximum(0.0, norm.ppf(u_demand, loc=cfg.demand_mean, scale=cfg.demand_std))
    price = np.array([sample_price(u, cfg.price_bands) for u in u_price])

    profit_post = (price - var_cost) * demand - cfg.fixed_cost
    royalties = profit_post * cfg.royalty_rate
    profit_pre = profit_post * (1 - u_pre)

    return {
        "profit_post": profit_post,
        "royalties": royalties,
        "profit_pre": profit_pre,
        "demand": demand,
        "var_cost": var_cost,
        "price": price,
    }


def summarise(profits: np.ndarray) -> dict:
    return {
        "mean": float(np.mean(profits)),
        "median": float(np.median(profits)),
        "std": float(np.std(profits, ddof=1)),
        "p05": float(np.percentile(profits, 5)),
        "p25": float(np.percentile(profits, 25)),
        "p75": float(np.percentile(profits, 75)),
        "p95": float(np.percentile(profits, 95)),
        "min": float(np.min(profits)),
        "max": float(np.max(profits)),
        "p_loss": float(np.mean(profits < 0)),
        "annual_mean": float(np.mean(profits) * 12),
        "five_year_mean": float(np.mean(profits) * 12 * 5),
    }


def print_summary(name: str, stats: dict, roy_mean: float):
    print(f"\n{'='*60}")
    print(f"  {name}")
    print(f"{'='*60}")
    print(f"  Mean monthly profit (post) : {CURRENCY} {stats['mean']:,.0f}")
    print(f"  Median monthly profit      : {CURRENCY} {stats['median']:,.0f}")
    print(f"  Std Dev                    : {CURRENCY} {stats['std']:,.0f}")
    print(f"  5th percentile (downside)  : {CURRENCY} {stats['p05']:,.0f}")
    print(f"  95th percentile (upside)   : {CURRENCY} {stats['p95']:,.0f}")
    print(f"  P(Profit < 0)              : {stats['p_loss']:.1%}")
    print(f"  Mean annual profit         : {CURRENCY} {stats['annual_mean']:,.0f}")
    print(f"  Expected 5-year profit     : {CURRENCY} {stats['five_year_mean']:,.0f}")
    print(f"  Mean monthly royalties     : {CURRENCY} {roy_mean:,.0f}")


def main():
    print("NutriHub Monte Carlo Simulation")
    print(f"Trials per product : {N_TRIALS}")
    print(f"Random seed        : {RNG_SEED}")
    print(f"Currency           : {CURRENCY}")

    all_stats = {}
    combined_profit = None

    for cfg in PRODUCTS:
        res = run_simulation(cfg)
        stats = summarise(res["profit_post"])
        roy_mean = float(np.mean(res["royalties"]))
        print_summary(cfg.name, stats, roy_mean)
        all_stats[cfg.name] = stats

        if combined_profit is None:
            combined_profit = res["profit_post"].copy()
        else:
            combined_profit += res["profit_post"]

    # Combined
    comb_stats = summarise(combined_profit)
    print_summary("COMBINED (all three lines)", comb_stats, 0.0)

    print("\n" + "="*60)
    print("KEY INSIGHT")
    print("="*60)
    print("Subscriptions dominates both return and risk.")
    print("Short-term Meals carries material downside (high P(loss)).")
    print("Prioritise Subscriptions; review cost/pricing of Short-term Meals.")
    print("="*60)


if __name__ == "__main__":
    main()
