"""
Plotly charts for TEI analyses.

Each function returns a ``plotly.graph_objects.Figure`` so callers can
``.show()`` (notebook), pass to ``st.plotly_chart`` (Streamlit), or write to
HTML / image. No styling is hard-coded beyond a neutral default palette.
"""

from __future__ import annotations

from collections.abc import Iterable

import plotly.graph_objects as go

PALETTE = {
    "benefits": "#2E7D32",  # green
    "costs": "#C62828",  # red
    "net_positive": "#1565C0",  # blue
    "net_negative": "#C62828",
    "cumulative": "#616161",  # grey
}

#: Visual theme — override per study/client with :func:`apply_theme`.
#: Hex colours; fonts are CSS font-family strings.
THEME = {
    "heading_font": "Helvetica Neue, Arial, sans-serif",
    "body_font": "Helvetica, Arial, sans-serif",
    "font_color": "#1F2937",
    # Circle-chart slice colours a–j, used in order.
    "pie_colors": [
        "#1565C0",  # a
        "#2E7D32",  # b
        "#C62828",  # c
        "#F9A825",  # d
        "#6A1B9A",  # e
        "#00838F",  # f
        "#EF6C00",  # g
        "#5D4037",  # h
        "#37474F",  # i
        "#AD1457",  # j
    ],
    "bar_green": "#2E7D32",
    "bar_red": "#C62828",
}


def apply_theme(**overrides) -> dict:
    """
    Override theme values for all charts in this session.

    Accepts any THEME key. ``pie_colors`` may be a list (used in order) or
    a dict keyed ``"a"``–``"j"`` (sorted alphabetically). Returns the
    active theme. Example::

        from core.notebook_helpers import charts
        charts.apply_theme(
            heading_font="Georgia, serif",
            font_color="#102A43",
            pie_colors={"a": "#1565C0", "b": "#2E7D32"},
            bar_green="#1B5E20",
            bar_red="#B71C1C",
        )
    """
    for key, value in overrides.items():
        if key not in THEME:
            raise KeyError(
                f"Unknown theme key {key!r}. Valid keys: {sorted(THEME)}"
            )
        if key == "pie_colors" and isinstance(value, dict):
            value = [value[k] for k in sorted(value)]
        THEME[key] = value
    return THEME


def _themed(fig: go.Figure) -> go.Figure:
    """Apply theme fonts/colours to a figure's layout."""
    fig.update_layout(
        font={"family": THEME["body_font"], "color": THEME["font_color"]},
        title_font={
            "family": THEME["heading_font"],
            "color": THEME["font_color"],
        },
        legend_font={"family": THEME["body_font"], "color": THEME["font_color"]},
    )
    return fig


def cashflow_chart(
    yearly_breakdown: list[dict],
    *,
    title: str = "Cash Flow Analysis (Risk-Adjusted)",
    initial_cost: float = 0.0,
) -> go.Figure:
    """
    Stacked bars of benefits & costs by year + cumulative net line.

    Mirrors the chart on page 25 of the Forrester Amazon Connect TEI study.
    """
    if not yearly_breakdown:
        return go.Figure(layout={"title": title})

    years = ["Initial"] + [f"Year {row['year']}" for row in yearly_breakdown]
    benefits = [0.0] + [float(row.get("benefits", 0)) for row in yearly_breakdown]
    costs = [-float(initial_cost)] + [
        -float(row.get("costs", 0)) for row in yearly_breakdown
    ]
    # cumulative_net assumes initial cost has already been deducted
    cumulative = [-float(initial_cost)] + [
        float(row.get("cumulative_net", 0)) for row in yearly_breakdown
    ]

    fig = go.Figure()
    fig.add_bar(
        name="Total benefits",
        x=years,
        y=benefits,
        marker_color=THEME["bar_green"],
    )
    fig.add_bar(
        name="Total costs",
        x=years,
        y=costs,
        marker_color=THEME["bar_red"],
    )
    fig.add_scatter(
        name="Cumulative net benefits",
        x=years,
        y=cumulative,
        mode="lines+markers",
        line={"color": PALETTE["cumulative"], "width": 3},
    )
    fig.update_layout(
        title=title,
        barmode="relative",
        yaxis_tickformat="$,.0f",
        legend={"orientation": "h", "y": -0.15},
        margin={"l": 40, "r": 20, "t": 60, "b": 40},
    )
    return _themed(fig)


def benefits_bar(items: list[dict], *, title: str = "Benefits (Three-Year)") -> go.Figure:
    """Horizontal bars of risk-adjusted three-year totals per benefit."""
    labels: list[str] = []
    totals: list[float] = []
    for it in items:
        rf = float(it.get("risk_adjustment") or 0.0)
        yv = it.get("year_values") or {}
        ra_total = sum(float(v or 0) * (1.0 - rf) for v in yv.values())
        labels.append(it.get("label", "") or it.get("field_key", ""))
        totals.append(ra_total)

    fig = go.Figure(
        go.Bar(
            x=totals,
            y=labels,
            orientation="h",
            marker_color=THEME["bar_green"],
            text=[f"${t/1_000_000:,.1f}M" for t in totals],
            textposition="auto",
        )
    )
    fig.update_layout(
        title=title,
        xaxis_tickformat="$,.0f",
        yaxis={"autorange": "reversed"},
        margin={"l": 40, "r": 20, "t": 60, "b": 40},
    )
    return _themed(fig)


