"""Tests for withdrawal request and cancellation endpoints."""

from decimal import Decimal
from django.test import TestCase
from rest_framework.test import APIClient
from rest_framework import status

from apps.users.models import User
from apps.payments.models import WithdrawalRequest
from apps.transactions.models import Transaction


class WithdrawalRequestTestCase(TestCase):
    """Test withdrawal request creation and validation."""

    def setUp(self):
        """Set up test data."""
        self.client = APIClient()
        self.user = User.objects.create_user(
            telegram_user_id=111111,
            first_name="Test User",
        )
        self.user.credit_balance = Decimal('1000.00000000')
        self.user.save()
        self.client.force_authenticate(user=self.user)

    def test_create_withdrawal_tron_success(self):
        """Test successful withdrawal creation on Tron network."""
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'tron',
            'amount': 100.00,
            'destination_address': 'T1234567890abcdef',
        })
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['blockchain'], 'tron')
        self.assertEqual(Decimal(str(response.data['amount'])), Decimal('100.00000000'))
        self.assertEqual(response.data['status'], 'requested')
        
        # Verify balance was deducted (amount + 1% + $3)
        self.user.refresh_from_db()
        expected_deduction = Decimal('100.00') + Decimal('1.00') + Decimal('3.00')  # 104
        expected_balance = Decimal('1000.00') - expected_deduction
        self.assertEqual(self.user.credit_balance, expected_balance)

    def test_create_withdrawal_bnb_success(self):
        """Test successful withdrawal creation on BNB network."""
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'bnb',
            'amount': 50.00,
            'destination_address': '0x1234567890abcdef',
        })
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['blockchain'], 'bnb')
        
        # Fee: 1% of 50 = 0.50, fixed = 2, total = 52.50
        self.user.refresh_from_db()
        expected_balance = Decimal('1000.00') - Decimal('52.50')
        self.assertEqual(self.user.credit_balance, expected_balance)

    def test_create_withdrawal_below_minimum(self):
        """Test withdrawal below minimum amount is rejected."""
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'tron',
            'amount': 9.99,
            'destination_address': 'T1234567890abcdef',
        })
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('amount', response.data)

    def test_create_withdrawal_insufficient_balance(self):
        """Test withdrawal exceeding balance is rejected."""
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'tron',
            'amount': 1000.00,  # Would need 1004 total with fees
            'destination_address': 'T1234567890abcdef',
        })
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('Insufficient balance', str(response.data))

    def test_create_withdrawal_exactly_at_balance_limit(self):
        """Test withdrawal at exact balance limit (accounting for fees)."""
        # Balance: 1000, try to withdraw amount where amount + fees = 1000
        # amount + (amount * 0.01) + 3 = 1000
        # amount * 1.01 + 3 = 1000
        # amount * 1.01 = 997
        # amount = 987.128...
        
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'tron',
            'amount': 987.12,
            'destination_address': 'T1234567890abcdef',
        })
        
        # Total: 987.12 + 9.8712 + 3 = 999.9912 (should succeed)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    def test_create_withdrawal_invalid_blockchain(self):
        """Test withdrawal with unsupported blockchain is rejected."""
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'ethereum',
            'amount': 100.00,
            'destination_address': '0x1234567890abcdef',
        })
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_create_withdrawal_case_insensitive_blockchain(self):
        """Test blockchain parameter is normalized to lowercase."""
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'TRON',
            'amount': 100.00,
            'destination_address': 'T1234567890abcdef',
        })
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['blockchain'], 'tron')

    def test_create_withdrawal_creates_transaction_record(self):
        """Test withdrawal creates corresponding transaction record."""
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'tron',
            'amount': 100.00,
            'destination_address': 'T1234567890abcdef',
        })
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # Verify transaction was created
        tx = Transaction.objects.filter(
            user=self.user,
            transaction_type=Transaction.TYPE_WITHDRAWAL,
            status=Transaction.STATUS_PENDING,
        ).first()
        
        self.assertIsNotNone(tx)
        self.assertEqual(tx.amount, Decimal('104.00000000'))  # 100 + 1 + 3

    def test_create_withdrawal_high_precision_amount(self):
        """Test withdrawal with 8-decimal precision."""
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'tron',
            'amount': '123.45678901',
            'destination_address': 'T1234567890abcdef',
        })
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        # Amount should be stored with 8 decimal precision
        self.assertEqual(
            Decimal(str(response.data['amount'])),
            Decimal('123.45678901')
        )

    def test_create_withdrawal_empty_address(self):
        """Test withdrawal with empty address is rejected."""
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'tron',
            'amount': 100.00,
            'destination_address': '',
        })
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_create_withdrawal_very_large_amount(self):
        """Test withdrawal with very large amount."""
        self.user.credit_balance = Decimal('1000000.00000000')
        self.user.save()
        
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'tron',
            'amount': '100000.00',
            'destination_address': 'T1234567890abcdef',
        })
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    def test_create_withdrawal_unauthenticated(self):
        """Test unauthenticated withdrawal request is rejected."""
        self.client.force_authenticate(user=None)
        response = self.client.post('/api/payments/withdrawals/', {
            'blockchain': 'tron',
            'amount': 100.00,
            'destination_address': 'T1234567890abcdef',
        })
        
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)


