from datetime import datetime, timezone
from typing import get_args

import keble_exceptions
import pytest
from keble_exceptions import KebleException
from pydantic import BaseModel, Field

from keble_helpers import (
    AmazonMarketplace,
    AmazonMarketplaceEU,
    AnyMarketplace,
    CommerceEntityType,
    Currency,
    ExchangeRateInUsd,
    Language,
    Marketplace,
    Money,
    MoneyRange,
    NaturalLanguage,
    StdMoneyAmount,
    StdPercentage,
    PercentageRange,
    PromptRole,
    Timestamp,
    UnspecifiedMarketplace,
    is_vectors_list,
)


def test_CommerceEntityType_values_and_title_conversion():
    assert CommerceEntityType.PRODUCT.value == "PRODUCT"
    assert CommerceEntityType.BRAND_IDENTITY.value == "BRAND_IDENTITY"
    assert CommerceEntityType.CATEGORY.value == "CATEGORY"
    assert CommerceEntityType.SKU.upper_snake_to_title() == "SKU"
    assert CommerceEntityType.BRAND_MENTION.upper_snake_to_title() == "Brand Mention"


def test_timestamp_pydantic_type():
    class CustomCls(BaseModel):
        created_ts: Timestamp = Field(
            default_factory=lambda: datetime.now(timezone.utc)
        )
        updated_ts: Timestamp = Field(
            default_factory=lambda: datetime.now(timezone.utc)
        )

    c = CustomCls(created_ts=datetime.now())
    assert isinstance(c.created_ts, datetime)
    assert isinstance(c.updated_ts, datetime)

    model_dump = c.model_dump()
    model_dump_json_mode = c.model_dump(mode="json")

    assert isinstance(model_dump["created_ts"], float)
    assert isinstance(model_dump["updated_ts"], float)

    assert c.created_ts.timestamp() == model_dump["created_ts"]
    assert c.updated_ts.timestamp() == model_dump["updated_ts"]

    assert isinstance(model_dump_json_mode["created_ts"], float)
    assert isinstance(model_dump_json_mode["updated_ts"], float)

    assert c.created_ts.timestamp() == model_dump_json_mode["created_ts"]
    assert c.updated_ts.timestamp() == model_dump_json_mode["updated_ts"]

    model_dump_json_str = c.model_dump_json()
    assert f"{c.created_ts.timestamp()}" in model_dump_json_str
    assert f"{c.updated_ts.timestamp()}" in model_dump_json_str


@pytest.mark.parametrize(
    "marketplace, amazon_marketplace",
    [
        (Marketplace.US, AmazonMarketplace.US),
        (Marketplace.ES, AmazonMarketplace.ES),
        (Marketplace.FR, AmazonMarketplace.FR),
        (Marketplace.IT, AmazonMarketplace.IT),
    ],
)
def test_Marketplace_to_amazon_marketplace(marketplace, amazon_marketplace):
    assert marketplace.to_amazon_marketplace() == amazon_marketplace


def test_UnspecifiedMarketplace_values_and_casting():
    assert UnspecifiedMarketplace.GLOBAL.value == "GLOBAL"
    assert UnspecifiedMarketplace.UNKNOWN.value == "UNKNOWN"
    assert UnspecifiedMarketplace("GLOBAL") == UnspecifiedMarketplace.GLOBAL
    assert UnspecifiedMarketplace("UNKNOWN") == UnspecifiedMarketplace.UNKNOWN


def test_AnyMarketplace_is_union_of_marketplace_and_unspecified_marketplace():
    assert set(get_args(AnyMarketplace)) == {Marketplace, UnspecifiedMarketplace}


def test_AnyMarketplace_pydantic_parsing():
    class Model(BaseModel):
        marketplace: AnyMarketplace

    assert Model(marketplace="US").marketplace == Marketplace.US
    assert Model(marketplace="GLOBAL").marketplace == UnspecifiedMarketplace.GLOBAL