def cost_breakdown_pie(
    items: list[dict], *, title: str = "Cost Breakdown (Three-Year, Risk-Adjusted)"
) -> go.Figure:
    """Pie chart of risk-adjusted costs by category/label."""
    labels: list[str] = []
    values: list[float] = []
    for it in items:
        rf = float(it.get("risk_adjustment") or 0.0)
        yv = it.get("year_values") or {}
        initial = float(it.get("initial") or 0.0)
        ra_total = (
            initial * (1.0 + rf)
            + sum(float(v or 0) * (1.0 + rf) for v in yv.values())
        )
        labels.append(it.get("label", "") or it.get("field_key", ""))
        values.append(ra_total)

    fig = go.Figure(go.Pie(labels=labels, values=values, hole=0.35,
                       marker={"colors": THEME["pie_colors"]}))
    fig.update_layout(title=title, margin={"l": 40, "r": 20, "t": 60, "b": 40})
    return _themed(fig)


def benefits_vs_costs_by_year(
    benefit_items: list[dict],
    cost_items: list[dict],
    *,
    title: str = "Benefits vs Costs by Year (Risk-Adjusted)",
) -> go.Figure:
    """
    Grouped bars of risk-adjusted benefits and costs per year, with an
    Initial (Year 0) column for one-time costs.

    Accepts the friendly value rows from ``TEIClient.get_values``:
    benefit values are nominal (field-level risk adjustment applied here);
    cost values are stored already risk-adjusted (Palladium convention),
    with ``initial`` carrying the Year-0 amount.
    """
    years: set[int] = set()
    for it in [*benefit_items, *cost_items]:
        years.update(int(y) for y in (it.get("year_values") or {}))
    year_list = sorted(years) or [1, 2, 3]

    benefits_by_year: dict[int, float] = dict.fromkeys(year_list, 0.0)
    costs_by_year: dict[int, float] = dict.fromkeys(year_list, 0.0)
    initial_total = 0.0

    for it in benefit_items:
        rf = float(it.get("risk_adjustment") or 0.0)
        for y, v in (it.get("year_values") or {}).items():
            benefits_by_year[int(y)] += float(v or 0) * (1.0 - rf)
    for it in cost_items:
        initial_total += float(it.get("initial") or 0.0)
        for y, v in (it.get("year_values") or {}).items():
            costs_by_year[int(y)] += float(v or 0)

    x = ["Initial"] + [f"Year {y}" for y in year_list]
    benefits = [0.0] + [benefits_by_year[y] for y in year_list]
    costs = [initial_total] + [costs_by_year[y] for y in year_list]

    fig = go.Figure()
    fig.add_bar(name="Benefits", x=x, y=benefits, marker_color=THEME["bar_green"],
                text=[f"${v/1_000_000:,.1f}M" if v else "" for v in benefits],
                textposition="outside")
    fig.add_bar(name="Costs", x=x, y=costs, marker_color=THEME["bar_red"],
                text=[f"${v/1_000_000:,.1f}M" if v else "" for v in costs],
                textposition="outside")
    fig.update_layout(
        title=title,
        barmode="group",
        yaxis_tickformat="$,.0f",
        legend={"orientation": "h", "y": -0.15},
        margin={"l": 40, "r": 20, "t": 60, "b": 40},
    )
    return _themed(fig)


def scenario_comparison(scenarios: dict) -> go.Figure:
    """Grouped bars comparing NPV and Costs PV across scenarios."""
    keys: list[str] = list(scenarios.keys())
    if not keys:
        return go.Figure()
    benefits = [float(scenarios[k].get("total_benefits_pv") or 0) for k in keys]
    costs = [float(scenarios[k].get("total_costs_pv") or 0) for k in keys]
    npvs = [float(scenarios[k].get("npv") or 0) for k in keys]

    fig = go.Figure()
    fig.add_bar(name="Benefits PV", x=keys, y=benefits, marker_color=THEME["bar_green"])
    fig.add_bar(name="Costs PV", x=keys, y=costs, marker_color=THEME["bar_red"])
    fig.add_bar(name="NPV", x=keys, y=npvs, marker_color=PALETTE["net_positive"])
    fig.update_layout(
        title="Scenario Comparison",
        barmode="group",
        yaxis_tickformat="$,.0f",
        legend={"orientation": "h", "y": -0.15},
    )
    return _themed(fig)


def cumulative_benefits_chart(
    yearly_breakdown: list[dict],
    *,
    title: str = "Cumulative Net Benefits",
) -> go.Figure:
    """Single-line cumulative net benefits trajectory."""
    if not yearly_breakdown:
        return go.Figure(layout={"title": title})
    years = [f"Year {row['year']}" for row in yearly_breakdown]
    cumulative = [float(row.get("cumulative_net", 0)) for row in yearly_breakdown]
    fig = go.Figure(
        go.Scatter(
            x=years,
            y=cumulative,
            mode="lines+markers",
            fill="tozeroy",
            line={"color": PALETTE["net_positive"], "width": 3},
        )
    )
    fig.update_layout(title=title, yaxis_tickformat="$,.0f")
    return _themed(fig)


def waterfall(values: Iterable[tuple[str, float]], *, title: str = "TEI Waterfall") -> go.Figure:
    """
    Generic waterfall (pass tuples of (label, value)).

    Used by 03_business_case to show: Benefits PV → Costs PV → NPV.
    """
    labels, amounts = zip(*values, strict=True) if values else ([], [])
    measures = ["relative"] * (len(labels) - 1) + ["total"] if labels else []
    fig = go.Figure(
        go.Waterfall(
            x=list(labels),
            y=list(amounts),
            measure=measures,
            text=[f"${v/1_000_000:,.1f}M" for v in amounts],
            textposition="outside",
            increasing={"marker": {"color": THEME["bar_green"]}},
            decreasing={"marker": {"color": THEME["bar_red"]}},
            totals={"marker": {"color": PALETTE["net_positive"]}},
        )
    )
    fig.update_layout(title=title, yaxis_tickformat="$,.0f")
    return _themed(fig)
