"""
Tests for transaction type constants and helper functions.
"""
from django.test import TestCase

from wallet_utils import transaction_types
from wallet_utils.transaction_types import (
    # Wallet Operations
    WALLET_DEPOSIT,
    WALLET_DEPOSIT_CANCEL,
    WALLET_TRANSFER,
    WALLET_WITHDRAW,
    WALLET_WITHDRAW_REFUND,
    WALLET_ADJUST,
    WALLET_TOPUP,
    WALLET_DEDUCT,
    # Package Operations
    PACKAGE_ACTIVATION,
    PACKAGE_ACTIVATION_CANCEL,
    PACKAGE_UPGRADE,
    PACKAGE_UPGRADE_CANCEL,
    PACKAGE_RENEWAL,
    PACKAGE_RENEWAL_CANCEL,
    PACKAGE_EXPIRY_REFUND,
    # Commission & Rewards
    COMMISSION_DISTRIBUTION,
    COMMISSION_DISTRIBUTION_REVERSE,
    ROI_DISTRIBUTION,
    ROI_DISTRIBUTION_REVERSE,
    # Product Operations
    PRODUCT_ORDER,
    PRODUCT_ORDER_CANCEL,
    PRODUCT_ORDER_REFUND,
    PRODUCT_ORDER_PARTIAL_REFUND,
    PRODUCT_REDEMPTION,
    PRODUCT_REDEMPTION_CANCEL,
    # System Operations
    SYSTEM_ADJUSTMENT,
    SYSTEM_REWARD,
    SYSTEM_PENALTY,
    SYSTEM_REVERSAL,
    SYSTEM_MIGRATION,
    # Helper functions and dictionaries
    CUSTOM_TRANSACTION_TYPE_RANGE_END,
    CUSTOM_TRANSACTION_TYPE_RANGE_START,
    get_custom_transaction_types,
    get_transaction_type,
    get_transaction_type_name,
    register_custom_transaction_type,
    unregister_custom_transaction_type,
)


class TransactionTypeConstantsTests(TestCase):
    """Tests for transaction type constants."""
    
    def test_wallet_operations_constants(self):
        """Test wallet operation constants."""
        self.assertEqual(WALLET_DEPOSIT, 1000)
        self.assertEqual(WALLET_DEPOSIT_CANCEL, 1001)
        self.assertEqual(WALLET_TRANSFER, 1002)
        self.assertEqual(WALLET_WITHDRAW, 1003)
        self.assertEqual(WALLET_WITHDRAW_REFUND, 1004)
        self.assertEqual(WALLET_ADJUST, 1005)
        self.assertEqual(WALLET_TOPUP, 1006)
        self.assertEqual(WALLET_DEDUCT, 1007)
        
        # Verify they're integers
        self.assertIsInstance(WALLET_DEPOSIT, int)
        self.assertIsInstance(WALLET_WITHDRAW, int)
    
    def test_package_operations_constants(self):
        """Test package operation constants."""
        self.assertEqual(PACKAGE_ACTIVATION, 2000)
        self.assertEqual(PACKAGE_ACTIVATION_CANCEL, 2001)
        self.assertEqual(PACKAGE_UPGRADE, 2002)
        self.assertEqual(PACKAGE_UPGRADE_CANCEL, 2003)
        self.assertEqual(PACKAGE_RENEWAL, 2004)
        self.assertEqual(PACKAGE_RENEWAL_CANCEL, 2005)
        self.assertEqual(PACKAGE_EXPIRY_REFUND, 2006)
    
    def test_commission_rewards_constants(self):
        """Test commission and rewards constants."""
        self.assertEqual(COMMISSION_DISTRIBUTION, 3000)
        self.assertEqual(COMMISSION_DISTRIBUTION_REVERSE, 3001)
        self.assertEqual(ROI_DISTRIBUTION, 3100)
        self.assertEqual(ROI_DISTRIBUTION_REVERSE, 3101)
    
    def test_product_operations_constants(self):
        """Test product operation constants."""
        self.assertEqual(PRODUCT_ORDER, 4000)
        self.assertEqual(PRODUCT_ORDER_CANCEL, 4001)
        self.assertEqual(PRODUCT_ORDER_REFUND, 4002)
        self.assertEqual(PRODUCT_ORDER_PARTIAL_REFUND, 4003)
        self.assertEqual(PRODUCT_REDEMPTION, 4004)
        self.assertEqual(PRODUCT_REDEMPTION_CANCEL, 4005)
    
    def test_system_operations_constants(self):
        """Test system operation constants."""
        self.assertEqual(SYSTEM_ADJUSTMENT, 5000)
        self.assertEqual(SYSTEM_REWARD, 5001)
        self.assertEqual(SYSTEM_PENALTY, 5002)
        self.assertEqual(SYSTEM_REVERSAL, 5003)
        self.assertEqual(SYSTEM_MIGRATION, 5004)
    
    def test_constant_ranges(self):
        """Test that constants are in expected ranges."""
        # Wallet Operations: 1000-1999
        self.assertGreaterEqual(WALLET_DEPOSIT, 1000)
        self.assertLess(WALLET_DEDUCT, 2000)
        
        # Package Operations: 2000-2999
        self.assertGreaterEqual(PACKAGE_ACTIVATION, 2000)
        self.assertLess(PACKAGE_EXPIRY_REFUND, 3000)
        
        # Commission & Rewards: 3000-3999
        self.assertGreaterEqual(COMMISSION_DISTRIBUTION, 3000)
        self.assertLess(ROI_DISTRIBUTION_REVERSE, 4000)
        
        # Product Operations: 4000-4999
        self.assertGreaterEqual(PRODUCT_ORDER, 4000)
        self.assertLess(PRODUCT_REDEMPTION_CANCEL, 5000)
        
        # System Operations: 5000-5999
        self.assertGreaterEqual(SYSTEM_ADJUSTMENT, 5000)
        self.assertLess(SYSTEM_MIGRATION, 6000)


