"""
Tests for augment_d() chart output.

Regression for: charts not rendering because augment_d() was producing raw
data dicts instead of full Chart.js 4.x config objects. Each chart must have
the shape {type, data: {labels, datasets: [{data, ...}]}, options} so that
initCharts() can pass it directly to new Chart(canvas, config).
"""
import json

import pytest
from padelnomics.planner.calculator import calc, validate_state
from padelnomics.planner.routes import augment_d


def make_result(lang="en", **state_overrides):
    """Return (d, s) with augment_d applied."""
    s = validate_state(state_overrides)
    d = calc(s)
    augment_d(d, s, lang)
    return d, s


INDOOR_CHART_KEYS = ["capex_chart", "ramp_chart", "pl_chart", "cf_chart", "cum_chart", "dscr_chart"]
ALL_CHART_KEYS = INDOOR_CHART_KEYS + ["season_chart"]


# ════════════════════════════════════════════════════════════
# Structure: every chart must be a valid Chart.js config
# ════════════════════════════════════════════════════════════

class TestChartStructure:
    """Each chart must have the shape Chart.js 4.x expects: {type, data, options}."""

    def test_all_indoor_charts_present(self):
        d, _ = make_result()
        for key in INDOOR_CHART_KEYS:
            assert key in d, f"Missing chart key: {key}"

    def test_season_chart_present_for_outdoor(self):
        d, _ = make_result(venue="outdoor")
        assert "season_chart" in d

    @pytest.mark.parametrize("key", INDOOR_CHART_KEYS)
    def test_chart_has_type_string(self, key):
        d, _ = make_result()
        assert "type" in d[key], f"{key} missing 'type'"
        assert isinstance(d[key]["type"], str)

    @pytest.mark.parametrize("key", INDOOR_CHART_KEYS)
    def test_chart_has_data_with_datasets_list(self, key):
        d, _ = make_result()
        chart = d[key]
        assert "data" in chart, f"{key} missing 'data'"
        assert "datasets" in chart["data"], f"{key} missing 'data.datasets'"
        assert isinstance(chart["data"]["datasets"], list)
        assert len(chart["data"]["datasets"]) >= 1

    @pytest.mark.parametrize("key", INDOOR_CHART_KEYS)
    def test_chart_has_responsive_options(self, key):
        d, _ = make_result()
        opts = d[key].get("options", {})
        assert opts.get("responsive") is True, f"{key} options.responsive must be True"
        assert opts.get("maintainAspectRatio") is False, f"{key} options.maintainAspectRatio must be False"

    @pytest.mark.parametrize("key", INDOOR_CHART_KEYS)
    def test_chart_is_json_serializable(self, key):
        """Charts are embedded as JSON in templates — they must serialise cleanly."""
        d, _ = make_result()
        json.dumps(d[key])  # must not raise

    @pytest.mark.parametrize("key", INDOOR_CHART_KEYS)
    def test_datasets_each_have_data_array(self, key):
        d, _ = make_result()
        for ds in d[key]["data"]["datasets"]:
            assert "data" in ds, f"{key} dataset missing 'data'"
            assert isinstance(ds["data"], list)


# ════════════════════════════════════════════════════════════
# Per-chart specifics
# ════════════════════════════════════════════════════════════

class TestCapexChart:
    def test_type_is_doughnut(self):
        d, _ = make_result()
        assert d["capex_chart"]["type"] == "doughnut"

    def test_labels_data_colors_same_length(self):
        d, _ = make_result()
        chart = d["capex_chart"]
        ds = chart["data"]["datasets"][0]
        assert len(chart["data"]["labels"]) == len(ds["data"]) == len(ds["backgroundColor"])

    def test_only_nonzero_items_included(self):
        """Zero-amount CAPEX items must be excluded from the chart."""
        d, _ = make_result()
        for val in d["capex_chart"]["data"]["datasets"][0]["data"]:
            assert val > 0, "Chart must only contain items with amount > 0"

    def test_cutout_set(self):
        d, _ = make_result()
        assert d["capex_chart"]["options"].get("cutout") == "60%"

    def test_legend_position_right(self):
        d, _ = make_result()
        legend = d["capex_chart"]["options"]["plugins"]["legend"]
        assert legend["position"] == "right"


class TestRampChart:
    def test_type_is_line(self):
        d, _ = make_result()
        assert d["ramp_chart"]["type"] == "line"

    def test_two_datasets(self):
        d, _ = make_result()
        assert len(d["ramp_chart"]["data"]["datasets"]) == 2

    def test_24_data_points(self):
        d, _ = make_result()
        for ds in d["ramp_chart"]["data"]["datasets"]:
            assert len(ds["data"]) == 24

    def test_labels_translated(self):
        d_en, _ = make_result(lang="en")
        d_de, _ = make_result(lang="de")
        en_label = d_en["ramp_chart"]["data"]["datasets"][0]["label"]
        de_label = d_de["ramp_chart"]["data"]["datasets"][0]["label"]
        assert en_label != de_label, "Ramp chart labels must be translated"


