"""Dependency-free checks for the pure logic: validators, pricing, unique amounts.

    python scripts/selftest.py

Needs no database and no network. Complements scripts/verify.sh, which exercises
the running API.
"""
import asyncio
import sys
from decimal import Decimal
from pathlib import Path

sys.path.append(str(Path(__file__).resolve().parents[1]))

from app.core.exceptions import ValidationError  # noqa: E402
from app.flows import get_flow  # noqa: E402
from app.flows.base import FlowContext  # noqa: E402
from app.services.coupon_service import CouponService  # noqa: E402
from app.utils.helpers import crypto, money, order_number, slugify  # noqa: E402
from app.utils.validators import (  # noqa: E402
    clean_amount,
    clean_channel,
    clean_int,
    clean_username,
)

passed = 0
failed: list[str] = []


def check(name: str, condition: bool) -> None:
    global passed
    if condition:
        passed += 1
    else:
        failed.append(name)


def raises(name: str, func, *args) -> None:
    try:
        func(*args)
    except ValidationError:
        check(name, True)
        return
    check(name, False)


class FakeSettings:
    def __init__(self, values: dict[str, str]) -> None:
        self.values = values

    async def get_decimal(self, key: str, default: str = "0") -> Decimal:
        return Decimal(self.values.get(key, default))


class FakeProduct:
    def __init__(self, price: str, data: dict, currency: str = "IRT") -> None:
        self.effective_price = Decimal(price)
        self.data = data
        self.currency = currency


class FakeCoupon:
    def __init__(self, type_: str, value: str, max_discount: str | None = None) -> None:
        self.type = type_
        self.value = Decimal(value)
        self.max_discount = Decimal(max_discount) if max_discount else None


def test_validators() -> None:
    check("username plain", clean_username("Ali_Reza") == "Ali_Reza")
    check("username @", clean_username("@Ali_Reza") == "Ali_Reza")
    check("username link", clean_username("https://t.me/Ali_Reza") == "Ali_Reza")
    check("channel link", clean_channel("t.me/my_channel") == "my_channel")
    raises("username too short", clean_username, "ab")
    raises("username bad chars", clean_username, "9bad-name")
    raises("channel invite link", clean_channel, "https://t.me/+abcdef")
    check("amount comma", clean_amount("1,500") == Decimal("1500"))
    raises("amount zero", clean_amount, "0")
    raises("amount text", clean_amount, "abc")
    check("int ok", clean_int("25", 1, 100) == 25)
    raises("int below min", clean_int, "0", 1, 100)
    raises("int above max", clean_int, "500", 1, 100)


def test_helpers() -> None:
    check("money rounds", money("1000.456") == Decimal("1000.46"))
    check("crypto truncates", crypto("100.1234567") == Decimal("100.123456"))
    check("slugify ascii", slugify("Premium 3 Months") == "premium-3-months")
    check("slugify fallback", len(slugify("پرمیوم")) > 0)
    check("order numbers unique", len({order_number() for _ in range(200)}) == 200)


async def test_pricing() -> None:
    ctx = FlowContext(FakeSettings({"ton_rate_irt": "60000", "gift_premium_comment_price": "10000"}))

    premium = FakeProduct("850000", {"months": 3})
    price = await get_flow("premium").calculate_price(ctx, premium, {"username": "someone"})
    check("premium price", price == Decimal("850000.00"))

    boost = FakeProduct("45000", {"per_unit": True, "max_quantity": 1000})
    flow = get_flow("boost")
    inputs = flow.validate_all(boost, {"quantity": "10", "channel": "@my_channel"})
    check("boost quantity parsed", inputs["quantity"] == 10)
    check("boost channel cleaned", inputs["channel"] == "my_channel")
    check("boost price", await flow.calculate_price(ctx, boost, inputs) == Decimal("450000.00"))

    ton = FakeProduct("0", {})
    ton_price = await get_flow("ton").calculate_price(
        ctx, ton, {"amount": "2.5", "wallet": "x" * 48}
    )
    check("ton price from rate", ton_price == Decimal("150000.00"))

    gift = FakeProduct("740000", {"stars": 500, "allow_hide": True, "allow_comment": True})
    gift_flow = get_flow("gift")
    normal = await gift_flow.calculate_price(ctx, gift, {"username": "someone"})
    premium_comment = await gift_flow.calculate_price(
        ctx, gift, {"username": "someone", "comment_type": "premium", "comment": "hi"}
    )
    check("gift base price", normal == Decimal("740000.00"))
    check("gift premium comment adds", premium_comment - normal == Decimal("10000.00"))

    steps = {step.key for step in gift_flow.get_steps(gift)}
    check("gift asks username only", steps == {"username"})
    check("boost asks quantity", "quantity" in {s.key for s in flow.get_steps(boost)})

    try:
        get_flow("premium").validate_all(premium, {})
        check("missing input rejected", False)
    except ValidationError:
        check("missing input rejected", True)


def test_coupons() -> None:
    service = CouponService.__new__(CouponService)
    percent = FakeCoupon("percent", "20")
    capped = FakeCoupon("percent", "50", "50000")
    fixed = FakeCoupon("fixed", "30000")

    check("percent discount", service.calculate_discount(percent, Decimal("100000")) == Decimal("20000.00"))
    check("capped discount", service.calculate_discount(capped, Decimal("400000")) == Decimal("50000.00"))
    check("fixed discount", service.calculate_discount(fixed, Decimal("100000")) == Decimal("30000.00"))
    check(
        "discount never exceeds total",
        service.calculate_discount(fixed, Decimal("10000")) == Decimal("10000.00"),
    )


async def test_unique_amounts() -> None:
    from app.services.payment_service import PaymentService

    class FakePayments:
        def __init__(self) -> None:
            self.taken: set[Decimal] = set()

        async def payable_amount_taken(self, amount: Decimal) -> bool:
            return amount in self.taken

    service = PaymentService.__new__(PaymentService)
    service.payments = FakePayments()  # type: ignore[assignment]

    base = Decimal("100")
    amounts = []
    for _ in range(300):
        amount = await service._unique_payable_amount(base)
        service.payments.taken.add(amount)  # type: ignore[attr-defined]
        amounts.append(amount)

    check("amounts unique", len(set(amounts)) == len(amounts))
    check("amounts above original", all(amount > base for amount in amounts))
    check("amounts close to original", all(amount - base < Decimal("0.02") for amount in amounts))
    check("six decimals", all(amount.as_tuple().exponent == -6 for amount in amounts))


async def main() -> int:
    test_validators()
    test_helpers()
    await test_pricing()
    test_coupons()
    await test_unique_amounts()

    print(f"passed: {passed}   failed: {len(failed)}")
    for name in failed:
        print(f"  FAIL  {name}")
    return 1 if failed else 0


if __name__ == "__main__":
    raise SystemExit(asyncio.run(main()))
