"""
Tests for WalletRepository implementation.
"""
from decimal import Decimal
from django.test import TestCase

from wallet_utils import WalletRepository
from wallet_utils.exceptions import WalletOperationError
from tests.models import TestUser, TestWallet, TestWalletCustomField


class WalletRepositoryInitializationTests(TestCase):
    """Tests for WalletRepository initialization."""
    
    def test_init_with_user_model(self):
        """Test initialization with User model only."""
        repo = WalletRepository(
            user_model=TestUser,
            point_types={"credit_balance": 2},
        )
        
        self.assertEqual(repo.get_user_model(), TestUser)
        self.assertEqual(repo.get_wallet_balance_model(), TestUser)
        self.assertEqual(repo.get_point_types(), {"credit_balance": 2})
    
    def test_init_with_separate_wallet_model(self):
        """Test initialization with separate wallet model."""
        repo = WalletRepository(
            user_model=TestUser,
            wallet_balance_model=TestWallet,
            point_types={"credit_balance": 2},
        )
        
        self.assertEqual(repo.get_user_model(), TestUser)
        self.assertEqual(repo.get_wallet_balance_model(), TestWallet)
    
    def test_init_with_custom_field_map(self):
        """Test initialization with custom field name mapping."""
        repo = WalletRepository(
            user_model=TestUser,
            point_types={"reward_points": 0},
            point_type_field_map={"reward_points": "points"},
        )
        
        field_name = repo.get_point_type_field("reward_points")
        self.assertEqual(field_name, "points")
    
    def test_init_with_custom_user_id_field(self):
        """Test initialization with custom user_id field name."""
        repo = WalletRepository(
            user_model=TestUser,
            wallet_balance_model=TestWalletCustomField,
            point_types={"credit_balance": 2},
            wallet_user_id_field="owner_id",
        )
        
        # Verify it uses the custom field
        wallet = TestWalletCustomField.objects.create(
            owner_id=123,
            credit_balance=Decimal("100.00"),
        )
        
        balance = repo.get_user_balance(123, "credit_balance")
        self.assertEqual(balance, Decimal("100.00"))


class WalletRepositoryBalanceOperationsTests(TestCase):
    """Tests for balance operations."""
    
    def setUp(self):
        """Set up test fixtures."""
        self.user = TestUser.objects.create(username="testuser", email="test@example.com")
        self.repo = WalletRepository(
            user_model=TestUser,
            point_types={"credit_balance": 2},
        )
    
    def test_update_balance_add(self):
        """Test update_balance for adding."""
        new_balance = self.repo.update_balance(
            user_id=self.user.id,
            point_type="credit_balance",
            amount=Decimal("100.00"),
        )
        
        self.assertEqual(new_balance, Decimal("100.00"))
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("100.00"))
    
    def test_update_balance_negative_not_allowed(self):
        """Test update_balance prevents negative balance."""
        with self.assertRaises(ValueError):
            self.repo.update_balance(
                user_id=self.user.id,
                point_type="credit_balance",
                amount=Decimal("-100.00"),
                allow_negative=False,
            )
    
    def test_update_balance_negative_allowed(self):
        """Test update_balance allows negative balance."""
        new_balance = self.repo.update_balance(
            user_id=self.user.id,
            point_type="credit_balance",
            amount=Decimal("-100.00"),
            allow_negative=True,
        )
        
        self.assertEqual(new_balance, Decimal("-100.00"))
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("-100.00"))
    
    def test_deduct_balance_atomic_success(self):
        """Test atomic deduction succeeds when balance is sufficient."""
        # Pre-fund
        self.user.credit_balance = Decimal("1000.00")
        self.user.save()
        
        success, new_balance = self.repo.deduct_balance_atomic(
            user_id=self.user.id,
            point_type="credit_balance",
            amount=Decimal("500.00"),
        )
        
        self.assertTrue(success)
        self.assertEqual(new_balance, Decimal("500.00"))
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("500.00"))
    
    def test_deduct_balance_atomic_insufficient(self):
        """Test atomic deduction fails when balance is insufficient."""
        # Pre-fund
        self.user.credit_balance = Decimal("100.00")
        self.user.save()
        
        success, current_balance = self.repo.deduct_balance_atomic(
            user_id=self.user.id,
            point_type="credit_balance",
            amount=Decimal("500.00"),
        )
        
        self.assertFalse(success)
        self.assertEqual(current_balance, Decimal("100.00"))
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("100.00"))
    
    def test_deduct_balance_atomic_exact_balance(self):
        """Test atomic deduction with exact balance."""
        # Pre-fund
        self.user.credit_balance = Decimal("500.00")
        self.user.save()
        
        success, new_balance = self.repo.deduct_balance_atomic(
            user_id=self.user.id,
            point_type="credit_balance",
            amount=Decimal("500.00"),
        )
        
        self.assertTrue(success)
        self.assertEqual(new_balance, Decimal("0.00"))
    
    def test_deduct_balance_atomic_allow_negative(self):
        """Test atomic deduction with allow_negative=True."""
        # Pre-fund
        self.user.credit_balance = Decimal("100.00")
        self.user.save()
        
        success, new_balance = self.repo.deduct_balance_atomic(
            user_id=self.user.id,
            point_type="credit_balance",
            amount=Decimal("500.00"),
            allow_negative=True,
        )
        
        self.assertTrue(success)
        self.assertEqual(new_balance, Decimal("-400.00"))
        self.user.refresh_from_db()
        self.assertEqual(self.user.credit_balance, Decimal("-400.00"))
    
    def test_get_user_balance(self):
        """Test get_user_balance."""
        self.user.credit_balance = Decimal("123.45")
        self.user.save()
        
        balance = self.repo.get_user_balance(self.user.id, "credit_balance")
        self.assertEqual(balance, Decimal("123.45"))


