"""Tests for cpz.model — typed Price, Quantity, Money value types."""

from __future__ import annotations

import math
from decimal import Decimal

import pytest

from cpz.model.types import Money, Price, Quantity


class TestPrice:
    def test_from_float(self):
        p = Price(150.25)
        assert p.as_float() == 150.25

    def test_from_str(self):
        p = Price.from_str("150.25")
        assert p.value == Decimal("150.25")

    def test_from_int(self):
        p = Price(100)
        assert p.as_float() == 100.0

    def test_precision(self):
        p = Price(150.256, precision=2)
        assert str(p) == "150.26"

    def test_reject_nan(self):
        with pytest.raises(ValueError, match="must be finite"):
            Price(float("nan"))

    def test_reject_inf(self):
        with pytest.raises(ValueError, match="must be finite"):
            Price(float("inf"))

    def test_reject_nan_str(self):
        with pytest.raises(ValueError):
            Price("NaN")

    def test_equality(self):
        assert Price(100) == Price(100)
        assert Price(100) == 100
        assert Price(100) != Price(101)

    def test_comparison(self):
        assert Price(100) < Price(101)
        assert Price(100) <= Price(100)
        assert Price(101) > Price(100)
        assert Price(100) >= Price(100)

    def test_addition(self):
        result = Price(100) + Price(50)
        assert isinstance(result, Price)
        assert result == Price(150)

    def test_subtraction(self):
        result = Price(150) - Price(50)
        assert isinstance(result, Price)
        assert result == Price(100)

    def test_multiply_by_quantity(self):
        result = Price(100) * Quantity(10)
        assert isinstance(result, Money)
        assert result.as_float() == 1000.0

    def test_multiply_by_scalar(self):
        result = Price(100) * 2
        assert isinstance(result, Money)
        assert result.as_float() == 200.0

    def test_negation(self):
        assert (-Price(100)).as_float() == -100.0

    def test_hash(self):
        s = {Price(100), Price(100), Price(200)}
        assert len(s) == 2

    def test_repr(self):
        p = Price("150.25")
        assert "150.25" in repr(p)

    def test_bool(self):
        assert bool(Price(100))
        assert not bool(Price(0))


class TestQuantity:
    def test_from_int(self):
        q = Quantity.from_int(100)
        assert int(q) == 100

    def test_from_float(self):
        q = Quantity.from_float(10.5, precision=1)
        assert q.as_float() == 10.5

    def test_reject_negative(self):
        with pytest.raises(ValueError, match="cannot be negative"):
            Quantity(-1)

    def test_reject_nan(self):
        with pytest.raises(ValueError, match="must be finite"):
            Quantity(float("nan"))

    def test_zero(self):
        q = Quantity(0)
        assert q.as_float() == 0.0
        assert not bool(q)

    def test_addition(self):
        result = Quantity(10) + Quantity(5)
        assert isinstance(result, Quantity)
        assert result == 15

    def test_sub_negative_raises(self):
        with pytest.raises(ValueError, match="cannot be negative"):
            Quantity(5) - Quantity(10)

    def test_equality(self):
        assert Quantity(10) == Quantity(10)
        assert Quantity(10) == 10


class TestMoney:
    def test_basic(self):
        m = Money(1000, "USD")
        assert m.as_float() == 1000.0
        assert m.currency == "USD"

    def test_zero(self):
        m = Money.zero()
        assert m.as_float() == 0.0
        assert m.currency == "USD"

    def test_addition_same_currency(self):
        result = Money(100, "USD") + Money(50, "USD")
        assert result == Money(150, "USD")

    def test_addition_diff_currency_raises(self):
        with pytest.raises(ValueError, match="Cannot add"):
            Money(100, "USD") + Money(50, "EUR")

    def test_subtraction(self):
        result = Money(100, "USD") - Money(30, "USD")
        assert result == Money(70, "USD")

    def test_multiply_scalar(self):
        result = Money(100, "USD") * 3
        assert result == Money(300, "USD")

    def test_negation(self):
        result = -Money(100, "USD")
        assert result == Money(-100, "USD")

    def test_comparison_diff_currency_raises(self):
        with pytest.raises(ValueError, match="Cannot compare"):
            Money(100, "USD") < Money(200, "EUR")

    def test_reject_nan(self):
        with pytest.raises(ValueError, match="must be finite"):
            Money(float("nan"))

    def test_str(self):
        assert "USD" in str(Money(100, "USD"))
