"""
Business case — combines costs, benefits, and cost takeouts into a
3-year net view with NPV, payback, and ROI.

Convention: all cashflows are year-end and discounted at
``discount_rate`` (default 8%); there is no undiscounted year-0 column
— implementation is amortized straight-line across the analysis years
(spec §5.6 "Implementation amort." line).
"""

from __future__ import annotations

import pandas as pd

from .benefit_model import calculate_total_benefit
from .cost_model import calculate_total_cost
from .defaults import (
    DEFAULT_DISCOUNT_RATE,
    DEFAULT_IMPLEMENTATION_COST,
    PLATFORM_RATE_PER_USER_MONTHLY,
)
from .inputs import CostTakeout, FeatureScope, SiteInput
from .meters import Confidence, TokenMeter, TokenPricing
from .rollout import RolloutPlan
from .scenarios import Scenario, get_scenario


def npv(cashflows_by_year: list[float], discount_rate: float) -> float:
    """Year-end-discounted NPV of year-1..N cashflows."""
    return sum(
        cf / (1 + discount_rate) ** year
        for year, cf in enumerate(cashflows_by_year, start=1)
    )


def payback_years(cashflows_by_year: list[float]) -> float | None:
    """First (fractional) year cumulative net turns >= 0; None if never.

    Cashflows are assumed evenly spread within each year.
    """
    cumulative = 0.0
    for year, cf in enumerate(cashflows_by_year, start=1):
        if cumulative + cf >= 0 and cf != 0:
            if cumulative >= 0:
                return float(year - 1)
            return (year - 1) + (-cumulative / cf)
        cumulative += cf
    return None


def build_business_case(
    sites: list[SiteInput],
    feature_scopes: list[FeatureScope],
    meters: dict[str, TokenMeter],
    pricing: dict[str, TokenPricing],
    takeouts: list[CostTakeout],
    scenario: str | Scenario,
    years: int = 3,
    discount_rate: float = DEFAULT_DISCOUNT_RATE,
    platform_rate: float = PLATFORM_RATE_PER_USER_MONTHLY,
    implementation_cost: float = DEFAULT_IMPLEMENTATION_COST,
    use_contracted: bool = False,
    benefit_params: str = "realistic",
    rollout: RolloutPlan | None = None,
) -> dict:
    """Returns the dict described in spec §4.3 (DataFrames + headline
    metrics). Every number traces to a cost line, benefit line, or
    takeout row in the per-year detail frames.
    """
    sc = get_scenario(scenario) if isinstance(scenario, str) else scenario
    year_cols = [f"Y{y}" for y in range(1, years + 1)]

    cost_frames, benefit_frames = {}, {}
    for y in range(1, years + 1):
        cost_frames[y] = calculate_total_cost(
            sites, feature_scopes, meters, pricing, sc, y,
            platform_rate=platform_rate, use_contracted=use_contracted,
            rollout=rollout,
        )
        benefit_frames[y] = calculate_total_benefit(
            sites, feature_scopes, sc, y, params=benefit_params,
            rollout=rollout,
        )

    # ── cost_by_year: one row per cost line, one column per year ────
    cost_lines = list(cost_frames[1]["cost_line"])
    cost_by_year = pd.DataFrame({"line": cost_lines})
    for y in range(1, years + 1):
        cost_by_year[f"Y{y}"] = list(cost_frames[y]["annual_cost"])
    cost_by_year["confidence"] = list(cost_frames[1]["confidence"])
    if implementation_cost:
        amort = implementation_cost / years
        cost_by_year = pd.concat(
            [
                cost_by_year,
                pd.DataFrame(
                    [
                        {
                            "line": "Implementation (amortized)",
                            **{c: amort for c in year_cols},
                            "confidence": Confidence.ESTIMATED.value,
                        }
                    ]
                ),
            ],
            ignore_index=True,
        )

    # ── benefit_by_year ──────────────────────────────────────────────
    benefit_lines: list[str] = []
    for y in range(1, years + 1):
        for line in benefit_frames[y]["benefit_line"]:
            if line not in benefit_lines:
                benefit_lines.append(line)
    benefit_by_year = pd.DataFrame({"line": benefit_lines})
    for y in range(1, years + 1):
        lookup = dict(
            zip(benefit_frames[y]["benefit_line"], benefit_frames[y]["annual_value"])
        )
        benefit_by_year[f"Y{y}"] = [lookup.get(line, 0.0) for line in benefit_lines]
    conf_lookup: dict[str, str] = {}
    for y in range(1, years + 1):
        conf_lookup.update(
            dict(zip(benefit_frames[y]["benefit_line"], benefit_frames[y]["confidence"]))
        )
    benefit_by_year["confidence"] = [
        conf_lookup.get(line, Confidence.ESTIMATED.value) for line in benefit_lines
    ]

    # ── takeouts_by_year ─────────────────────────────────────────────
    takeouts_by_year = pd.DataFrame(
        [
            {
                "line": t.name,
                **{f"Y{y}": t.value_in_year(y) for y in range(1, years + 1)},
                "confidence": t.confidence.value,
            }
            for t in takeouts
        ]
    )

    # ── net + cumulative ─────────────────────────────────────────────
    total_costs = [float(cost_by_year[c].sum()) for c in year_cols]
    total_benefits = [float(benefit_by_year[c].sum()) for c in year_cols]
    total_takeouts = [
        float(takeouts_by_year[c].sum()) if not takeouts_by_year.empty else 0.0
        for c in year_cols
    ]
    net = [
        b + t - c for b, t, c in zip(total_benefits, total_takeouts, total_costs)
    ]
    cumulative = pd.Series(net).cumsum().tolist()

    net_by_year = pd.DataFrame(
        {
            "line": [
                "TOTAL COSTS", "TOTAL TAKEOUTS", "TOTAL BENEFITS",
                "NET", "Cumulative net",
            ],
            **{
                f"Y{y}": [
                    total_costs[y - 1], total_takeouts[y - 1],
                    total_benefits[y - 1], net[y - 1], cumulative[y - 1],
                ]
                for y in range(1, years + 1)
            },
        }
    )
    cumulative_net = pd.DataFrame(
        {"year": list(range(1, years + 1)), "cumulative_net": cumulative}
    )

    total_cost_sum = sum(total_costs)
    total_value_sum = sum(total_benefits) + sum(total_takeouts)
    return {
        "cost_by_year": cost_by_year,
        "benefit_by_year": benefit_by_year,
        "takeouts_by_year": takeouts_by_year,
        "net_by_year": net_by_year,
        "cumulative_net": cumulative_net,
        "npv": npv(net, discount_rate),
        "payback_period_years": payback_years(net),
        "roi_3yr": (
            (total_value_sum - total_cost_sum) / total_cost_sum
            if total_cost_sum
            else None
        ),
    }