class WalletRepositorySeparateModelTests(TestCase):
    """Tests for repository with separate wallet model."""
    
    def setUp(self):
        """Set up test fixtures."""
        self.user = TestUser.objects.create(username="testuser", email="test@example.com")
        self.wallet = TestWallet.objects.create(
            user_id=self.user.id,
            credit_balance=Decimal("500.00"),
        )
        self.repo = WalletRepository(
            user_model=TestUser,
            wallet_balance_model=TestWallet,
            point_types={"credit_balance": 2},
        )
    
    def test_update_balance_separate_model(self):
        """Test update_balance with separate wallet model."""
        new_balance = self.repo.update_balance(
            user_id=self.user.id,
            point_type="credit_balance",
            amount=Decimal("100.00"),
        )
        
        self.assertEqual(new_balance, Decimal("600.00"))
        self.wallet.refresh_from_db()
        self.assertEqual(self.wallet.credit_balance, Decimal("600.00"))
    
    def test_deduct_balance_atomic_separate_model(self):
        """Test atomic deduction with separate wallet model."""
        success, new_balance = self.repo.deduct_balance_atomic(
            user_id=self.user.id,
            point_type="credit_balance",
            amount=Decimal("200.00"),
        )
        
        self.assertTrue(success)
        self.assertEqual(new_balance, Decimal("300.00"))
        self.wallet.refresh_from_db()
        self.assertEqual(self.wallet.credit_balance, Decimal("300.00"))
    
    def test_get_user_balance_separate_model(self):
        """Test get_user_balance with separate wallet model."""
        balance = self.repo.get_user_balance(self.user.id, "credit_balance")
        self.assertEqual(balance, Decimal("500.00"))


class WalletRepositoryTransactionRecordTests(TestCase):
    """Tests for transaction record creation."""
    
    def setUp(self):
        """Set up test fixtures."""
        self.user = TestUser.objects.create(username="testuser", email="test@example.com")
        self.repo = WalletRepository(
            user_model=TestUser,
            point_types={"credit_balance": 2},
        )
        from wallet_utils.service import TransactionRecord
        from datetime import datetime
        
        self.record = TransactionRecord(
            id=None,
            wtype="credit_balance",
            iid=-100,
            uid=self.user.id,
            type="c",
            amount=Decimal("100.00"),
            balance=Decimal("100.00"),
            trans_type=1000,
            descr="Test transaction",
            cdate=datetime.now().isoformat(),
            extra_data={"test": "data"},
        )
    
    def test_create_transaction_record(self):
        """Test creating a transaction record."""
        transaction_id = self.repo.create_transaction_record(self.record)
        
        self.assertIsInstance(transaction_id, int)
        self.assertGreater(transaction_id, 0)
        
        # Verify record was created
        from wallet_utils.models import WalletTransaction
        txn = WalletTransaction.objects.get(id=transaction_id)
        self.assertEqual(txn.wtype, "credit_balance")
        self.assertEqual(txn.amount, Decimal("100.00"))
        self.assertEqual(txn.extra_data, {"test": "data"})
    
    def test_create_transaction_record_with_datetime(self):
        """Test creating transaction record with datetime object."""
        from datetime import datetime
        from wallet_utils.service import TransactionRecord
        
        record = TransactionRecord(
            id=None,
            wtype="credit_balance",
            iid=-100,
            uid=self.user.id,
            type="c",
            amount=Decimal("50.00"),
            balance=Decimal("50.00"),
            trans_type=None,
            descr="Test",
            cdate=datetime.now(),  # datetime object instead of string
            extra_data={},
        )
        
        transaction_id = self.repo.create_transaction_record(record)
        self.assertIsInstance(transaction_id, int)