class TransactionTypeHelperFunctionsTests(TestCase):
    """Tests for transaction type helper functions."""
    
    def test_get_transaction_type_valid(self):
        """Test get_transaction_type() with valid names."""
        self.assertEqual(get_transaction_type("wallet-deposit"), 1000)
        self.assertEqual(get_transaction_type("wallet-withdraw"), 1003)
        self.assertEqual(get_transaction_type("roi-distribution"), 3100)
        self.assertEqual(get_transaction_type("product-order-partial-refund"), 4003)
        self.assertEqual(get_transaction_type("system-adjustment"), 5000)
    
    def test_get_transaction_type_invalid(self):
        """Test get_transaction_type() with invalid name."""
        with self.assertRaises(ValueError) as cm:
            get_transaction_type("invalid-type")
        
        self.assertIn("Unknown transaction type", str(cm.exception))
    
    def test_get_transaction_type_name_valid(self):
        """Test get_transaction_type_name() with valid constants."""
        self.assertEqual(get_transaction_type_name(1000), "wallet-deposit")
        self.assertEqual(get_transaction_type_name(1003), "wallet-withdraw")
        self.assertEqual(get_transaction_type_name(3100), "roi-distribution")
        self.assertEqual(get_transaction_type_name(4003), "product-order-partial-refund")
        self.assertEqual(get_transaction_type_name(5000), "system-adjustment")
    
    def test_get_transaction_type_name_invalid(self):
        """Test get_transaction_type_name() with invalid constant."""
        self.assertIsNone(get_transaction_type_name(99999))
        self.assertIsNone(get_transaction_type_name(0))
        self.assertIsNone(get_transaction_type_name(-1))
    
    def test_transaction_types_dictionary(self):
        """Test TRANSACTION_TYPES dictionary."""
        TRANSACTION_TYPES = transaction_types.TRANSACTION_TYPES
        self.assertIn("wallet-deposit", TRANSACTION_TYPES)
        self.assertEqual(TRANSACTION_TYPES["wallet-deposit"], 1000)
        self.assertIn("roi-distribution", TRANSACTION_TYPES)
        self.assertEqual(TRANSACTION_TYPES["roi-distribution"], 3100)
    
    def test_transaction_type_names_dictionary(self):
        """Test TRANSACTION_TYPE_NAMES dictionary."""
        TRANSACTION_TYPE_NAMES = transaction_types.TRANSACTION_TYPE_NAMES
        self.assertIn(1000, TRANSACTION_TYPE_NAMES)
        self.assertEqual(TRANSACTION_TYPE_NAMES[1000], "wallet-deposit")
        self.assertIn(3100, TRANSACTION_TYPE_NAMES)
        self.assertEqual(TRANSACTION_TYPE_NAMES[3100], "roi-distribution")
    
    def test_dictionary_consistency(self):
        """Test that TRANSACTION_TYPES and TRANSACTION_TYPE_NAMES are consistent."""
        TRANSACTION_TYPES = transaction_types.TRANSACTION_TYPES
        TRANSACTION_TYPE_NAMES = transaction_types.TRANSACTION_TYPE_NAMES
        for name, trans_type in TRANSACTION_TYPES.items():
            self.assertEqual(TRANSACTION_TYPE_NAMES[trans_type], name)
        
        for trans_type, name in TRANSACTION_TYPE_NAMES.items():
            self.assertEqual(TRANSACTION_TYPES[name], trans_type)
    
    def test_all_constants_in_dictionary(self):
        """Test that all constants are in the dictionaries."""
        TRANSACTION_TYPES = transaction_types.TRANSACTION_TYPES
        TRANSACTION_TYPE_NAMES = transaction_types.TRANSACTION_TYPE_NAMES
        constants = [
            WALLET_DEPOSIT, WALLET_WITHDRAW, ROI_DISTRIBUTION,
            SYSTEM_ADJUSTMENT, PRODUCT_ORDER, PRODUCT_ORDER_PARTIAL_REFUND,
        ]
        
        for constant in constants:
            self.assertIn(constant, TRANSACTION_TYPE_NAMES)
            name = TRANSACTION_TYPE_NAMES[constant]
            self.assertIn(name, TRANSACTION_TYPES)
            self.assertEqual(TRANSACTION_TYPES[name], constant)