@pytest.mark.parametrize(
    "marketplace, expected_currency",
    [
        (AmazonMarketplace.US, Currency.USD),
        (AmazonMarketplace.JP, Currency.JPY),
        (AmazonMarketplace.UK, Currency.GBP),
        (AmazonMarketplace.SE, Currency.SEK),
        (AmazonMarketplace.PL, Currency.PLN),
        (AmazonMarketplace.DE, Currency.EUR),
        (AmazonMarketplace.ES, Currency.EUR),
        (AmazonMarketplace.FR, Currency.EUR),
        (AmazonMarketplace.IT, Currency.EUR),
        (AmazonMarketplace.BE, Currency.EUR),
    ],
)
def test_AmazonMarketplace_currency(marketplace, expected_currency):
    assert marketplace.currency == expected_currency


def test_Currency_has_decimal():
    # Currencies without decimal places
    for currency in [
        "JPY",
        "KRW",
        "VND",
        "XAF",
        "XOF",
        "CLP",
        "PYG",
        "RWF",
        "BIF",
        "DJF",
        "GNF",
        "KMF",
        "UGX",
        "MGA",
        "VUV",
    ]:
        assert not Currency(currency).has_decimal, (
            f"{currency} should not have decimal places"
        )

    # Currencies with decimal places
    for currency in ["USD", "EUR", "GBP", "CAD", "AUD", "INR", "CNY"]:
        assert Currency(currency).has_decimal, f"{currency} should have decimal places"


# Create some common currency instances for testing
USD = Currency.USD
EUR = Currency.EUR
JPY = Currency.JPY

# Create some common StdMoneyAmount instances for testing
amount_1000 = StdMoneyAmount(value=1000)  # $10.00
amount_500 = StdMoneyAmount(value=500)  # $5.00
amount_1500 = StdMoneyAmount(value=1500)  # $15.00
amount_0 = StdMoneyAmount(value=0)  # $0.00


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_500, currency=USD),
            Money(amount=amount_1500, currency=USD),
        ),
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_1000 * 2, currency=USD),
        ),
    ],
)
def test_Money_addition(a, b, expected):
    assert (a + b) == expected


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_500, currency=USD),
            Money(amount=StdMoneyAmount(value=500), currency=USD),
        ),
        (
            Money(amount=amount_500, currency=USD),
            Money(amount=amount_1000, currency=USD),
            Money(amount=StdMoneyAmount(value=-500), currency=USD),
        ),
    ],
)
def test_Money_subtraction(a, b, expected):
    assert (a - b) == expected


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            2,
            Money(amount=StdMoneyAmount(value=2000), currency=USD),
        ),
        (
            Money(amount=amount_500, currency=USD),
            3,
            Money(amount=StdMoneyAmount(value=1500), currency=USD),
        ),
    ],
)
def test_Money_multiplication(a, b, expected):
    assert (a * b) == expected


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            2,
            Money(amount=StdMoneyAmount(value=500), currency=USD),
        ),
        (
            Money(amount=amount_1000, currency=USD),
            4,
            Money(amount=StdMoneyAmount(value=250), currency=USD),
        ),
    ],
)
def test_Money_division(a, b, expected):
    assert (a / b) == expected


def test_is_vectors_list():
    assert is_vectors_list([0.1]) is False
    assert is_vectors_list([[0.1], [0.2]]) is True

    with pytest.raises(keble_exceptions.ServerSideInvalidParams):
        is_vectors_list([])

    with pytest.raises(keble_exceptions.ServerSideInvalidParams):
        is_vectors_list(None)  # type: ignore[arg-type]


def test_prompt_role_aliases():
    assert PromptRole("ai") == PromptRole.assistant
    assert PromptRole("human") == PromptRole.user
    assert PromptRole("system") == PromptRole.system


def test_money_range_validation():
    usd = Currency.USD
    eur = Currency.EUR

    lower = Money(amount=StdMoneyAmount(value=100), currency=usd)
    upper = Money(amount=StdMoneyAmount(value=200), currency=usd)
    assert MoneyRange.build(lower=lower, upper=upper, currency=usd).currency == usd

    with pytest.raises(keble_exceptions.ServerSideInvalidParams):
        MoneyRange.build(lower=Money(amount=StdMoneyAmount(value=100), currency=eur), upper=None, currency=usd)

    with pytest.raises(keble_exceptions.ServerSideInvalidParams):
        MoneyRange.build(lower=upper, upper=lower, currency=usd)


