from typing import cast
from unittest.mock import MagicMock

import pytest
from fields_metadata import FieldMetadata

from orama.variables import (
    Aggregation,
    CategoricalVariable,
    CountryCodeVariable,
    DateTimeVariable,
    NonDerivedNumericalVariable,
    NumericalAggregationVariable,
    NumericalVariable,
)


@pytest.mark.parametrize(
    "field,function,expected",
    [
        ("revenue", "average", "Average of revenue"),
        (None, "count", "Count"),
        ("sales", "sum", "Sum of sales"),
    ],
)
def test_aggregation_default_label(field: str | None, function: str, expected: str) -> None:
    agg = Aggregation(name="out", function=function, field=field)
    assert agg.default_label == expected


def test_categorical_variable_candidate_fields(
    categorical_metadata: MagicMock,
    numeric_metadata: MagicMock,
) -> None:
    var = CategoricalVariable("test", "desc", "map_to")
    fields = cast(dict[str, FieldMetadata], {"cat_field": categorical_metadata, "num_field": numeric_metadata})
    candidates = var.candidate_fields(fields)
    assert "cat_field" in candidates
    assert "num_field" not in candidates


def test_categorical_variable_validate_single_valid(categorical_metadata: MagicMock) -> None:
    var = CategoricalVariable("test", "desc", "map_to")
    var.validate_fields({"field": categorical_metadata}, "field")


def test_categorical_variable_validate_two_fields_not_multicategorical(
    categorical_metadata: MagicMock,
) -> None:
    var = CategoricalVariable("test", "desc", "map_to", multicategorical=False)
    fields = cast(dict[str, FieldMetadata], {"f1": categorical_metadata, "f2": categorical_metadata})
    with pytest.raises(ValueError):
        var.validate_fields(fields, "f1", "f2")


def test_categorical_variable_validate_two_fields_multicategorical(
    categorical_metadata: MagicMock,
) -> None:
    var = CategoricalVariable("test", "desc", "map_to", multicategorical=True)
    fields = cast(dict[str, FieldMetadata], {"f1": categorical_metadata, "f2": categorical_metadata})
    var.validate_fields(fields, "f1", "f2")


def test_categorical_variable_validate_non_candidate(
    numeric_metadata: MagicMock,
) -> None:
    var = CategoricalVariable("test", "desc", "map_to")
    with pytest.raises(ValueError):
        var.validate_fields({"field": numeric_metadata}, "field")


def test_categorical_variable_describe(categorical_metadata: MagicMock) -> None:
    var = CategoricalVariable(
        "test",
        "desc",
        "map_to",
        multicategorical=True,
        ordered=True,
        optional=True,
    )
    desc = var.describe({"field": categorical_metadata})
    assert "multicategorical" in desc
    assert "ordered" in desc
    assert "optional" in desc
    assert desc["multicategorical"] is True
    assert desc["ordered"] is True
    assert desc["optional"] is True


def test_numerical_variable_candidate_fields(
    numeric_metadata: MagicMock,
    categorical_metadata: MagicMock,
) -> None:
    var = NumericalVariable("test", "desc", "map_to")
    fields = cast(dict[str, FieldMetadata], {"num_field": numeric_metadata, "cat_field": categorical_metadata})
    candidates = var.candidate_fields(fields)
    assert "num_field" in candidates
    assert "cat_field" not in candidates


def test_numerical_variable_validate_single_valid(numeric_metadata: MagicMock) -> None:
    var = NumericalVariable("test", "desc", "map_to")
    var.validate_fields({"field": numeric_metadata}, "field")


def test_numerical_variable_validate_two_fields_raises(numeric_metadata: MagicMock) -> None:
    var = NumericalVariable("test", "desc", "map_to")
    fields = cast(dict[str, FieldMetadata], {"f1": numeric_metadata, "f2": numeric_metadata})
    with pytest.raises(ValueError):
        var.validate_fields(fields, "f1", "f2")


