import math
from decimal import Decimal

import pytest

from keble_helpers import StdMoneyAmount, StdPercentage


@pytest.mark.parametrize(
    "a,b,expected",
    [
        (StdMoneyAmount(1000), StdMoneyAmount(2000), StdMoneyAmount(3000)),
        (StdMoneyAmount(1000), 2000, StdMoneyAmount(3000)),
        (StdMoneyAmount(1000), 2000.5, StdMoneyAmount(3000)),
        (StdMoneyAmount(-1000), 1000, StdMoneyAmount(0)),
        (StdMoneyAmount(1000), Decimal("2.5"), StdMoneyAmount(1002)),
    ],
)
def test_std_money_amount_add(a, b, expected):
    assert a + b == expected


@pytest.mark.parametrize(
    "a,b,expected",
    [
        (StdMoneyAmount(2000), StdMoneyAmount(1000), StdMoneyAmount(1000)),
        (StdMoneyAmount(2000), 1000, StdMoneyAmount(1000)),
        (StdMoneyAmount(2000), 1000.5, StdMoneyAmount(1000)),
        (StdMoneyAmount(-1000), 1000, StdMoneyAmount(-2000)),
    ],
)
def test_std_money_amount_sub(a, b, expected):
    assert a - b == expected


@pytest.mark.parametrize(
    "a,b,expected",
    [
        (StdMoneyAmount(1000), 2, StdMoneyAmount(2000)),
        (StdMoneyAmount(1000), 0.5, StdMoneyAmount(500)),
        (StdMoneyAmount(1000), Decimal("2.5"), StdMoneyAmount(2500)),
    ],
)
def test_std_money_amount_mul(a, b, expected):
    assert a * b == expected


@pytest.mark.parametrize(
    "a,b,expected",
    [
        (StdMoneyAmount(1000), 2, StdMoneyAmount(500)),
        (StdMoneyAmount(1000), 2.5, StdMoneyAmount(400)),
        (StdMoneyAmount(1000), Decimal("3.33"), StdMoneyAmount(300)),
        (StdMoneyAmount(1000), StdMoneyAmount(500), 2.0),
    ],
)
def test_std_money_amount_truediv(a, b, expected):
    assert a / b == expected


@pytest.mark.parametrize(
    "a,b,expected",
    [
        (StdMoneyAmount(1000), 200, 10),
        (StdMoneyAmount(1000), 333, 10),
        (StdMoneyAmount(-1000), 200, -10),
        (StdMoneyAmount(1000000), 200, 0),
        (StdMoneyAmount(1000), Decimal("3.33"), 0.01),
    ],
)
def test_std_money_amount_mod(a, b, expected):
    assert math.isclose(a % b, expected, rel_tol=0, abs_tol=1e-12)


def test_std_money_amount_division_by_zero():
    with pytest.raises(ZeroDivisionError):
        _ = StdMoneyAmount(1000) / 0
    with pytest.raises(ZeroDivisionError):
        _ = StdMoneyAmount(1000) % 0
    with pytest.raises(ZeroDivisionError):
        _ = StdMoneyAmount(1000) // 0


@pytest.mark.parametrize(
    "value,expected",
    [
        (10000, 100.0),
        (5050, 50.5),
        (0, 0.0),
        (-10000, -100.0),
        (10077, 100.77),
    ],
)
def test_std_money_amount_true_value(value, expected):
    assert StdMoneyAmount(value).true_value == expected


@pytest.mark.parametrize(
    "float_money,expected_value",
    [
        (1.00, 100),
        (50.50, 5050),
        (0.00, 0),
        (-1.23, -123),
        (1000000.99, 100000099),
        (0.01, 1),
        (123.45, 12345),
    ],
)
def test_std_money_amount_from_true_value(float_money, expected_value):
    assert StdMoneyAmount.from_true_value(float_money=float_money).value == expected_value