def test_percentage_range_validation():
    with pytest.raises(keble_exceptions.ServerSideInvalidParams):
        PercentageRange.build(lower=StdPercentage(20000), upper=StdPercentage(10000))

    assert PercentageRange.build(lower=StdPercentage(10000), upper=StdPercentage(20000))


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (Money(amount=amount_1000, currency=USD), 300, 10),
        (Money(amount=amount_1000, currency=USD), 200, 10),
    ],
)
def test_Money_modulus(a, b, expected):
    assert (a % b) == expected


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            3,
            Money(amount=StdMoneyAmount(value=333), currency=USD),
        ),
        (
            Money(amount=amount_1000, currency=USD),
            1000,
            Money(amount=StdMoneyAmount(value=1), currency=USD),
        ),
    ],
)
def test_Money_floor_division(a, b, expected):
    assert (a // b) == expected


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            2,
            Money(amount=StdMoneyAmount(value=1000000), currency=USD),
        ),
        (
            Money(amount=amount_500, currency=USD),
            3,
            Money(amount=StdMoneyAmount(value=125000000), currency=USD),
        ),
    ],
)
def test_Money_exponentiation(a, b, expected):
    assert (a**b) == expected


@pytest.mark.parametrize(
    "a, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=StdMoneyAmount(value=-1000), currency=USD),
        ),
        (
            Money(amount=amount_500, currency=USD),
            Money(amount=StdMoneyAmount(value=-500), currency=USD),
        ),
    ],
)
def test_Money_negation(a, expected):
    assert (-a) == expected


@pytest.mark.parametrize(
    "a, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=StdMoneyAmount(value=1000), currency=USD),
        ),
        (
            Money(amount=amount_500, currency=USD),
            Money(amount=StdMoneyAmount(value=500), currency=USD),
        ),
    ],
)
def test_Money_absolute(a, expected):
    assert (abs(a)) == expected


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_500, currency=USD),
            Money(amount=amount_1000, currency=USD),
            True,
        ),
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_500, currency=USD),
            False,
        ),
    ],
)
def test_Money_less_than(a, b, expected):
    assert (a < b) == expected


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_500, currency=USD),
            Money(amount=amount_1000, currency=USD),
            True,
        ),
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_1000, currency=USD),
            True,
        ),
    ],
)
def test_Money_less_than_equal(a, b, expected):
    assert (a <= b) == expected


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_500, currency=USD),
            True,
        ),
        (
            Money(amount=amount_500, currency=USD),
            Money(amount=amount_1000, currency=USD),
            False,
        ),
    ],
)
def test_Money_greater_than(a, b, expected):
    assert (a > b) == expected


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_500, currency=USD),
            True,
        ),
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_1000, currency=USD),
            True,
        ),
    ],
)
def test_Money_greater_than_equal(a, b, expected):
    assert (a >= b) == expected


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_1000, currency=USD),
            True,
        ),
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_500, currency=USD),
            False,
        ),
    ],
)
def test_Money_equal(a, b, expected):
    assert (a == b) == expected


@pytest.mark.parametrize(
    "a, b, expected",
    [
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_500, currency=USD),
            True,
        ),
        (
            Money(amount=amount_1000, currency=USD),
            Money(amount=amount_1000, currency=USD),
            False,
        ),
    ],
)
def test_Money_not_equal(a, b, expected):
    assert (a != b) == expected


def test_Money_currency_mismatch():
    a = Money(amount=amount_1000, currency=USD)
    b = Money(amount=amount_500, currency=EUR)
    with pytest.raises(KebleException):
        _ = a + b
    with pytest.raises(KebleException):
        _ = a - b
    with pytest.raises(KebleException):
        _ = a == b
    with pytest.raises(KebleException):
        _ = a != b
    with pytest.raises(KebleException):
        _ = a < b
    with pytest.raises(KebleException):
        _ = a > b