def test_numerical_variable_validate_non_numeric(categorical_metadata: MagicMock) -> None:
    var = NumericalVariable("test", "desc", "map_to")
    with pytest.raises(ValueError):
        var.validate_fields({"field": categorical_metadata}, "field")


def test_non_derived_numerical_variable_excludes_derived(
    numeric_metadata: MagicMock,
    derived_numeric_metadata: MagicMock,
) -> None:
    var = NonDerivedNumericalVariable("test", "desc", "map_to")
    fields = cast(dict[str, FieldMetadata], {"num": numeric_metadata, "derived": derived_numeric_metadata})
    candidates = var.candidate_fields(fields)
    assert "num" in candidates
    assert "derived" not in candidates


def test_numerical_aggregation_variable_excludes_derived(
    numeric_metadata: MagicMock,
    derived_numeric_metadata: MagicMock,
) -> None:
    var = NumericalAggregationVariable("test", "desc", "map_to")
    fields = cast(dict[str, FieldMetadata], {"num": numeric_metadata, "derived": derived_numeric_metadata})
    candidates = var.candidate_fields(fields)
    assert "num" in candidates
    assert "derived" not in candidates


def test_datetime_variable_candidate_fields(
    datetime_metadata: MagicMock,
    datetime_datetime_metadata: MagicMock,
    categorical_metadata: MagicMock,
) -> None:
    var = DateTimeVariable("test", "desc", "map_to")
    fields = cast(
        dict[str, FieldMetadata],
        {
            "date_field": datetime_metadata,
            "datetime_field": datetime_datetime_metadata,
            "cat_field": categorical_metadata,
        },
    )
    candidates = var.candidate_fields(fields)
    assert "date_field" in candidates
    assert "datetime_field" in candidates
    assert "cat_field" not in candidates


def test_datetime_variable_validate_single_valid(datetime_metadata: MagicMock) -> None:
    var = DateTimeVariable("test", "desc", "map_to")
    var.validate_fields({"field": datetime_metadata}, "field")


def test_datetime_variable_validate_multiple_raises(datetime_metadata: MagicMock) -> None:
    var = DateTimeVariable("test", "desc", "map_to")
    fields = cast(dict[str, FieldMetadata], {"f1": datetime_metadata, "f2": datetime_metadata})
    with pytest.raises(ValueError):
        var.validate_fields(fields, "f1", "f2")


def test_datetime_variable_validate_non_datetime(categorical_metadata: MagicMock) -> None:
    var = DateTimeVariable("test", "desc", "map_to")
    with pytest.raises(ValueError):
        var.validate_fields({"field": categorical_metadata}, "field")


def test_country_code_variable_candidate_fields(
    country_code_metadata: MagicMock,
    categorical_metadata: MagicMock,
) -> None:
    var = CountryCodeVariable("test", "desc", "map_to")
    fields = cast(dict[str, FieldMetadata], {"cc_field": country_code_metadata, "cat_field": categorical_metadata})
    candidates = var.candidate_fields(fields)
    assert "cc_field" in candidates
    assert "cat_field" not in candidates


def test_country_code_variable_validate_single_valid(country_code_metadata: MagicMock) -> None:
    var = CountryCodeVariable("test", "desc", "map_to")
    var.validate_fields({"field": country_code_metadata}, "field")


def test_country_code_variable_validate_multiple_raises(country_code_metadata: MagicMock) -> None:
    var = CountryCodeVariable("test", "desc", "map_to")
    fields = cast(dict[str, FieldMetadata], {"f1": country_code_metadata, "f2": country_code_metadata})
    with pytest.raises(ValueError):
        var.validate_fields(fields, "f1", "f2")


def test_country_code_variable_validate_non_candidate(categorical_metadata: MagicMock) -> None:
    var = CountryCodeVariable("test", "desc", "map_to")
    with pytest.raises(ValueError):
        var.validate_fields({"field": categorical_metadata}, "field")