class TestPLChart:
    def test_type_is_bar(self):
        d, _ = make_result()
        assert d["pl_chart"]["type"] == "bar"

    def test_horizontal_axis(self):
        """P&L chart must be horizontal (indexAxis='y')."""
        d, _ = make_result()
        assert d["pl_chart"]["options"].get("indexAxis") == "y"

    def test_five_labels_and_values(self):
        d, _ = make_result()
        assert len(d["pl_chart"]["data"]["labels"]) == 5
        assert len(d["pl_chart"]["data"]["datasets"][0]["data"]) == 5

    def test_colors_reflect_sign(self):
        """Positive values → green, negative → red."""
        d, _ = make_result()
        ds = d["pl_chart"]["data"]["datasets"][0]
        for val, color in zip(ds["data"], ds["backgroundColor"]):
            if val >= 0:
                assert "22,163,74" in color, f"Expected green for positive {val}"
            else:
                assert "239,68,68" in color, f"Expected red for negative {val}"


class TestCFChart:
    def test_type_is_bar(self):
        d, _ = make_result()
        assert d["cf_chart"]["type"] == "bar"

    def test_60_data_points(self):
        d, _ = make_result()
        assert len(d["cf_chart"]["data"]["datasets"][0]["data"]) == 60

    def test_colors_reflect_sign(self):
        d, _ = make_result()
        ds = d["cf_chart"]["data"]["datasets"][0]
        for val, color in zip(ds["data"], ds["backgroundColor"]):
            if val >= 0:
                assert "22,163,74" in color
            else:
                assert "239,68,68" in color


class TestCumChart:
    def test_type_is_line(self):
        d, _ = make_result()
        assert d["cum_chart"]["type"] == "line"

    def test_60_data_points(self):
        d, _ = make_result()
        assert len(d["cum_chart"]["data"]["datasets"][0]["data"]) == 60

    def test_fill_enabled(self):
        d, _ = make_result()
        assert d["cum_chart"]["data"]["datasets"][0].get("fill") is True


class TestDSCRChart:
    def test_type_is_bar(self):
        d, _ = make_result()
        assert d["dscr_chart"]["type"] == "bar"

    def test_five_entries(self):
        d, _ = make_result()
        assert len(d["dscr_chart"]["data"]["datasets"][0]["data"]) == 5

    def test_colors_reflect_1_2_threshold(self):
        d, _ = make_result()
        ds = d["dscr_chart"]["data"]["datasets"][0]
        for val, color in zip(ds["data"], ds["backgroundColor"]):
            if val >= 1.2:
                assert "22,163,74" in color, f"Expected green for DSCR {val} >= 1.2"
            else:
                assert "239,68,68" in color, f"Expected red for DSCR {val} < 1.2"

    def test_values_capped_at_10(self):
        """DSCR values above 10 (no-debt scenarios) must be capped."""
        d, _ = make_result(loanPct=0)
        for val in d["dscr_chart"]["data"]["datasets"][0]["data"]:
            assert val <= 10


class TestSeasonChart:
    def test_type_is_bar(self):
        d, _ = make_result(venue="outdoor")
        assert d["season_chart"]["type"] == "bar"

    def test_12_points(self):
        d, _ = make_result(venue="outdoor")
        assert len(d["season_chart"]["data"]["datasets"][0]["data"]) == 12
        assert len(d["season_chart"]["data"]["labels"]) == 12

    def test_values_are_percentages(self):
        """Season values are fractions (0–1.5) multiplied by 100."""
        d, s = make_result(venue="outdoor")
        chart_vals = d["season_chart"]["data"]["datasets"][0]["data"]
        expected = [v * 100 for v in s["season"]]
        assert chart_vals == expected

    def test_labels_translated(self):
        d_en, _ = make_result(lang="en", venue="outdoor")
        d_de, _ = make_result(lang="de", venue="outdoor")
        assert d_en["season_chart"]["data"]["labels"] != d_de["season_chart"]["data"]["labels"]


# ════════════════════════════════════════════════════════════
# All combos: charts always valid
# ════════════════════════════════════════════════════════════

@pytest.mark.parametrize("venue,own", [
    ("indoor", "rent"), ("indoor", "buy"), ("outdoor", "rent"), ("outdoor", "buy")
])
def test_all_charts_json_serializable_for_all_combos(venue, own):
    d, _ = make_result(venue=venue, own=own)
    for key in INDOOR_CHART_KEYS:
        json.dumps(d[key])