# test exchange rate
jpy_exchange_rate = ExchangeRateInUsd(
    currency=Currency.JPY,
    exchange_rate=StdMoneyAmount.from_true_value(float_money=146.21),
)
hkd_exchange_rate = ExchangeRateInUsd(
    currency=Currency.HKD,
    exchange_rate=StdMoneyAmount.from_true_value(float_money=7.80),
)
eur_exchange_rate = ExchangeRateInUsd(
    currency=Currency.EUR,
    exchange_rate=StdMoneyAmount.from_true_value(float_money=0.90),
)
exchange_rates = [jpy_exchange_rate, hkd_exchange_rate, eur_exchange_rate]


@pytest.mark.parametrize(
    "exchange_rates, money, currency, expect_amount",
    [
        (
            exchange_rates,
            Money(
                currency=Currency.JPY,
                amount=StdMoneyAmount.from_true_value(float_money=300),
            ),
            Currency.EUR,
            1.85,
        ),
        (
            exchange_rates,
            Money(currency=Currency.HKD, float_money=888),
            Currency.EUR,
            102.94,
        ),
    ],
)
def test_Money_exchange_to(exchange_rates, money, currency, expect_amount):
    changed = money.exchange_to(exchange_rates=exchange_rates, currency=currency)
    assert changed.currency == currency
    assert changed.true_value == pytest.approx(expect_amount, 0.01)


def test_Money_exchange_to_test2():
    exchange_rates = [
        ExchangeRateInUsd(
            currency=Currency.USD,
            exchange_rate=StdMoneyAmount.from_true_value(float_money=1),
        ),
        ExchangeRateInUsd(
            currency=Currency.JPY,
            exchange_rate=StdMoneyAmount.from_true_value(float_money=150),
        ),
        ExchangeRateInUsd(
            currency=Currency.GBP,
            exchange_rate=StdMoneyAmount.from_true_value(float_money=0.8),
        ),
        ExchangeRateInUsd(
            currency=Currency.SEK,
            exchange_rate=StdMoneyAmount.from_true_value(float_money=10.5),
        ),
        ExchangeRateInUsd(
            currency=Currency.PLN,
            exchange_rate=StdMoneyAmount.from_true_value(float_money=4.1),
        ),
        ExchangeRateInUsd(
            currency=Currency.EUR,
            exchange_rate=StdMoneyAmount.from_true_value(float_money=0.9),
        ),
        ExchangeRateInUsd(
            currency=Currency.CAD,
            exchange_rate=StdMoneyAmount.from_true_value(float_money=1.3),
        ),
        ExchangeRateInUsd(
            currency=Currency.MXN,
            exchange_rate=StdMoneyAmount.from_true_value(float_money=17.2),
        ),
        ExchangeRateInUsd(
            currency=Currency.CNY,
            exchange_rate=StdMoneyAmount.from_true_value(float_money=7),
        ),
    ]
    cad = Money(currency=Currency.CAD, float_money=1)
    cny = Money(currency=Currency.CNY, float_money=1)
    cad.exchange_to(exchange_rates=exchange_rates, currency=Currency.CNY)
    cny.exchange_to(exchange_rates=exchange_rates, currency=Currency.CAD)


# Test AmazonMarketplace currency property
def test_AmazonMarketplace_currency_test2():
    assert AmazonMarketplace.US.currency == Currency.USD
    assert AmazonMarketplace.JP.currency == Currency.JPY
    assert AmazonMarketplace.UK.currency == Currency.GBP
    assert AmazonMarketplace.SE.currency == Currency.SEK
    assert AmazonMarketplace.PL.currency == Currency.PLN
    assert AmazonMarketplace.DE.currency == Currency.EUR
    assert AmazonMarketplace.ES.currency == Currency.EUR
    assert AmazonMarketplace.FR.currency == Currency.EUR
    assert AmazonMarketplace.IT.currency == Currency.EUR
    assert AmazonMarketplace.BE.currency == Currency.EUR
    assert AmazonMarketplace.NL.currency == Currency.EUR
    assert AmazonMarketplace.CA.currency == Currency.CAD
    assert AmazonMarketplace.MX.currency == Currency.MXN

    # Test invalid marketplace
    class MockMarketplace:
        def __init__(self, value):
            self.value = value

    mock = MockMarketplace("INVALID")
    with pytest.raises(keble_exceptions.UnhandledScenarioOrCase):
        _ = AmazonMarketplace.currency.__get__(mock, None)  # type: ignore


