"""Unit tests for wallet-utils integration in investment operations."""

from __future__ import annotations

from decimal import Decimal

from django.test import TestCase, TransactionTestCase
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient, APITestCase

from django_wallet_utils.exceptions import InsufficientBalanceError
from django_wallet_utils.models import WalletTransaction

from apps.investments.models import Investment
from apps.transactions.models import Transaction
from apps.users.models import User
from utils.wallet import MAKE_INVESTMENT


class InvestmentWalletIntegrationTests(APITestCase):
    """Standard test cases for wallet-utils integration in investments."""

    def setUp(self) -> None:
        """Set up test user and client."""
        self.user = User.objects.create_user(
            telegram_user_id=1001,
            first_name="TestUser",
            username="testuser",
        )
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)

    def test_successful_investment_deducts_balance(self):
        """Standard: Investment successfully deducts user balance."""
        initial_balance = Decimal("1000.00000000")
        investment_amount = Decimal("100.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.user.refresh_from_db()
        expected_balance = initial_balance - investment_amount
        self.assertEqual(self.user.credit_balance, expected_balance)

    def test_wallet_transaction_is_created(self):
        """Standard: Wallet transaction record is created via wallet-utils."""
        initial_balance = Decimal("500.00000000")
        investment_amount = Decimal("150.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # Check wallet transaction was created
        wallet_txns = WalletTransaction.objects.filter(
            uid=self.user.id,
            wtype="credit_balance",
            trans_type=MAKE_INVESTMENT,
        )
        self.assertEqual(wallet_txns.count(), 1)
        
        wallet_txn = wallet_txns.first()
        self.assertEqual(wallet_txn.type, "d")  # Debit
        self.assertEqual(wallet_txn.amount, investment_amount)
        self.assertEqual(wallet_txn.iid, self.user.id)
        self.assertIn("Investment in tier", wallet_txn.descr)

    def test_app_transaction_record_is_created(self):
        """Standard: App Transaction record is created for audit trail."""
        initial_balance = Decimal("200.00000000")
        investment_amount = Decimal("50.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # Check app transaction was created
        app_txns = Transaction.objects.filter(
            user=self.user,
            transaction_type=Transaction.TYPE_INVESTMENT,
        )
        self.assertEqual(app_txns.count(), 1)
        
        app_txn = app_txns.first()
        self.assertEqual(app_txn.amount, investment_amount)
        self.assertEqual(app_txn.status, Transaction.STATUS_COMPLETED)
        self.assertEqual(app_txn.balance_before, initial_balance)
        self.assertEqual(app_txn.balance_after, initial_balance - investment_amount)
        self.assertEqual(app_txn.data["investment_id"], response.data["id"])

    def test_balance_before_and_after_tracked_correctly(self):
        """Standard: Balance before and after are correctly tracked."""
        initial_balance = Decimal("300.00000000")
        investment_amount = Decimal("75.50000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        app_txn = Transaction.objects.get(
            user=self.user,
            transaction_type=Transaction.TYPE_INVESTMENT,
        )
        self.assertEqual(app_txn.balance_before, initial_balance)
        self.assertEqual(app_txn.balance_after, initial_balance - investment_amount)

    def test_investment_created_with_correct_metadata(self):
        """Standard: Investment record is created with correct tier and metadata."""
        initial_balance = Decimal("1500.00000000")
        investment_amount = Decimal("1500.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        investment = Investment.objects.get(id=response.data["id"])
        self.assertEqual(investment.amount, investment_amount)
        self.assertEqual(investment.user, self.user)
        self.assertEqual(investment.tier, 4)  # Tier 4 for $1500
        self.assertEqual(investment.status, Investment.STATUS_PENDING)


class InvestmentWalletEdgeCasesTests(APITestCase):
    """Edge case tests for wallet-utils integration."""

    def setUp(self) -> None:
        """Set up test user and client."""
        self.user = User.objects.create_user(
            telegram_user_id=2001,
            first_name="EdgeUser",
            username="edgeuser",
        )
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)

    def test_exact_balance_match(self):
        """Edge: User has exactly the amount needed for investment."""
        investment_amount = Decimal("100.00000000")
        self.user.credit_balance = investment_amount
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("0.00000000"))

    def test_minimum_investment_amount(self):
        """Edge: Minimum investment amount ($10) works correctly."""
        initial_balance = Decimal("10.00000000")
        investment_amount = Decimal("10.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("0.00000000"))

    def test_large_investment_amount(self):
        """Edge: Large investment amount (tier 6) works correctly."""
        initial_balance = Decimal("10000.00000000")
        investment_amount = Decimal("6000.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.user.refresh_from_db()
        expected_balance = initial_balance - investment_amount
        self.assertEqual(self.user.credit_balance, expected_balance)
        
        investment = Investment.objects.get(id=response.data["id"])
        self.assertEqual(investment.tier, 6)

    def test_multiple_sequential_investments(self):
        """Edge: Multiple investments in sequence correctly deduct balance."""
        initial_balance = Decimal("500.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        # First investment
        url = reverse("investment-list")
        response1 = self.client.post(url, {"amount": "100"}, format="json")
        self.assertEqual(response1.status_code, status.HTTP_201_CREATED)
        
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("400.00000000"))

        # Second investment
        response2 = self.client.post(url, {"amount": "150"}, format="json")
        self.assertEqual(response2.status_code, status.HTTP_201_CREATED)
        
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("250.00000000"))

        # Third investment
        response3 = self.client.post(url, {"amount": "200"}, format="json")
        self.assertEqual(response3.status_code, status.HTTP_201_CREATED)
        
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("50.00000000"))

        # Verify all investments were created
        investments = Investment.objects.filter(user=self.user)
        self.assertEqual(investments.count(), 3)

    def test_decimal_precision_eight_places(self):
        """Edge: Decimal precision of 8 places is maintained for user balance."""
        initial_balance = Decimal("100.12345678")
        investment_amount = Decimal("50.98765432")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.user.refresh_from_db()
        expected_balance = initial_balance - investment_amount
        self.assertEqual(self.user.credit_balance, expected_balance)
        
        # Verify wallet transaction was created
        # Note: wallet-utils stores transaction amounts with 2 decimal places by default,
        # but the actual deduction uses full 8-decimal precision from user balance
        wallet_txn = WalletTransaction.objects.filter(
            uid=self.user.id,
            wtype="credit_balance",
        ).first()
        self.assertIsNotNone(wallet_txn)
        # Wallet transaction amount is rounded to 2 decimal places by wallet-utils model
        # but the actual deduction preserves 8 decimal precision
        self.assertEqual(wallet_txn.amount, Decimal("50.99"))  # Rounded to 2 decimal places

    def test_different_tier_investments(self):
        """Edge: Different tier investments correctly deduct balance."""
        initial_balance = Decimal("10000.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        
        # Tier 1: $50
        response1 = self.client.post(url, {"amount": "50"}, format="json")
        self.assertEqual(response1.status_code, status.HTTP_201_CREATED)
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("9950.00000000"))

        # Tier 2: $300
        response2 = self.client.post(url, {"amount": "300"}, format="json")
        self.assertEqual(response2.status_code, status.HTTP_201_CREATED)
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("9650.00000000"))

        # Tier 3: $800
        response3 = self.client.post(url, {"amount": "800"}, format="json")
        self.assertEqual(response3.status_code, status.HTTP_201_CREATED)
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("8850.00000000"))

        # Verify tier assignments
        investments = Investment.objects.filter(user=self.user).order_by("created_at")
        self.assertEqual(investments[0].tier, 1)
        self.assertEqual(investments[1].tier, 2)
        self.assertEqual(investments[2].tier, 3)


