"""Cash flow projection from product data, demand history, and inventory state.

Composes product prices, demand signals, and reorder costs into daily cash
projections. Tracks when reorders trigger (stock ≤ reorder_point), applies
lead time delays, and computes cumulative revenue, COGS, and net cash.

No library backend needed — pure arithmetic over existing data structures.

Usage via provider:
    from platoon.learning.providers import get_provider
    cf = get_provider("cashflow")
    result = cf.project(products, demand_by_product, inventory_state, horizon=30)

Direct usage:
    from platoon.learning.cashflow import project_cashflow
    result = project_cashflow(products, demand_by_product, inventory_state)
"""

from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional

from platoon.learning.providers import ModelProvider, register_provider


# ---------------------------------------------------------------------------
# Result types
# ---------------------------------------------------------------------------


@dataclass
class CashFlowPeriod:
    """Cash flow for a single day."""

    date: str
    revenue: float
    cogs: float  # cost of goods sold
    gross_profit: float
    reorder_costs: float  # cash out for reorders placed this day
    net_cash: float  # gross_profit - reorder_costs


@dataclass
class CashFlowResult:
    """Output of cash flow projection."""

    periods: List[CashFlowPeriod]
    summary: Dict[str, float]  # total_revenue, total_cogs, total_reorder, net_cash
    reorder_events: List[Dict[str, Any]]  # [{day, product_id, quantity, cost}]
    horizon_days: int


# ---------------------------------------------------------------------------
# Pure-math implementation
# ---------------------------------------------------------------------------


def project_cashflow(
    products: List[Dict[str, Any]],
    demand_by_product: Dict[str, List[float]],
    inventory_state: Optional[Dict[str, Dict[str, float]]] = None,
    horizon: int = 30,
    start_date: Optional[str] = None,
) -> CashFlowResult:
    """Project daily cash flow over a horizon.

    Args:
        products: List of product dicts with keys: product_id, price, cost,
            lead_time_days. Optionally: eoq, reorder_point, safety_stock.
        demand_by_product: {product_id: [daily_demand_values]}. Length should
            be >= horizon. If shorter, last value is repeated.
        inventory_state: Optional {product_id: {stock, reorder_point, eoq}}.
            If None, reorder simulation is skipped.
        horizon: Days to project.
        start_date: ISO date string for day 0 (default: today).

    Returns:
        CashFlowResult with daily periods, summary, and reorder events.
    """
    if start_date:
        base_date = datetime.strptime(start_date, "%Y-%m-%d")
    else:
        base_date = datetime.now()

    # Build product lookup
    product_lookup = {p["product_id"]: p for p in products}

    # Initialize per-product stock tracking
    stock: Dict[str, float] = {}
    pending_orders: Dict[str, List[tuple]] = {}  # product_id -> [(delivery_day, qty)]
    reorder_points: Dict[str, float] = {}
    eoqs: Dict[str, float] = {}

    if inventory_state:
        for pid, state in inventory_state.items():
            stock[pid] = state.get("stock", 0)
            reorder_points[pid] = state.get("reorder_point", 0)
            eoqs[pid] = state.get("eoq", 0)
            pending_orders[pid] = []

    periods: List[CashFlowPeriod] = []
    reorder_events: List[Dict[str, Any]] = []
    total_revenue = 0.0
    total_cogs = 0.0
    total_reorder = 0.0

    for day in range(horizon):
        date_str = (base_date + timedelta(days=day)).strftime("%Y-%m-%d")
        day_revenue = 0.0
        day_cogs = 0.0
        day_reorder = 0.0

        for pid, demand_series in demand_by_product.items():
            p = product_lookup.get(pid)
            if not p:
                continue

            price = float(p.get("price", 0))
            cost = float(p.get("cost", 0))
            lead_time = int(p.get("lead_time_days", 7))

            # Get demand for this day
            idx = min(day, len(demand_series) - 1)
            demand = demand_series[idx] if demand_series else 0

            # Revenue and COGS from sales
            if inventory_state and pid in stock:
                # Receive pending deliveries
                new_pending = []
                for (delivery_day, qty) in pending_orders.get(pid, []):
                    if day >= delivery_day:
                        stock[pid] += qty
                    else:
                        new_pending.append((delivery_day, qty))
                pending_orders[pid] = new_pending

                # Sell from stock (can't sell more than available)
                actual_sold = min(demand, stock[pid])
                stock[pid] -= actual_sold

                # Reorder check
                rop = reorder_points.get(pid, 0)
                eoq = eoqs.get(pid, 0)
                if stock[pid] <= rop and eoq > 0:
                    # Check no pending orders
                    if not pending_orders[pid]:
                        order_cost = eoq * cost
                        pending_orders[pid].append((day + lead_time, eoq))
                        day_reorder += order_cost
                        reorder_events.append({
                            "day": day,
                            "date": date_str,
                            "product_id": pid,
                            "quantity": eoq,
                            "cost": round(order_cost, 2),
                        })
            else:
                # No inventory tracking — assume all demand is fulfilled
                actual_sold = demand

            day_revenue += actual_sold * price
            day_cogs += actual_sold * cost

        gross_profit = day_revenue - day_cogs
        net_cash = gross_profit - day_reorder

        total_revenue += day_revenue
        total_cogs += day_cogs
        total_reorder += day_reorder

        periods.append(CashFlowPeriod(
            date=date_str,
            revenue=round(day_revenue, 2),
            cogs=round(day_cogs, 2),
            gross_profit=round(gross_profit, 2),
            reorder_costs=round(day_reorder, 2),
            net_cash=round(net_cash, 2),
        ))

    return CashFlowResult(
        periods=periods,
        summary={
            "total_revenue": round(total_revenue, 2),
            "total_cogs": round(total_cogs, 2),
            "total_gross_profit": round(total_revenue - total_cogs, 2),
            "total_reorder_costs": round(total_reorder, 2),
            "net_cash": round(total_revenue - total_cogs - total_reorder, 2),
            "avg_daily_revenue": round(total_revenue / horizon, 2) if horizon else 0,
            "avg_daily_profit": round((total_revenue - total_cogs) / horizon, 2) if horizon else 0,
        },
        reorder_events=reorder_events,
        horizon_days=horizon,
    )


# ---------------------------------------------------------------------------
# Built-in provider
# ---------------------------------------------------------------------------


@register_provider
class BuiltinCashFlowProvider(ModelProvider):
    """Pure-math cash flow projection — no external dependencies."""

    name = "builtin_cashflow"
    domain = "cashflow"
    backend = "builtin"

    def project(
        self,
        products: List[Dict[str, Any]],
        demand_by_product: Dict[str, List[float]],
        inventory_state: Optional[Dict[str, Dict[str, float]]] = None,
        horizon: int = 30,
        start_date: Optional[str] = None,
    ) -> CashFlowResult:
        """Project daily cash flow over a horizon."""
        return project_cashflow(
            products, demand_by_product, inventory_state,
            horizon=horizon, start_date=start_date,
        )