# Test AmazonMarketplace from_marketplace method
def test_AmazonMarketplace_from_marketplace():
    # Test with AmazonMarketplace instance
    assert (
        AmazonMarketplace.from_marketplace(AmazonMarketplace.US) == AmazonMarketplace.US
    )

    # Test with Marketplace instance
    assert AmazonMarketplace.from_marketplace(Marketplace.US) == AmazonMarketplace.US

    # Test with AmazonMarketplaceEU instance
    assert (
        AmazonMarketplace.from_marketplace(AmazonMarketplaceEU.DE)
        == AmazonMarketplace.DE
    )

    # Test with string
    assert AmazonMarketplace.from_marketplace("US") == AmazonMarketplace.US

    # Test invalid string
    with pytest.raises(keble_exceptions.ServerSideInvalidParams):
        AmazonMarketplace.from_marketplace("INVALID")

    # Test invalid type
    with pytest.raises(keble_exceptions.ServerSideInvalidParams):
        AmazonMarketplace.from_marketplace(123)  # type: ignore


# Test AmazonMarketplace domain property
def test_AmazonMarketplace_domain():
    assert AmazonMarketplace.US.domain == "amazon.com"
    assert AmazonMarketplace.UK.domain == "amazon.co.uk"
    assert AmazonMarketplace.DE.domain == "amazon.de"
    assert AmazonMarketplace.JP.domain == "amazon.co.jp"


@pytest.mark.parametrize(
    "url, expected_asin, expected_marketplace",
    [
        ("https://www.amazon.com/dp/B08N5WRWNW", "B08N5WRWNW", AmazonMarketplace.US),
        ("https://amazon.com/dp/B08N5WRWNW", "B08N5WRWNW", AmazonMarketplace.US),
        (
            "https://amazon.co.uk/gp/product/B012345678?th=1",
            "B012345678",
            AmazonMarketplace.UK,
        ),
        (
            "https://smile.amazon.de/exec/obidos/ASIN/B000123456/ref=nosim",
            "B000123456",
            AmazonMarketplace.DE,
        ),
        (
            "https://amazon.com.be/gp/aw/d/B0ABC12345?psc=1",
            "B0ABC12345",
            AmazonMarketplace.BE,
        ),
        (
            "https://www.amazon.co.jp/-/en/product/B0A1B2C3D4",
            "B0A1B2C3D4",
            AmazonMarketplace.JP,
        ),
        (
            "https://WWW.AMAZON.COM/GP/PRODUCT/b08n5wrwnw",
            "B08N5WRWNW",
            AmazonMarketplace.US,
        ),
        (
            "https://www.amazon.com.mx/gp/aw/dp/b0abc12345",
            "B0ABC12345",
            AmazonMarketplace.MX,
        ),
        (
            "https://www.amazon.com/o/asin/b012345678",
            "B012345678",
            AmazonMarketplace.US,
        ),
        (
            "https://www.amazon.de/gp/customer-reviews/B000123456",
            "B000123456",
            AmazonMarketplace.DE,
        ),
        (
            "https://www.amazon.fr/gp/product?ASIN=b00test123&th=1",
            "B00TEST123",
            AmazonMarketplace.FR,
        ),
    ],
)
def test_AmazonMarketplace_parse_amazon_product_url_valid(
    url: str,
    expected_asin: str,
    expected_marketplace: AmazonMarketplace,
):
    parsed_result = AmazonMarketplace.parse_amazon_product_url(url)

    assert parsed_result is not None
    assert parsed_result.asin == expected_asin
    assert parsed_result.marketplace == expected_marketplace


