from django.test import TestCase
from decimal import Decimal
from datetime import datetime, timedelta
from unittest.mock import patch, MagicMock
from django.utils import timezone

from shop.flutterwave_service import (
    FlutterwaveService,
    FlutterwaveServiceError,
    FlutterwaveTimeoutError,
    FlutterwaveNetworkError
)


class TestFlutterwaveServiceInit(TestCase):
    """Test service initialization"""
    
    def test_service_initializes_with_settings(self):
        """Test that service initializes with Django settings"""
        service = FlutterwaveService()
        self.assertIsNotNone(service.secret_key)
        self.assertEqual(service.headers['Content-Type'], 'application/json')
    
    def test_transaction_reference_generation(self):
        """Test unique transaction reference generation"""
        service = FlutterwaveService()
        ref1 = service._generate_transaction_reference()
        ref2 = service._generate_transaction_reference()
        
        self.assertTrue(ref1.startswith('NX'))
        self.assertNotEqual(ref1, ref2)
        self.assertEqual(len(ref1), 24)  # NX + 14 timestamp + 8 uuid


class TestInitiatePayment(TestCase):
    """Test payment initiation"""
    
    @patch.object(FlutterwaveService, '_make_request')
    def test_initiate_payment_success(self, mock_request):
        """Test successful payment initialization"""
        mock_request.return_value = {
            'status': 'success',
            'data': {
                'link': 'https://checkout.flutterwave.com/v3/pay/xxxxx',
                'access_code': 'ACCESS_CODE_123'
            }
        }
        
        service = FlutterwaveService()
        result = service.initiate_payment(
            amount=Decimal('500.00'),
            email='test@example.com',
            phone='+234801234567',
            order_reference='ORD123'
        )
        
        self.assertEqual(result['status'], 'success')
        self.assertIn('authorization_url', result)
        self.assertIn('access_code', result)
        self.assertIn('transaction_ref', result)
    
    @patch.object(FlutterwaveService, '_make_request')
    def test_initiate_payment_failure(self, mock_request):
        """Test failed payment initialization"""
        mock_request.return_value = {
            'status': 'error',
            'message': 'Invalid amount'
        }
        
        service = FlutterwaveService()
        result = service.initiate_payment(
            amount=Decimal('0.00'),
            email='test@example.com',
            phone='+234801234567',
            order_reference='ORD123'
        )
        
        self.assertEqual(result['status'], 'failed')
        self.assertIn('message', result)
    
    @patch.object(FlutterwaveService, '_make_request')
    def test_initiate_payment_timeout(self, mock_request):
        """Test timeout handling"""
        mock_request.side_effect = FlutterwaveTimeoutError("Request timed out")
        
        service = FlutterwaveService()
        result = service.initiate_payment(
            amount=Decimal('500.00'),
            email='test@example.com',
            phone='+234801234567',
            order_reference='ORD123'
        )
        
        self.assertEqual(result['status'], 'failed')
        self.assertIn('Network error', result['message'])


class TestChargeRecurringCard(TestCase):
    """Test recurring card charging"""
    
    @patch.object(FlutterwaveService, '_make_request')
    def test_charge_success(self, mock_request):
        """Test successful card charge"""
        mock_request.return_value = {
            'status': 'success',
            'data': {
                'status': 'successful',
                'processor_response': 'Approved'
            }
        }
        
        service = FlutterwaveService()
        result = service.charge_recurring_card(
            amount=Decimal('250.00'),
            email='test@example.com',
            token='flw_t_xxxx',
            order_reference='ORD123',
            installment_num=1
        )
        
        self.assertEqual(result['status'], 'success')
        self.assertIn('transaction_ref', result)
    
    @patch.object(FlutterwaveService, '_make_request')
    def test_charge_failure(self, mock_request):
        """Test failed card charge"""
        mock_request.return_value = {
            'status': 'success',
            'data': {
                'status': 'failed',
                'processor_response': 'Insufficient funds'
            }
        }
        
        service = FlutterwaveService()
        result = service.charge_recurring_card(
            amount=Decimal('250.00'),
            email='test@example.com',
            token='flw_t_xxxx',
            order_reference='ORD123',
            installment_num=1
        )
        
        self.assertEqual(result['status'], 'failed')


