"""
Unit tests for LineChartStrategy helper methods.
"""

import datetime
from typing import Any
from unittest.mock import MagicMock

import polars as pl
import pytest
from fields_metadata import FieldMetadata

from orama.enums import LineSortOrder
from orama.strategies.line import LineChartStrategy
from orama.variables import Aggregation


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


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_line_full(**kwargs: Any) -> LineChartStrategy:
    """LineChartStrategy with full fields_metadata for _prepare_plot_data tests."""
    date_meta = MagicMock(spec=FieldMetadata)
    date_meta.effective_type = datetime.date
    date_meta.categorical = False
    date_meta.numeric = False
    date_meta.derived = False
    date_meta.extra = {}
    return LineChartStrategy(
        fields_metadata={
            "date": date_meta,
            "date__year": _int_meta(),
            "date__month": _int_meta(),
            "cat": _cat_meta(),
        },
        date_variable="date",
        category_variable="cat",
        aggregation=Aggregation(name="count", function="count"),
        **kwargs,
    )


def _make_line(**kwargs: Any) -> LineChartStrategy:
    date_meta = MagicMock(spec=FieldMetadata)
    date_meta.effective_type = datetime.date
    date_meta.categorical = False
    date_meta.numeric = False
    date_meta.derived = False
    date_meta.extra = {}
    return LineChartStrategy(
        fields_metadata={"date": date_meta},
        date_variable="date",
        aggregation=Aggregation(name="count", function="count"),
        **kwargs,
    )


def test_compute_value_range_explicit_min_max() -> None:
    instance = _make_line(min_value=2.0, max_value=10.0)
    df = pl.DataFrame({"count": [3, 4, 5]})
    min_val, max_val = instance._compute_value_range(df)
    assert min_val == 2.0
    assert max_val == 10.0


def test_compute_value_range_infers_from_data() -> None:
    instance = _make_line()
    df = pl.DataFrame({"count": [10, 100]})
    min_val, max_val = instance._compute_value_range(df)
    assert min_val < 10
    assert max_val > 100


def test_compute_value_range_explicit_min_only() -> None:
    instance = _make_line(min_value=5.0)
    df = pl.DataFrame({"count": [0, 50]})
    min_val, max_val = instance._compute_value_range(df)
    assert min_val == 5.0
    assert max_val > 50


@pytest.mark.parametrize(
    "sort_order,expected_first",
    [
        (LineSortOrder.NONE, "B"),
        (LineSortOrder.ALPHABETICAL_ASC, "A"),
        (LineSortOrder.ALPHABETICAL_DESC, "C"),
        (LineSortOrder.FIRST_VALUE_ASC, "A"),
        (LineSortOrder.FIRST_VALUE_DESC, "C"),
        (LineSortOrder.LAST_VALUE_ASC, "A"),
        (LineSortOrder.LAST_VALUE_DESC, "C"),
    ],
)
def test_sort_active_categories(sort_order: LineSortOrder, expected_first: str) -> None:
    instance = _make_line(sort_order=sort_order)
    line_dfs = {
        "B": pl.DataFrame({"count": [2.0, 2.0]}),
        "A": pl.DataFrame({"count": [1.0, 1.0]}),
        "C": pl.DataFrame({"count": [3.0, 3.0]}),
    }
    active = ["B", "A", "C"]
    result = instance._sort_active_categories(active, line_dfs)
    assert result[0] == expected_first


@pytest.fixture
def line_df() -> pl.DataFrame:
    return pl.DataFrame(
        {
            "date__year": [2024, 2024, 2024, 2024],
            "date__month": [1, 1, 2, 2],
            "cat": ["A", "B", "A", "B"],
            "count": [10, 20, 30, 40],
        }
    )


def test_prepare_plot_data_returns_categories_and_colors(
    line_df: pl.DataFrame,
    tr: Any,
    theme: Any,
) -> None:
    instance = _make_line_full()
    result = instance._prepare_plot_data({"main": line_df}, tr, theme)
    assert isinstance(result["categories"], list)
    assert len(result["colors"]) == len(result["categories"])
    assert all(c.startswith("#") for c in result["colors"])


def test_prepare_plot_data_returns_line_dfs_keyed_by_category(
    line_df: pl.DataFrame,
    tr: Any,
    theme: Any,
) -> None:
    instance = _make_line_full()
    result = instance._prepare_plot_data({"main": line_df}, tr, theme)
    assert isinstance(result["_line_dfs"], dict)
    assert set(result["_line_dfs"].keys()) == {"A", "B"}


def test_prepare_plot_data_returns_value_range(
    line_df: pl.DataFrame,
    tr: Any,
    theme: Any,
) -> None:
    instance = _make_line_full()
    result = instance._prepare_plot_data({"main": line_df}, tr, theme)
    min_val, max_val = result["_value_range"]
    assert min_val < max_val