@pytest.mark.parametrize(
    "url",
    [
        "https://www.amazon.com/dp/B08N5WRW",  # short ASIN
        "https://www.amazon.in/dp/B08N5WRWNW",  # unsupported marketplace
        "https://www.google.com/dp/B08N5WRWNW",  # non-amazon domain
        "https://www.amazon.com/s?k=mouse",  # not a product URL pattern
    ],
)
def test_AmazonMarketplace_parse_amazon_product_url_invalid(url: str):
    assert AmazonMarketplace.parse_amazon_product_url(url) is None


# Test Marketplace currency property
def test_Marketplace_currency():
    # Test North American marketplaces
    assert Marketplace.US.currency == Currency.USD
    assert Marketplace.CA.currency == Currency.CAD
    assert Marketplace.MX.currency == Currency.MXN
    
    # Test European marketplaces
    assert Marketplace.UK.currency == Currency.GBP
    assert Marketplace.DE.currency == Currency.EUR
    assert Marketplace.FR.currency == Currency.EUR
    assert Marketplace.IT.currency == Currency.EUR
    assert Marketplace.ES.currency == Currency.EUR
    assert Marketplace.NL.currency == Currency.EUR
    assert Marketplace.BE.currency == Currency.EUR
    assert Marketplace.SE.currency == Currency.SEK
    assert Marketplace.PL.currency == Currency.PLN
    
    # Test Asian marketplaces
    assert Marketplace.JP.currency == Currency.JPY
    assert Marketplace.CN.currency == Currency.CNY
    assert Marketplace.IN.currency == Currency.INR
    assert Marketplace.SG.currency == Currency.SGD
    
    # Test Middle Eastern marketplaces
    assert Marketplace.AE.currency == Currency.AED
    assert Marketplace.SA.currency == Currency.SAR
    assert Marketplace.TR.currency == Currency.TRY
    assert Marketplace.EG.currency == Currency.EGP
    
    # Test other marketplaces
    assert Marketplace.BR.currency == Currency.BRL
    assert Marketplace.AU.currency == Currency.AUD


# Test Marketplace currency property with parametrize
@pytest.mark.parametrize(
    "marketplace, expected_currency",
    [
        (Marketplace.US, Currency.USD),
        (Marketplace.CA, Currency.CAD),
        (Marketplace.MX, Currency.MXN),
        (Marketplace.UK, Currency.GBP),
        (Marketplace.DE, Currency.EUR),
        (Marketplace.FR, Currency.EUR),
        (Marketplace.IT, Currency.EUR),
        (Marketplace.ES, Currency.EUR),
        (Marketplace.NL, Currency.EUR),
        (Marketplace.BE, Currency.EUR),
        (Marketplace.SE, Currency.SEK),
        (Marketplace.PL, Currency.PLN),
        (Marketplace.JP, Currency.JPY),
        (Marketplace.CN, Currency.CNY),
        (Marketplace.IN, Currency.INR),
        (Marketplace.SG, Currency.SGD),
        (Marketplace.AE, Currency.AED),
        (Marketplace.SA, Currency.SAR),
        (Marketplace.TR, Currency.TRY),
        (Marketplace.EG, Currency.EGP),
        (Marketplace.BR, Currency.BRL),
        (Marketplace.AU, Currency.AUD),
    ],
)
def test_Marketplace_currency_parametrized(marketplace, expected_currency):
    assert marketplace.currency == expected_currency


# Test AmazonMarketplaceEU currency property
def test_AmazonMarketplaceEU_currency():
    assert AmazonMarketplaceEU.UK.currency == Currency.GBP
    assert AmazonMarketplaceEU.SE.currency == Currency.SEK
    assert AmazonMarketplaceEU.PL.currency == Currency.PLN
    assert AmazonMarketplaceEU.DE.currency == Currency.EUR
    assert AmazonMarketplaceEU.ES.currency == Currency.EUR
    assert AmazonMarketplaceEU.FR.currency == Currency.EUR
    assert AmazonMarketplaceEU.IT.currency == Currency.EUR
    assert AmazonMarketplaceEU.BE.currency == Currency.EUR