class InvestmentWalletOutlierTests(APITestCase):
    """Outlier/error case tests for wallet-utils integration."""

    def setUp(self) -> None:
        """Set up test user and client."""
        self.user = User.objects.create_user(
            telegram_user_id=3001,
            first_name="OutlierUser",
            username="outlieruser",
        )
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)

    def test_insufficient_balance_rejects_investment(self):
        """Outlier: Investment with insufficient balance is rejected."""
        initial_balance = Decimal("50.00000000")
        investment_amount = Decimal("100.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("amount", response.data)
        self.assertIn("Insufficient balance", str(response.data["amount"][0]))
        self.assertIn("50.00000000", str(response.data["amount"][0]))  # Available
        self.assertIn("100.00000000", str(response.data["amount"][0]))  # Required

        # Balance should not change
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, initial_balance)

        # No investment should be created
        self.assertEqual(Investment.objects.filter(user=self.user).count(), 0)

        # No wallet transaction should be created
        self.assertEqual(
            WalletTransaction.objects.filter(uid=self.user.id).count(), 0
        )

    def test_zero_balance_rejects_investment(self):
        """Outlier: User with zero balance cannot invest."""
        self.user.credit_balance = Decimal("0.00000000")
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": "10"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("Insufficient balance", str(response.data["amount"][0]))

    def test_negative_balance_attempt_fails(self):
        """Outlier: Attempting to invest more than balance fails (no negative balance)."""
        initial_balance = Decimal("100.00000000")
        investment_amount = Decimal("100.00000001")  # Just over balance
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("Insufficient balance", str(response.data["amount"][0]))

    def test_amount_just_below_minimum(self):
        """Outlier: Amount just below minimum ($10) is rejected."""
        initial_balance = Decimal("1000.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": "9.99999999"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("amount", response.data)

    def test_very_small_amount_above_minimum(self):
        """Outlier: Very small amount just above minimum works."""
        initial_balance = Decimal("100.00000000")
        investment_amount = Decimal("10.00000001")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.user.refresh_from_db()
        expected_balance = initial_balance - investment_amount
        self.assertEqual(self.user.credit_balance, expected_balance)

    def test_very_large_amount(self):
        """Outlier: Very large investment amount works if balance is sufficient."""
        initial_balance = Decimal("1000000.00000000")
        investment_amount = Decimal("999999.99999999")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": str(investment_amount)}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.user.refresh_from_db()
        expected_balance = initial_balance - investment_amount
        self.assertEqual(self.user.credit_balance, expected_balance)

    def test_negative_amount_rejected(self):
        """Outlier: Negative amount is rejected by serializer validation."""
        initial_balance = Decimal("100.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": "-10"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("amount", response.data)

    def test_zero_amount_rejected(self):
        """Outlier: Zero amount is rejected by serializer validation."""
        initial_balance = Decimal("100.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": "0"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("amount", response.data)

    def test_non_numeric_amount_rejected(self):
        """Outlier: Non-numeric amount is rejected."""
        initial_balance = Decimal("100.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        url = reverse("investment-list")
        response = self.client.post(url, {"amount": "abc"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("amount", response.data)


class InvestmentWalletConcurrencyTests(TransactionTestCase):
    """Concurrency and race condition tests for wallet operations."""

    def setUp(self) -> None:
        """Set up test user."""
        self.user = User.objects.create_user(
            telegram_user_id=4001,
            first_name="ConcurrentUser",
            username="concurrentuser",
        )

    def test_concurrent_investments_prevent_race_condition(self):
        """Outlier: Concurrent investments are handled atomically."""
        initial_balance = Decimal("200.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        # Simulate two concurrent investment attempts
        # Both try to invest $150, but only one should succeed
        def create_investment():
            client = APIClient()
            client.force_authenticate(user=self.user)
            url = reverse("investment-list")
            return client.post(url, {"amount": "150"}, format="json")

        # First investment should succeed
        response1 = create_investment()
        self.assertEqual(response1.status_code, status.HTTP_201_CREATED)
        
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("50.00000000"))

        # Second investment should fail due to insufficient balance
        response2 = create_investment()
        self.assertEqual(response2.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("Insufficient balance", str(response2.data["amount"][0]))

        # Only one investment should exist
        self.assertEqual(Investment.objects.filter(user=self.user).count(), 1)

    def test_atomic_transaction_consistency(self):
        """Outlier: All operations in investment creation are atomic."""
        initial_balance = Decimal("100.00000000")
        self.user.credit_balance = initial_balance
        self.user.save()

        # Create investment successfully
        client = APIClient()
        client.force_authenticate(user=self.user)
        url = reverse("investment-list")
        response = client.post(url, {"amount": "50"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # Verify all related records were created atomically
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("50.00000000"))
        
        # Investment exists
        investment = Investment.objects.get(id=response.data["id"])
        self.assertIsNotNone(investment)
        
        # Wallet transaction exists
        wallet_txn = WalletTransaction.objects.filter(
            uid=self.user.id,
            wtype="credit_balance",
        ).first()
        self.assertIsNotNone(wallet_txn)
        
        # App transaction exists
        app_txn = Transaction.objects.filter(
            user=self.user,
            transaction_type=Transaction.TYPE_INVESTMENT,
        ).first()
        self.assertIsNotNone(app_txn)
        
        # All balances should be consistent
        self.assertEqual(app_txn.balance_before, initial_balance)
        self.assertEqual(app_txn.balance_after, Decimal("50.00000000"))
        self.assertEqual(wallet_txn.balance, Decimal("50.00000000"))


class InvestmentWalletTransactionTypeTests(TestCase):
    """Tests for transaction type registration and usage."""

    def test_make_investment_transaction_type_constant(self):
        """Standard: MAKE_INVESTMENT constant is correctly defined."""
        from utils.wallet import MAKE_INVESTMENT
        
        self.assertEqual(MAKE_INVESTMENT, 10000)

    def test_wallet_transaction_uses_correct_type(self):
        """Standard: Wallet transactions use MAKE_INVESTMENT transaction type."""
        user = User.objects.create_user(
            telegram_user_id=5001,
            first_name="TypeUser",
            username="typeuser",
        )
        user.credit_balance = Decimal("1000.00000000")
        user.save()

        client = APIClient()
        client.force_authenticate(user=user)
        url = reverse("investment-list")
        response = client.post(url, {"amount": "100"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        wallet_txn = WalletTransaction.objects.filter(
            uid=user.id,
            wtype="credit_balance",
        ).first()
        
        self.assertIsNotNone(wallet_txn)
        self.assertEqual(wallet_txn.trans_type, MAKE_INVESTMENT)
        self.assertEqual(wallet_txn.type, "d")  # Debit

    def test_wallet_transaction_remarks_include_tier(self):
        """Standard: Wallet transaction remarks include tier information."""
        user = User.objects.create_user(
            telegram_user_id=5002,
            first_name="RemarksUser",
            username="remarksuser",
        )
        user.credit_balance = Decimal("2000.00000000")
        user.save()

        client = APIClient()
        client.force_authenticate(user=user)
        url = reverse("investment-list")
        response = client.post(url, {"amount": "1500"}, format="json")

        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        wallet_txn = WalletTransaction.objects.filter(
            uid=user.id,
            wtype="credit_balance",
        ).first()
        
        self.assertIn("Investment in tier", wallet_txn.descr)
        self.assertIn("4", wallet_txn.descr)  # Tier 4 for $1500
