from contextlib import nullcontext
from typing import Any

import pytest

from orama.enums import GeoProjection, LineInterpolation, LinePlacement, LineSortOrder, SortOrder
from orama.options import (
    BooleanOption,
    FloatOption,
    GeoProjectionOption,
    IntOption,
    LineInterpolationOption,
    LinePlacementOption,
    LineSortOrderOption,
    SortOrderOption,
    StringOption,
)


@pytest.mark.parametrize(
    "value,ctx",
    [
        (True, nullcontext()),
        (False, nullcontext()),
        ("yes", pytest.raises(ValueError)),
        (1, pytest.raises(ValueError)),
        (None, pytest.raises(ValueError)),
    ],
)
def test_boolean_option_validate(value: Any, ctx: Any) -> None:
    opt = BooleanOption("test", "desc", "map_to")
    with ctx:
        opt.validate(value)


def test_boolean_option_describe() -> None:
    opt = BooleanOption("test", "desc", "map_to", default_value=True)
    desc = opt.describe()
    assert "default_value" in desc
    assert desc["value_type"] is bool


@pytest.mark.parametrize(
    "value,ctx",
    [
        (1.0, nullcontext()),
        (2, nullcontext()),
        ("string", pytest.raises(ValueError)),
        (-1.0, pytest.raises(ValueError)),
        (11.0, pytest.raises(ValueError)),
    ],
)
def test_float_option_validate(value: Any, ctx: Any) -> None:
    opt = FloatOption("test", "desc", "map_to", min_value=0.0, max_value=10.0)
    with ctx:
        opt.validate(value)


def test_float_option_describe_with_bounds() -> None:
    opt = FloatOption("test", "desc", "map_to", min_value=0.0, max_value=10.0)
    desc = opt.describe()
    assert "min_value" in desc
    assert "max_value" in desc


def test_float_option_describe_without_bounds() -> None:
    opt = FloatOption("test", "desc", "map_to")
    desc = opt.describe()
    assert "min_value" not in desc
    assert "max_value" not in desc


@pytest.mark.parametrize(
    "value,ctx",
    [
        (5, nullcontext()),
        (True, pytest.raises(ValueError)),
        ("string", pytest.raises(ValueError)),
        (-1, pytest.raises(ValueError)),
        (11, pytest.raises(ValueError)),
    ],
)
def test_int_option_validate(value: Any, ctx: Any) -> None:
    opt = IntOption("test", "desc", "map_to", min_value=0, max_value=10)
    with ctx:
        opt.validate(value)


def test_int_option_describe_with_bounds() -> None:
    opt = IntOption("test", "desc", "map_to", min_value=0, max_value=10)
    desc = opt.describe()
    assert "min_value" in desc
    assert "max_value" in desc


def test_int_option_describe_without_bounds() -> None:
    opt = IntOption("test", "desc", "map_to")
    desc = opt.describe()
    assert "min_value" not in desc
    assert "max_value" not in desc


@pytest.mark.parametrize(
    "value,ctx",
    [
        ("hello", nullcontext()),
        (123, pytest.raises(ValueError)),
        (None, pytest.raises(ValueError)),
    ],
)
def test_string_option_validate(value: Any, ctx: Any) -> None:
    opt = StringOption("test", "desc", "map_to")
    with ctx:
        opt.validate(value)


def test_string_option_describe() -> None:
    opt = StringOption("test", "desc", "map_to")
    desc = opt.describe()
    assert desc["value_type"] is str


@pytest.mark.parametrize(
    "option_class,valid_value,expected_type",
    [
        (SortOrderOption, SortOrder.ASCENDING, SortOrder),
        (LinePlacementOption, LinePlacement.OVERLAPPING, LinePlacement),
        (LineInterpolationOption, LineInterpolation.LINEAR, LineInterpolation),
        (LineSortOrderOption, LineSortOrder.NONE, LineSortOrder),
        (GeoProjectionOption, GeoProjection.NATURAL_EARTH, GeoProjection),
    ],
)
def test_enum_option_validate_valid(option_class: Any, valid_value: Any, expected_type: Any) -> None:
    opt = option_class("test", "desc", "map_to")
    opt.validate(valid_value)
    assert opt.describe()["value_type"] == expected_type


@pytest.mark.parametrize(
    "option_class,invalid_value",
    [
        (SortOrderOption, "ascending"),
        (SortOrderOption, 1),
        (LinePlacementOption, "overlapping"),
        (LinePlacementOption, 0),
        (LineInterpolationOption, "linear"),
        (LineInterpolationOption, 0),
        (LineSortOrderOption, "none"),
        (LineSortOrderOption, 0),
        (GeoProjectionOption, "natural earth"),
        (GeoProjectionOption, 0),
    ],
)
def test_enum_option_validate_invalid(option_class: Any, invalid_value: Any) -> None:
    opt = option_class("test", "desc", "map_to")
    with pytest.raises(ValueError):
        opt.validate(invalid_value)