class CustomTransactionTypeTests(TestCase):
    """Tests for custom transaction type registration."""
    
    def setUp(self):
        """Clean up any custom types before each test."""
        # Get all custom types and unregister them
        custom_types = get_custom_transaction_types()
        for name in list(custom_types.keys()):
            try:
                unregister_custom_transaction_type(name)
            except ValueError:
                pass  # Ignore if it's a built-in type
    
    def tearDown(self):
        """Clean up any custom types after each test."""
        # Get all custom types and unregister them
        custom_types = get_custom_transaction_types()
        for name in list(custom_types.keys()):
            try:
                unregister_custom_transaction_type(name)
            except ValueError:
                pass  # Ignore if it's a built-in type
    
    def test_custom_range_constants(self):
        """Test custom transaction type range constants."""
        self.assertEqual(CUSTOM_TRANSACTION_TYPE_RANGE_START, 10000)
        self.assertEqual(CUSTOM_TRANSACTION_TYPE_RANGE_END, 19999)
    
    def test_register_custom_transaction_type(self):
        """Test registering a custom transaction type."""
        register_custom_transaction_type("custom-reward", 10000)
        
        # Verify it's in the dictionaries (access through module to get updated version)
        TRANSACTION_TYPES = transaction_types.TRANSACTION_TYPES
        TRANSACTION_TYPE_NAMES = transaction_types.TRANSACTION_TYPE_NAMES
        self.assertIn("custom-reward", TRANSACTION_TYPES)
        self.assertEqual(TRANSACTION_TYPES["custom-reward"], 10000)
        self.assertIn(10000, TRANSACTION_TYPE_NAMES)
        self.assertEqual(TRANSACTION_TYPE_NAMES[10000], "custom-reward")
        
        # Verify helper functions work
        self.assertEqual(get_transaction_type("custom-reward"), 10000)
        self.assertEqual(get_transaction_type_name(10000), "custom-reward")
    
    def test_register_multiple_custom_types(self):
        """Test registering multiple custom transaction types."""
        register_custom_transaction_type("custom-reward", 10000)
        register_custom_transaction_type("custom-penalty", 10001)
        register_custom_transaction_type("affiliate-bonus", 10002)
        
        self.assertEqual(get_transaction_type("custom-reward"), 10000)
        self.assertEqual(get_transaction_type("custom-penalty"), 10001)
        self.assertEqual(get_transaction_type("affiliate-bonus"), 10002)
    
    def test_register_custom_type_out_of_range(self):
        """Test that registering a type outside the custom range raises ValueError."""
        with self.assertRaises(ValueError) as cm:
            register_custom_transaction_type("invalid", 5000)
        
        self.assertIn("must be in range", str(cm.exception))
        
        with self.assertRaises(ValueError) as cm:
            register_custom_transaction_type("invalid", 20000)
        
        self.assertIn("must be in range", str(cm.exception))
    
    def test_register_custom_type_duplicate_name(self):
        """Test that registering a duplicate name raises ValueError."""
        register_custom_transaction_type("custom-reward", 10000)
        
        with self.assertRaises(ValueError) as cm:
            register_custom_transaction_type("custom-reward", 10001)
        
        self.assertIn("already exists", str(cm.exception))
    
    def test_register_custom_type_duplicate_value(self):
        """Test that registering a duplicate value raises ValueError."""
        register_custom_transaction_type("custom-reward", 10000)
        
        with self.assertRaises(ValueError) as cm:
            register_custom_transaction_type("custom-penalty", 10000)
        
        self.assertIn("already exists", str(cm.exception))
    
    def test_register_custom_type_override(self):
        """Test overriding an existing custom type."""
        register_custom_transaction_type("custom-reward", 10000)
        register_custom_transaction_type("custom-reward", 10001, override=True)
        
        self.assertEqual(get_transaction_type("custom-reward"), 10001)
        TRANSACTION_TYPE_NAMES = transaction_types.TRANSACTION_TYPE_NAMES
        self.assertNotIn(10000, TRANSACTION_TYPE_NAMES)
    
    def test_unregister_custom_transaction_type(self):
        """Test unregistering a custom transaction type."""
        register_custom_transaction_type("custom-reward", 10000)
        
        # Verify it exists (access through module to get updated version)
        TRANSACTION_TYPES = transaction_types.TRANSACTION_TYPES
        self.assertIn("custom-reward", TRANSACTION_TYPES)
        
        # Unregister it
        unregister_custom_transaction_type("custom-reward")
        
        # Verify it's removed (access through module to get updated version)
        TRANSACTION_TYPES = transaction_types.TRANSACTION_TYPES
        TRANSACTION_TYPE_NAMES = transaction_types.TRANSACTION_TYPE_NAMES
        self.assertNotIn("custom-reward", TRANSACTION_TYPES)
        self.assertNotIn(10000, TRANSACTION_TYPE_NAMES)
        self.assertIsNone(get_transaction_type_name(10000))
    
    def test_unregister_builtin_type_raises_error(self):
        """Test that unregistering a built-in type raises ValueError."""
        with self.assertRaises(ValueError) as cm:
            unregister_custom_transaction_type("wallet-deposit")
        
        self.assertIn("Cannot unregister built-in", str(cm.exception))
    
    def test_unregister_nonexistent_type_raises_error(self):
        """Test that unregistering a non-existent type raises ValueError."""
        with self.assertRaises(ValueError) as cm:
            unregister_custom_transaction_type("nonexistent-type")
        
        self.assertIn("is not registered", str(cm.exception))
    
    def test_get_custom_transaction_types(self):
        """Test getting all custom transaction types."""
        # Initially empty
        custom_types = get_custom_transaction_types()
        self.assertEqual(len(custom_types), 0)
        
        # Register some custom types
        register_custom_transaction_type("custom-reward", 10000)
        register_custom_transaction_type("custom-penalty", 10001)
        
        # Get custom types
        custom_types = get_custom_transaction_types()
        self.assertEqual(len(custom_types), 2)
        self.assertIn("custom-reward", custom_types)
        self.assertIn("custom-penalty", custom_types)
        self.assertEqual(custom_types["custom-reward"], 10000)
        self.assertEqual(custom_types["custom-penalty"], 10001)
        
        # Verify it's a copy (modifying it doesn't affect the original)
        custom_types["test"] = 99999
        self.assertNotIn("test", get_custom_transaction_types())
    
    def test_custom_types_work_with_helper_functions(self):
        """Test that custom types work with helper functions."""
        register_custom_transaction_type("custom-reward", 10000)
        
        # get_transaction_type should work
        self.assertEqual(get_transaction_type("custom-reward"), 10000)
        
        # get_transaction_type_name should work
        self.assertEqual(get_transaction_type_name(10000), "custom-reward")
        
        # Invalid custom type should raise error
        with self.assertRaises(ValueError):
            get_transaction_type("nonexistent-custom")
        
        # Invalid value should return None
        self.assertIsNone(get_transaction_type_name(99999))
    
    def test_builtin_types_unchanged_after_custom_registration(self):
        """Test that built-in types remain unchanged after custom registration."""
        original_wallet_deposit = get_transaction_type("wallet-deposit")
        
        register_custom_transaction_type("custom-reward", 10000)
        
        # Built-in type should still work
        self.assertEqual(get_transaction_type("wallet-deposit"), original_wallet_deposit)
        self.assertEqual(get_transaction_type("wallet-deposit"), WALLET_DEPOSIT)
