"""
Unit tests for BarChartStrategy helper methods.
"""

from typing import Any
from unittest.mock import MagicMock, patch

import plotly.graph_objects as go
import polars as pl
import pytest
from fields_metadata import FieldMetadata
from polychromos import HSLColor

from orama.color import (
    ColorAssignerByCategory,
    ColorRule,
    ColorSelectorStrategy,
)
from orama.strategies.bar import BarChartStrategy
from orama.variables import Aggregation


def _cat_meta() -> MagicMock:
    m = MagicMock(spec=FieldMetadata)
    m.categorical = True
    m.numeric = False
    m.derived = False
    m.effective_type = str
    m.extra = {}
    return m


def _make_bar(fields_metadata: dict[str, Any], **kwargs: Any) -> BarChartStrategy:
    return BarChartStrategy(
        fields_metadata=fields_metadata,
        category_variables=["cat"],
        aggregation=Aggregation(name="count", function="count"),
        **kwargs,
    )


@pytest.fixture
def cat_fields() -> dict[str, Any]:
    return {"cat": _cat_meta()}


@pytest.fixture
def bar(cat_fields: dict[str, Any]) -> BarChartStrategy:
    return _make_bar(cat_fields)


@pytest.fixture
def base_df() -> pl.DataFrame:
    return pl.DataFrame({"cat": ["A", "B", "C"], "count": [10, 20, 30]})


def test_prepare_df_returns_none_mean_by_default(
    bar: BarChartStrategy,
    base_df: pl.DataFrame,
    tr: Any,
) -> None:
    _, _, mean_value = bar._prepare_df(base_df, tr)
    assert mean_value is None


def test_prepare_df_count_mean_returns_float(
    cat_fields: dict[str, Any],
    tr: Any,
) -> None:
    instance = _make_bar(cat_fields, mean_category_name="Mean")
    df = pl.DataFrame({"cat": ["A", "B", "C"], "count": [10, 20, 30], "__total_count__": [10, 20, 30]})
    _, _, mean_value = instance._prepare_df(df, tr)
    assert isinstance(mean_value, float)


def test_prepare_df_alias_is_category_variable(
    bar: BarChartStrategy,
    base_df: pl.DataFrame,
    tr: Any,
) -> None:
    _, alias, _ = bar._prepare_df(base_df, tr)
    assert alias == "cat"


def test_compute_bar_colors_no_rules_returns_sequence(
    bar: BarChartStrategy,
    anchor_color: HSLColor,
    theme: Any,
) -> None:
    df = pl.DataFrame({"cat": ["A", "B", "C"], "count": [1, 2, 3]})
    result = bar._compute_bar_colors(df, "cat", theme)
    assert len(result) == df.height


def test_compute_bar_colors_with_rules_returns_sequence(
    cat_fields: dict[str, Any],
    anchor_colors: list[HSLColor],
    theme: Any,
) -> None:
    assigner = ColorAssignerByCategory(anchor_colors=anchor_colors)
    rule = ColorRule(assigner=assigner, selector=ColorSelectorStrategy())
    instance = _make_bar(cat_fields, color_rules=[rule])
    df = pl.DataFrame({"cat": ["A", "B", "C"], "count": [1, 2, 3]})
    result = instance._compute_bar_colors(df, "cat", theme)
    assert len(result) == df.height


def test_configure_bar_layout_showlegend_false(
    bar: BarChartStrategy,
    theme: Any,
) -> None:
    fig = go.Figure()
    bar._configure_bar_layout(fig, None, "Category", "Count", None)
    assert fig.layout.showlegend is False


def test_configure_bar_layout_vertical_titles(
    bar: BarChartStrategy,
    theme: Any,
) -> None:
    fig = go.Figure()
    bar._configure_bar_layout(fig, None, "MyCategory", "MyValue", None)
    assert fig.layout.xaxis.title.text == "MyCategory"
    assert fig.layout.yaxis.title.text == "MyValue"


def test_configure_bar_layout_horizontal_titles(
    cat_fields: dict[str, Any],
    theme: Any,
) -> None:
    instance = _make_bar(cat_fields, horizontal=True)
    fig = go.Figure()
    instance._configure_bar_layout(fig, None, "MyCategory", "MyValue", None)
    assert fig.layout.xaxis.title.text == "MyValue"
    assert fig.layout.yaxis.title.text == "MyCategory"


def test_configure_bar_layout_no_hline_when_mean_none(
    bar: BarChartStrategy,
    theme: Any,
) -> None:
    fig = go.Figure()
    bar._configure_bar_layout(fig, None, "Category", "Count", None)
    assert len(fig.layout.shapes) == 0


def test_configure_bar_layout_adds_hline_for_vertical_mean(
    cat_fields: dict[str, Any],
    theme: Any,
) -> None:
    instance = _make_bar(cat_fields, mean_annotation=True, mean_category_name="Mean")
    fig = go.Figure()
    instance._configure_bar_layout(fig, 5.0, "Category", "Count", "#ff0000")
    assert len(fig.layout.shapes) == 1


def test_prepare_plot_data_adds_categories_values_colors(
    cat_fields: dict[str, Any],
    tr: Any,
    theme: Any,
) -> None:
    bar = _make_bar(cat_fields)
    df = pl.DataFrame({"cat": ["A", "B", "C"], "count": [10, 20, 30]})
    result = bar._prepare_plot_data({"main": df}, tr, theme)
    assert result["categories"] == ["A", "B", "C"]
    assert result["values"] == [10, 20, 30]
    assert len(result["colors"]) == 3
    assert all(isinstance(c, str) and c.startswith("#") for c in result["colors"])


def test_prepare_plot_data_colors_reflect_color_rules(
    cat_fields: dict[str, Any],
    tr: Any,
    theme: Any,
    anchor_colors: list[HSLColor],
) -> None:
    rule = ColorRule(assigner=ColorAssignerByCategory(anchor_colors=anchor_colors))
    bar = _make_bar(cat_fields, color_rules=[rule])
    df = pl.DataFrame({"cat": ["A", "B"], "count": [5, 15]})
    result = bar._prepare_plot_data({"main": df}, tr, theme)
    assert len(result["colors"]) == 2


def test_build_pre_callback_receives_prepared_bar_payload(
    cat_fields: dict[str, Any],
    tr: Any,
    theme: Any,
) -> None:
    received: dict[str, Any] = {}

    def cb(payload: dict[str, Any]) -> None:
        received.update(payload)

    bar = _make_bar(cat_fields)
    bar._pre_build_callback = cb
    df = pl.DataFrame({"cat": ["X", "Y"], "count": [3, 7]})
    from orama.strategies.base import FigureStrategy

    with patch.object(FigureStrategy, "_apply_template"):
        bar.build({"main": df}, tr, theme)
    assert "categories" in received
    assert "values" in received
    assert "colors" in received
    assert received["categories"] == ["X", "Y"]
    assert received["values"] == [3, 7]


def test_build_pre_payload_includes_variables_and_options(
    cat_fields: dict[str, Any],
) -> None:
    bar = _make_bar(cat_fields)
    pre = bar._build_pre_payload({"main": pl.DataFrame()})
    assert set(pre["variables"]) == {"category", "value"}
    for entry in pre["variables"].values():
        assert "name" in entry and "description" in entry and "value" in entry
    assert "sort_by_value" in pre["options"]
    for entry in pre["options"].values():
        assert "name" in entry and "description" in entry and "value" in entry