class WithdrawalCancellationTestCase(TestCase):
    """Test withdrawal cancellation functionality."""

    def setUp(self):
        """Set up test data."""
        self.client = APIClient()
        self.user = User.objects.create_user(
            telegram_user_id=111111,
            first_name="Test User",
        )
        self.user.credit_balance = Decimal('1000.00000000')
        self.user.save()
        self.client.force_authenticate(user=self.user)

    def test_cancel_requested_withdrawal(self):
        """Test cancelling a withdrawal in requested status."""
        # Create withdrawal
        withdrawal = WithdrawalRequest.objects.create(
            user=self.user,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('100.00000000'),
            destination_address='T1234567890abcdef',
            status=WithdrawalRequest.STATUS_REQUESTED,
            metadata={'total_deduction': '104.00000000'},
        )
        
        # Deduct balance
        self.user.credit_balance = Decimal('896.00000000')  # 1000 - 104
        self.user.save()
        
        # Cancel withdrawal
        response = self.client.delete(f'/api/payments/withdrawals/{withdrawal.id}/')
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # Verify withdrawal status updated
        withdrawal.refresh_from_db()
        self.assertEqual(withdrawal.status, WithdrawalRequest.STATUS_CANCELLED)
        
        # Verify balance refunded
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal('1000.00000000'))

    def test_cancel_processing_withdrawal(self):
        """Test cancelling a withdrawal in processing status is rejected."""
        withdrawal = WithdrawalRequest.objects.create(
            user=self.user,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('100.00000000'),
            destination_address='T1234567890abcdef',
            status=WithdrawalRequest.STATUS_PROCESSING,
            metadata={'total_deduction': '104.00000000'},
        )
        
        response = self.client.delete(f'/api/payments/withdrawals/{withdrawal.id}/')
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('Cannot cancel', str(response.data))

    def test_cancel_sent_withdrawal(self):
        """Test cancelling a sent withdrawal is rejected."""
        withdrawal = WithdrawalRequest.objects.create(
            user=self.user,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('100.00000000'),
            destination_address='T1234567890abcdef',
            status=WithdrawalRequest.STATUS_SENT,
            metadata={'total_deduction': '104.00000000'},
        )
        
        response = self.client.delete(f'/api/payments/withdrawals/{withdrawal.id}/')
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_cancel_already_cancelled_withdrawal(self):
        """Test cancelling an already cancelled withdrawal is rejected."""
        withdrawal = WithdrawalRequest.objects.create(
            user=self.user,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('100.00000000'),
            destination_address='T1234567890abcdef',
            status=WithdrawalRequest.STATUS_CANCELLED,
            metadata={'total_deduction': '104.00000000'},
        )
        
        response = self.client.delete(f'/api/payments/withdrawals/{withdrawal.id}/')
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_cancel_withdrawal_updates_transaction(self):
        """Test cancelling withdrawal updates linked transaction status."""
        # Create transaction
        tx = Transaction.objects.create(
            user=self.user,
            transaction_type=Transaction.TYPE_WITHDRAWAL,
            amount=Decimal('104.00000000'),
            status=Transaction.STATUS_PENDING,
            balance_before=Decimal('1000.00000000'),
            balance_after=Decimal('896.00000000'),
        )
        
        withdrawal = WithdrawalRequest.objects.create(
            user=self.user,
            transaction=tx,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('100.00000000'),
            destination_address='T1234567890abcdef',
            status=WithdrawalRequest.STATUS_REQUESTED,
            metadata={'total_deduction': '104.00000000'},
        )
        
        self.user.credit_balance = Decimal('896.00000000')
        self.user.save()
        
        # Cancel
        response = self.client.delete(f'/api/payments/withdrawals/{withdrawal.id}/')
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # Verify transaction status
        tx.refresh_from_db()
        self.assertEqual(tx.status, Transaction.STATUS_CANCELLED)

    def test_cancel_nonexistent_withdrawal(self):
        """Test cancelling a non-existent withdrawal returns 404."""
        response = self.client.delete('/api/payments/withdrawals/99999/')
        
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_cancel_other_user_withdrawal(self):
        """Test user cannot cancel another user's withdrawal."""
        other_user = User.objects.create_user(
            telegram_user_id=222222,
            first_name="Other User",
        )
        
        withdrawal = WithdrawalRequest.objects.create(
            user=other_user,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('100.00000000'),
            destination_address='T1234567890abcdef',
            status=WithdrawalRequest.STATUS_REQUESTED,
            metadata={'total_deduction': '104.00000000'},
        )
        
        response = self.client.delete(f'/api/payments/withdrawals/{withdrawal.id}/')
        
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)