class TestVerifyTransaction(TestCase):
    """Test transaction verification"""
    
    @patch.object(FlutterwaveService, '_make_request')
    def test_verify_success(self, mock_request):
        """Test successful transaction verification"""
        mock_request.return_value = {
            'status': 'success',
            'data': {
                'status': 'successful',
                'amount': '500.00',
                'charged_amount': '500.00',
                'processor_response': 'Approved'
            }
        }
        
        service = FlutterwaveService()
        result = service.verify_transaction('NX20240115120345ABC1')
        
        self.assertTrue(result['verified'])
        self.assertEqual(result['status'], 'successful')
    
    @patch.object(FlutterwaveService, '_make_request')
    def test_verify_pending(self, mock_request):
        """Test pending transaction verification"""
        mock_request.return_value = {
            'status': 'success',
            'data': {
                'status': 'pending',
                'amount': '500.00'
            }
        }
        
        service = FlutterwaveService()
        result = service.verify_transaction('NX20240115120345ABC1')
        
        self.assertFalse(result['verified'])
        self.assertEqual(result['status'], 'pending')


class TestCalculateNextPaymentDate(TestCase):
    """Test payment date calculation"""
    
    def test_monthly_interval(self):
        """Test monthly payment calculation"""
        service = FlutterwaveService()
        start = datetime(2024, 1, 15)
        
        next_date = service.calculate_next_payment_date(
            start_date=start,
            payment_number=2,
            interval='monthly'
        )
        
        self.assertEqual(next_date.month, 3)
        self.assertEqual(next_date.day, 15)
    
    def test_weekly_interval(self):
        """Test weekly payment calculation"""
        service = FlutterwaveService()
        start = datetime(2024, 1, 15)
        
        next_date = service.calculate_next_payment_date(
            start_date=start,
            payment_number=2,
            interval='weekly'
        )
        
        self.assertEqual((next_date - start).days, 14)
    
    def test_invalid_interval_defaults_to_monthly(self):
        """Test invalid interval defaults to monthly"""
        service = FlutterwaveService()
        start = datetime(2024, 1, 15)
        
        next_date = service.calculate_next_payment_date(
            start_date=start,
            payment_number=1,
            interval='invalid'
        )
        
        self.assertEqual(next_date.month, 2)


class TestPaymentStatusDisplay(TestCase):
    """Test status display formatting"""
    
    def test_status_display_mapping(self):
        """Test status display strings"""
        service = FlutterwaveService()
        
        self.assertEqual(
            service.get_payment_status_display('success'),
            'Payment Successful'
        )
        self.assertEqual(
            service.get_payment_status_display('failed'),
            'Payment Failed'
        )
        self.assertEqual(
            service.get_payment_status_display('unknown'),
            'unknown'
        )


class TestErrorHandling(TestCase):
    """Test error handling and recovery"""
    
    def test_flutterwave_service_error_hierarchy(self):
        """Test exception hierarchy"""
        self.assertTrue(issubclass(FlutterwaveTimeoutError, FlutterwaveServiceError))
        self.assertTrue(issubclass(FlutterwaveNetworkError, FlutterwaveServiceError))
    
    @patch('shop.flutterwave_service.requests.post')
    def test_timeout_error_handling(self, mock_post):
        """Test timeout error is properly caught"""
        import requests
        mock_post.side_effect = requests.exceptions.Timeout()
        
        service = FlutterwaveService()
        with self.assertRaises(FlutterwaveTimeoutError):
            service._make_request('POST', 'http://test', {})
    
    @patch('shop.flutterwave_service.requests.post')
    def test_connection_error_handling(self, mock_post):
        """Test connection error is properly caught"""
        import requests
        mock_post.side_effect = requests.exceptions.ConnectionError()
        
        service = FlutterwaveService()
        with self.assertRaises(FlutterwaveNetworkError):
            service._make_request('POST', 'http://test', {})
