from decimal import Decimal

from django.db import IntegrityError, transaction
from django.test import TestCase

from apps.payments.models import (
    Deposit,
    GatewayCallbackLog,
    PaymentAddress,
    WithdrawalRequest,
)
from apps.transactions.models import Transaction
from apps.users.models import User


class PaymentsModelTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            telegram_user_id=12345,
            first_name="Alice",
            username="alice",
        )

    def test_payment_address_defaults_and_unique_address(self):
        address = PaymentAddress.objects.create(
            user=self.user,
            blockchain="tron",
            asset="USDT",
            address="TRON_ADDR_1",
            metadata={"label": "primary"},
        )

        self.assertEqual(address.status, PaymentAddress.STATUS_ACTIVE)
        self.assertEqual(address.asset, "USDT")
        self.assertIn("tron", str(address))
        self.assertEqual(address.metadata["label"], "primary")

        with self.assertRaises(IntegrityError):
            with transaction.atomic():
                PaymentAddress.objects.create(
                    user=self.user,
                    blockchain="tron",
                    asset="USDT",
                    address="TRON_ADDR_1",
                )

    def test_deposit_unique_per_chain_and_defaults(self):
        address = PaymentAddress.objects.create(
            user=self.user,
            blockchain="tron",
            asset="USDT",
            address="TRON_ADDR_2",
        )

        deposit = Deposit.objects.create(
            user=self.user,
            address=address,
            blockchain="tron",
            asset="USDT",
            amount=Decimal("10.00000000"),
            transaction_hash="tx-abc",
            raw_payload={"from": "sender"},
        )

        self.assertEqual(deposit.status, Deposit.STATUS_PENDING)
        self.assertEqual(deposit.confirmations, 0)
        self.assertEqual(deposit.raw_payload["from"], "sender")
        self.assertIn("Deposit", str(deposit))

        with self.assertRaises(IntegrityError):
            with transaction.atomic():
                Deposit.objects.create(
                    user=self.user,
                    address=address,
                    blockchain="tron",
                    asset="USDT",
                    amount=Decimal("5.00"),
                    transaction_hash="tx-abc",
                )

        # Same hash on a different blockchain is allowed.
        other_address = PaymentAddress.objects.create(
            user=self.user,
            blockchain="bnb",
            asset="USDT",
            address="BSC_ADDR_1",
        )
        deposit_other_chain = Deposit.objects.create(
            user=self.user,
            address=other_address,
            blockchain="bnb",
            asset="USDT",
            amount=Decimal("3.00"),
            transaction_hash="tx-abc",
        )
        self.assertEqual(deposit_other_chain.blockchain, "bnb")

    def test_withdrawal_defaults_and_links_transaction(self):
        tx = Transaction.objects.create(
            user=self.user,
            transaction_type=Transaction.TYPE_WITHDRAWAL,
            amount=Decimal("50.00000000"),
            status=Transaction.STATUS_PENDING,
            balance_before=Decimal("100.00000000"),
            balance_after=Decimal("50.00000000"),
            blockchain="tron",
        )

        withdrawal = WithdrawalRequest.objects.create(
            user=self.user,
            blockchain="tron",
            asset="USDT",
            amount=Decimal("50.00000000"),
            destination_address="TRON_DEST_1",
            transaction=tx,
            metadata={"reason": "payout"},
        )

        self.assertEqual(withdrawal.status, WithdrawalRequest.STATUS_REQUESTED)
        self.assertEqual(withdrawal.transaction, tx)
        self.assertEqual(withdrawal.metadata["reason"], "payout")
        self.assertIn("Withdrawal", str(withdrawal))

    def test_callback_log_links_and_defaults(self):
        address = PaymentAddress.objects.create(
            user=self.user,
            blockchain="tron",
            asset="USDT",
            address="TRON_ADDR_3",
        )
        deposit = Deposit.objects.create(
            user=self.user,
            address=address,
            blockchain="tron",
            asset="USDT",
            amount=Decimal("1.00"),
            transaction_hash="tx-callback",
        )

        log = GatewayCallbackLog.objects.create(
            event_type="deposit.detected",
            related_deposit=deposit,
            payload={"tx": "tx-callback"},
            headers={"X-Signature": "abc"},
        )

        self.assertEqual(log.status, GatewayCallbackLog.STATUS_PENDING)
        self.assertEqual(log.related_deposit, deposit)
        self.assertIsNone(log.related_withdrawal)
        self.assertEqual(log.payload["tx"], "tx-callback")
        self.assertEqual(log.headers["X-Signature"], "abc")
        self.assertIn("deposit.detected", str(log))