class WithdrawalHistoryTestCase(TestCase):
    """Test withdrawal history retrieval."""

    def setUp(self):
        """Set up test data."""
        self.client = APIClient()
        self.user = User.objects.create_user(
            telegram_user_id=111111,
            first_name="Test User",
        )
        self.client.force_authenticate(user=self.user)

    def test_withdrawal_history_empty(self):
        """Test empty withdrawal history."""
        response = self.client.get('/api/payments/withdrawals/')
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data["results"]), 0)

    def test_withdrawal_history_with_withdrawals(self):
        """Test retrieving withdrawal history."""
        WithdrawalRequest.objects.create(
            user=self.user,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('100.00000000'),
            destination_address='T1234567890',
            status=WithdrawalRequest.STATUS_REQUESTED,
            metadata={'fee_amount': '4.00000000'},
        )
        WithdrawalRequest.objects.create(
            user=self.user,
            blockchain='bnb',
            asset='USDT',
            amount=Decimal('50.00000000'),
            destination_address='0x1234567890',
            status=WithdrawalRequest.STATUS_SENT,
            metadata={'fee_amount': '2.50000000'},
        )
        
        response = self.client.get('/api/payments/withdrawals/')
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data["results"]), 2)

    def test_withdrawal_history_includes_fees(self):
        """Test withdrawal history includes fee information."""
        WithdrawalRequest.objects.create(
            user=self.user,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('100.00000000'),
            destination_address='T1234567890',
            status=WithdrawalRequest.STATUS_REQUESTED,
            metadata={
                'fee_amount': '4.00000000',
                'total_deduction': '104.00000000',
            },
        )
        
        response = self.client.get('/api/payments/withdrawals/')
        
        self.assertEqual(Decimal(str(response.data["results"][0]['fee_amount'])), Decimal('4.00000000'))
        self.assertEqual(Decimal(str(response.data["results"][0]['net_amount'])), Decimal('100.00000000'))

    def test_withdrawal_history_filters_by_user(self):
        """Test user only sees their own withdrawals."""
        other_user = User.objects.create_user(
            telegram_user_id=222222,
            first_name="Other User",
        )
        
        WithdrawalRequest.objects.create(
            user=self.user,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('100.00000000'),
            destination_address='T1111111111',
            status=WithdrawalRequest.STATUS_REQUESTED,
        )
        WithdrawalRequest.objects.create(
            user=other_user,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('200.00000000'),
            destination_address='T2222222222',
            status=WithdrawalRequest.STATUS_REQUESTED,
        )
        
        response = self.client.get('/api/payments/withdrawals/')
        
        self.assertEqual(len(response.data["results"]), 1)
        self.assertEqual(response.data["results"][0]['destination_address'], 'T1111111111')

    def test_withdrawal_history_ordering(self):
        """Test withdrawals are ordered by requested_at (latest first)."""
        from django.utils import timezone
        from datetime import timedelta
        
        old_withdrawal = WithdrawalRequest.objects.create(
            user=self.user,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('100.00000000'),
            destination_address='T_old',
            status=WithdrawalRequest.STATUS_SENT,
        )
        old_withdrawal.requested_at = timezone.now() - timedelta(days=5)
        old_withdrawal.save()
        
        new_withdrawal = WithdrawalRequest.objects.create(
            user=self.user,
            blockchain='tron',
            asset='USDT',
            amount=Decimal('50.00000000'),
            destination_address='T_new',
            status=WithdrawalRequest.STATUS_REQUESTED,
        )
        
        response = self.client.get('/api/payments/withdrawals/')
        
        # Latest first
        self.assertEqual(response.data["results"][0]['destination_address'], 'T_new')
        self.assertEqual(response.data["results"][1]['destination_address'], 'T_old')