@pytest.mark.parametrize(
    "a,b,expected",
    [
        (StdPercentage(10000), StdPercentage(5000), StdPercentage(15000)),
        (StdPercentage(10000), 0.5, StdPercentage(15000)),
        (StdPercentage(-10000), -0.5, StdPercentage(-15000)),
    ],
)
def test_std_percentage_add(a, b, expected):
    assert a + b == expected


@pytest.mark.parametrize(
    "a,b,expected",
    [
        (StdPercentage(10000), StdPercentage(5000), StdPercentage(5000)),
        (StdPercentage(10000), 0.5, StdPercentage(5000)),
        (StdPercentage(-10000), -0.5, StdPercentage(-5000)),
    ],
)
def test_std_percentage_sub(a, b, expected):
    assert a - b == expected


@pytest.mark.parametrize(
    "a,b,expected",
    [
        (StdPercentage(10000), 2, StdPercentage(20000)),
        (StdPercentage(10000), 0.5, StdPercentage(5000)),
        (StdPercentage(10000), Decimal("2.5"), StdPercentage(25000)),
    ],
)
def test_std_percentage_mul(a, b, expected):
    assert a * b == expected


@pytest.mark.parametrize(
    "a,b,expected",
    [
        (StdPercentage(10000), 2, StdPercentage(5000)),
        (StdPercentage(10000), 2.5, StdPercentage(4000)),
    ],
)
def test_std_percentage_truediv(a, b, expected):
    assert a / b == expected


@pytest.mark.parametrize(
    "a,b,expected",
    [
        (StdPercentage(10033), 100, 0.33),  # 100.33% % 1.00%
        (StdPercentage(333), 10, 0.03),  # 3.33% % 0.10%
        (StdPercentage(10000), 333, 0.1),  # 100.00% % 3.33%
        (StdPercentage(2000333), 1000, 3.33),  # 20003.33% % 10.00%
    ],
)
def test_std_percentage_mod(a, b, expected):
    assert math.isclose(a % b, expected, rel_tol=0, abs_tol=1e-12)


def test_std_percentage_division_by_zero():
    with pytest.raises(ZeroDivisionError):
        _ = StdPercentage(10000) / 0
    with pytest.raises(ZeroDivisionError):
        _ = StdPercentage(10000) % 0
    with pytest.raises(ZeroDivisionError):
        _ = StdPercentage(10000) // 0


@pytest.mark.parametrize(
    "percentage,expected",
    [
        (StdPercentage(10000), 1.0),
        (StdPercentage(5050), 0.505),
        (StdPercentage(0), 0.0),
        (StdPercentage(-10000), -1.0),
        (StdPercentage(10077), 1.0077),
    ],
)
def test_std_percentage_true_value(percentage, expected):
    assert percentage.true_value == expected


@pytest.mark.parametrize(
    "float_percent,string_percent,expected_value",
    [
        (1.00, None, 10000),
        (0.50, None, 5000),
        (None, "100%", 10000),
        (None, "50%", 5000),
        (None, " 0.01% ", 1),
    ],
)
def test_std_percentage_from_true_value(float_percent, string_percent, expected_value):
    if float_percent is not None:
        percentage = StdPercentage.from_true_value(float_percent=float_percent)
    else:
        percentage = StdPercentage.from_true_value(string_percent=string_percent)
    assert percentage.value == expected_value


@pytest.mark.parametrize(
    "value,expected_str,expected_string,expected_repr",
    [
        (10000, "100.00%", "100%", "Percentage(value=10000)"),
        (5050, "50.50%", "50.5%", "Percentage(value=5050)"),
        (10, "0.10%", "0.1%", "Percentage(value=10)"),
        (1, "0.01%", "0.01%", "Percentage(value=1)"),
        (-10000, "-100.00%", "-100%", "Percentage(value=-10000)"),
    ],
)
def test_std_percentage_strings(value, expected_str, expected_string, expected_repr):
    percentage = StdPercentage(value)
    assert str(percentage) == expected_str
    assert percentage.string == expected_string
    assert percentage.four_digits_string == expected_str
    assert repr(percentage) == expected_repr