# Test AmazonMarketplaceEU from_marketplace method
def test_AmazonMarketplaceEU_from_marketplace():
    # Test with valid AmazonMarketplace
    assert (
        AmazonMarketplaceEU.from_marketplace(AmazonMarketplace.DE)
        == AmazonMarketplaceEU.DE
    )

    # Test with valid Marketplace
    assert (
        AmazonMarketplaceEU.from_marketplace(Marketplace.DE) == AmazonMarketplaceEU.DE
    )

    # Test with None
    with pytest.raises(keble_exceptions.ServerSideInvalidParams):
        AmazonMarketplaceEU.from_marketplace(None)

    # Test with non-EU marketplace
    with pytest.raises(keble_exceptions.ServerSideInvalidParams):
        AmazonMarketplaceEU.from_marketplace(AmazonMarketplace.US)


# Test AmazonMarketplaceEU is_eu_marketplace method
def test_AmazonMarketplaceEU_is_eu_marketplace():
    assert AmazonMarketplaceEU.is_eu_marketplace(AmazonMarketplaceEU.DE) is True
    assert AmazonMarketplaceEU.is_eu_marketplace(AmazonMarketplace.DE) is True
    assert AmazonMarketplaceEU.is_eu_marketplace(Marketplace.DE) is True
    assert AmazonMarketplaceEU.is_eu_marketplace("DE") is True
    assert AmazonMarketplaceEU.is_eu_marketplace(AmazonMarketplace.US) is False
    assert AmazonMarketplaceEU.is_eu_marketplace(None) is False


# todo
#   TRY TO CRUD in this strapi postgres table
#   Use typing
#   TestModel inherited the StrapiSqlObjectBase


# def test_strapi_sql_object_base():
#     class TestModelEnum(str, PyEnum):
#         enum1 = "enum1"
#         enum2 = "enum2"
#         enum3 = "enum3"

#     class TestModelCreate(StrapiBase):
#         # TEXT column (no length limit)
#         text: Optional[str] = Field(
#             default=None,
#             sa_column=Column("text", Text, nullable=True),
#         )

#         # BOOLEAN column
#         boolean: Optional[bool] = Field(
#             default=None,
#             sa_column=Column("boolean", Boolean, nullable=True),
#         )

#         # JSONB column (Postgres) / JSON for other dialects
#         json_data: Optional[Dict] = Field(
#             default=None,
#             sa_column=Column("json", JSON, nullable=True),
#         )

#         # DECIMAL / NUMERIC column with precision (e.g. 10 total digits, 2 after the decimal point)
#         decimal: Optional[Decimal] = Field(
#             default=None,
#             sa_column=Column("decimal", Numeric(10, 2), nullable=True),
#         )
#         enum: Optional[TestModelEnum] = Field(
#             default=None, sa_column=Column("enum", Enum(TestModelEnum), nullable=True)
#         )
#         # time → SQL TIME (no date)
#         time_field: Optional[time] = Field(
#             default=None, sa_column=Column("time", Time, nullable=True)
#         )

#         # date → SQL DATE (no time)
#         date_field: Optional[date] = Field(
#             default=None, sa_column=Column("date", Date, nullable=True)
#         )

#         # datetime → SQL TIMESTAMP WITHOUT TIME ZONE
#         datetime_field: Optional[datetime] = Field(
#             default=None,
#             sa_column=Column("datetime", DateTime(timezone=False), nullable=True),
#         )

#     class TestModelObject(StrapiSqlObjectBase, TestModelCreate, table=True):
#         __tablename__: str = settings.STRAPI_POSTGRES_TABLE

#     # Setup connection to the real Postgres database
#     from sqlalchemy import create_engine as sa_create_engine
#     from sqlmodel import Session

#     # Use the actual Postgres connection instead of in-memory SQLite
#     postgres_url = settings.STRAPI_POSTGRES_URI
#     assert postgres_url is not None
#     postgres_engine = sa_create_engine(str(postgres_url))

#     # Get the existing table name from settings
#     table_name = settings.STRAPI_POSTGRES_TABLE

#     # Setup test data
#     test_json = {"key": "value", "nested": {"test": True}}
#     test_date = date(2023, 1, 1)
#     test_time = time(12, 30, 0)
#     test_datetime = datetime(2023, 1, 1, 12, 30, 0, tzinfo=timezone.utc)

#     # Generate a unique document ID for this test run to avoid conflicts
#     import uuid

#     test_document_id = f"test-doc-{uuid.uuid4()}"

#     # Create model object - completely omit the id parameter
#     test_obj = TestModelObject(
#         # Don't specify id at all - let it be auto-assigned
#         document_id=test_document_id,
#         text="Test text content",
#         boolean=True,
#         json_data=test_json,
#         decimal=Decimal("123.45"),
#         enum=TestModelEnum.enum2,
#         time_field=test_time,
#         date_field=test_date,
#         datetime_field=test_datetime,
#         created_at=test_datetime,
#         updated_at=test_datetime,
#         published_at=test_datetime,
#         locale="en",
#         created_by_id=None,
#         updated_by_id=None,
#     )

#     # Don't create tables - use existing ones
#     with Session(postgres_engine) as session:
#         # Test Create
#         session.add(test_obj)
#         session.commit()
#         session.refresh(test_obj)

#         # Get the assigned ID for further operations
#         created_id = test_obj.id

#         # Test Read
#         db_obj = session.exec(
#             select(TestModelObject).where(
#                 TestModelObject.document_id == test_document_id
#             )
#         ).first()

#         assert db_obj is not None
#         assert db_obj.document_id == test_document_id
#         assert db_obj.text == "Test text content"
#         assert db_obj.boolean is True
#         assert db_obj.json_data == test_json

#         # Test Update
#         db_obj.text = "Updated text"
#         db_obj.boolean = False
#         session.add(db_obj)
#         session.commit()
#         session.refresh(db_obj)

#         updated_obj = session.exec(
#             select(TestModelObject).where(
#                 TestModelObject.document_id == test_document_id
#             )
#         ).first()

#         assert updated_obj is not None
#         assert updated_obj.text == "Updated text"
#         assert updated_obj.boolean is False

#         # Test partial update using model_dump with exclude_unset
#         update_data = TestModelObject(id=created_id, text="Partially updated")
#         update_dict = update_data.model_dump(exclude_unset=True)
#         db_obj.sqlmodel_update(update_dict)
#         session.add(db_obj)
#         session.commit()
#         session.refresh(db_obj)

#         # Verify partial update
#         assert db_obj.text == "Partially updated"
#         assert db_obj.boolean is False  # Should not have changed

#         # Print the table data for verification
#         print(f"Created record in {table_name} with ID: {created_id}")

#         # Test Delete - clean up after our test to not pollute the database
#         session.delete(db_obj)
#         session.commit()

#         # Verify deletion
#         deleted_obj = session.exec(
#             select(TestModelObject).where(
#                 TestModelObject.document_id == test_document_id
#             )
#         ).first()

#         assert deleted_obj is None


def test_NaturalLanguage_creation():
    # Create a NaturalLanguage instance
    nl = NaturalLanguage(english="Hello, world!", simplified_chinese="你好，世界！")

    # Verify the properties are set correctly
    assert nl.english == "Hello, world!"
    assert nl.simplified_chinese == "你好，世界！"


def test_NaturalLanguage_get_text_method():
    # Create a NaturalLanguage instance
    nl = NaturalLanguage(english="Hello, world!", simplified_chinese="你好，世界！")

    # Test getting text in different languages
    assert nl.get_text(Language.ENGLISH) == "Hello, world!"
    assert nl.get_text(Language.SIMPLIFIED_CHINESE) == "你好，世界！"

    # Test default behavior (should return English for unsupported languages)
    assert nl.get_text(Language.GERMAN) == "Hello, world!"
    assert nl.get_text(Language.JAPANESE) == "Hello, world!"


def test_NaturalLanguage_serialization():
    # Create a NaturalLanguage instance
    nl = NaturalLanguage(english="Hello, world!", simplified_chinese="你好，世界！")

    # Test serialization to dict
    data = nl.model_dump()
    assert data["english"] == "Hello, world!"
    assert data["simplified_chinese"] == "你好，世界！"

    # Test serialization to JSON
    json_data = nl.model_dump_json()
    assert '"english":"Hello, world!"' in json_data
    assert '"simplified_chinese":"你好，世界！"' in json_data
