"""
Flutterwave Payment Service for Recurring/Installment Payments

This service handles:
- Initial payment authorization and card tokenization
- Recurring charges on saved cards
- Transaction verification
- Payment failure handling and retry logic
- Next payment date calculation
"""
import logging
import requests
import uuid
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Dict, Optional, Any

from django.conf import settings
from django.utils import timezone
from dateutil.relativedelta import relativedelta

logger = logging.getLogger(__name__)


class FlutterwaveServiceError(Exception):
    """Base exception for Flutterwave service errors"""
    pass


class FlutterwaveTimeoutError(FlutterwaveServiceError):
    """Raised when Flutterwave API request times out"""
    pass


class FlutterwaveNetworkError(FlutterwaveServiceError):
    """Raised when network error occurs"""
    pass


class FlutterwaveService:
    """Service class for handling Flutterwave payment operations"""
    
    # Flutterwave API endpoints
    BASE_URL = "https://api.flutterwave.com/v3"
    INITIALIZE_PAYMENT = f"{BASE_URL}/payments"
    CHARGE_CARD = f"{BASE_URL}/charges?type=card"
    VERIFY_TRANSACTION = f"{BASE_URL}/transactions"
    
    # Request timeout in seconds
    REQUEST_TIMEOUT = 30
    
    # Retry configuration
    MAX_RETRY_ATTEMPTS = 3
    RETRY_BACKOFF_MINUTES = [5, 15, 60]  # Retry after 5min, 15min, 1hr
    
    def __init__(self):
        """Initialize Flutterwave service with API credentials"""
        self.public_key = settings.FLW_PUBLIC_KEY
        self.secret_key = settings.FLW_SECRET_KEY
        self.encryption_key = settings.FLW_ENCRYPTION_KEY
        
        if not self.secret_key:
            logger.warning("Flutterwave SECRET_KEY not configured in settings")
        
        self.headers = {
            "Authorization": f"Bearer {self.secret_key}",
            "Content-Type": "application/json",
        }
    
    def _generate_transaction_reference(self) -> str:
        """Generate unique transaction reference"""
        timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
        unique_id = str(uuid.uuid4())[:8].upper()
        return f"NX{timestamp}{unique_id}"
    
    def _make_request(
        self, 
        method: str, 
        endpoint: str, 
        data: Optional[Dict] = None,
        timeout: int = None
    ) -> Dict[str, Any]:
        """
        Make HTTP request to Flutterwave API
        
        Args:
            method: HTTP method (GET, POST, etc.)
            endpoint: Full API endpoint URL
            data: Request payload
            timeout: Request timeout in seconds
            
        Returns:
            Response JSON as dictionary
            
        Raises:
            FlutterwaveTimeoutError: If request times out
            FlutterwaveNetworkError: If network error occurs
            FlutterwaveServiceError: For other API errors
        """
        timeout = timeout or self.REQUEST_TIMEOUT
        
        try:
            if method == "POST":
                response = requests.post(
                    endpoint,
                    json=data,
                    headers=self.headers,
                    timeout=timeout
                )
            elif method == "GET":
                response = requests.get(
                    endpoint,
                    headers=self.headers,
                    timeout=timeout
                )
            else:
                raise FlutterwaveServiceError(f"Unsupported HTTP method: {method}")
            
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.Timeout as e:
            logger.error(f"Flutterwave API timeout: {e}")
            raise FlutterwaveTimeoutError(f"Request timed out after {timeout}s") from e
        except requests.exceptions.ConnectionError as e:
            logger.error(f"Flutterwave API connection error: {e}")
            raise FlutterwaveNetworkError(f"Network connection failed: {str(e)}") from e
        except requests.exceptions.RequestException as e:
            logger.error(f"Flutterwave API request failed: {e}")
            raise FlutterwaveServiceError(f"API request failed: {str(e)}") from e
        except ValueError as e:
            logger.error(f"Invalid JSON response from Flutterwave: {e}")
            raise FlutterwaveServiceError(f"Invalid API response: {str(e)}") from e
    
    def initiate_payment(
        self,
        amount: Decimal,
        email: str,
        phone: str,
        order_reference: str,
        customer_name: str = "",
        currency: str = "USD"
    ) -> Dict[str, Any]:
        """
        Initiate a payment to get authorization URL for card tokenization
        
        This is the first step where customer authorizes their card to be saved
        for future recurring charges.
        
        Args:
            amount: Payment amount (Decimal)
            email: Customer email address
            phone: Customer phone number
            order_reference: Unique order reference (e.g., Order ID)
            customer_name: Customer name (optional)
            currency: Currency code (default: USD)
            
        Returns:
            {
                'status': 'success' | 'failed',
                'authorization_url': str,
                'access_code': str,
                'message': str
            }
        """
        try:
            transaction_ref = self._generate_transaction_reference()
            # Validate against gateway maximum for NGN amounts
            if currency.upper() == "NGN":
                try:
                    max_limit = int(getattr(settings, "FLUTTERWAVE_MAX_CHARGE_NGN", 0))
                except Exception:
                    max_limit = 0

                if max_limit and Decimal(amount) > Decimal(max_limit):
                    msg = (
                        f"Charge amount must not exceed NGN {max_limit:,}. "
                        "Please split the order or contact support."
                    )
                    logger.warning(f"Attempted to initialize payment over gateway limit: {amount} NGN")
                    return {
                        'status': 'failed',
                        'authorization_url': "",
                        'access_code': "",
                        'transaction_ref': transaction_ref,
                        'message': msg
                    }

            payload = {
                "tx_ref": transaction_ref,
                "amount": str(amount),
                "currency": currency,
                "payment_options": "card",
                "customer": {
                    "email": email,
                    "phonenumber": phone,
                    "name": customer_name or email.split("@")[0]
                },
                "customizations": {
                    "title": "NexusMart Payment",
                    "description": f"Order #{order_reference}",
                    "logo": "https://nexusmart.com/logo.png"
                },
                "meta": {
                    "order_reference": order_reference
                }
            }
            
            response = self._make_request("POST", self.INITIALIZE_PAYMENT, payload)
            
            if response.get("status") == "success":
                logger.info(f"Payment initiated successfully: {transaction_ref}")
                return {
                    'status': 'success',
                    'authorization_url': response.get("data", {}).get("link", ""),
                    'access_code': response.get("data", {}).get("access_code", ""),
                    'transaction_ref': transaction_ref,
                    'message': "Payment initialized successfully"
                }
            else:
                error_msg = response.get("message", "Unknown error")
                logger.warning(f"Payment initialization failed: {error_msg}")
                return {
                    'status': 'failed',
                    'authorization_url': "",
                    'access_code': "",
                    'transaction_ref': transaction_ref,
                    'message': error_msg
                }
                
        except (FlutterwaveTimeoutError, FlutterwaveNetworkError) as e:
            logger.error(f"Network error during payment initiation: {e}")
            return {
                'status': 'failed',
                'authorization_url': "",
                'access_code': "",
                'transaction_ref': "",
                'message': f"Network error: {str(e)}"
            }
        except FlutterwaveServiceError as e:
            logger.error(f"Service error during payment initiation: {e}")
            return {
                'status': 'failed',
                'authorization_url': "",
                'access_code': "",
                'transaction_ref': "",
                'message': f"Service error: {str(e)}"
            }
    
    def charge_recurring_card(
        self,
        amount: Decimal,
        email: str,
        token: str,
        order_reference: str,
        installment_num: int,
        customer_name: str = "",
        currency: str = "USD"
    ) -> Dict[str, str]:
        """
        Charge a saved card for installment payment
        
        Args:
            amount: Amount to charge (Decimal)
            email: Customer email
            token: Saved card token from previous authorization
            order_reference: Order reference
            installment_num: Which installment number this is (1, 2, 3, etc.)
            customer_name: Customer name (optional)
            currency: Currency code (default: USD)
            
        Returns:
            {
                'status': 'success' | 'failed',
                'transaction_ref': str,
                'message': str,
                'gateway_response': dict (optional)
            }
        """
        try:
            transaction_ref = self._generate_transaction_reference()
            # Validate against gateway maximum for NGN amounts
            if currency.upper() == "NGN":
                try:
                    max_limit = int(getattr(settings, "FLUTTERWAVE_MAX_CHARGE_NGN", 0))
                except Exception:
                    max_limit = 0

                if max_limit and Decimal(amount) > Decimal(max_limit):
                    msg = (
                        f"Charge amount must not exceed NGN {max_limit:,}. "
                        "Payment exceeds gateway limit and cannot be processed."
                    )
                    logger.warning(f"Attempted recurring charge over gateway limit: {amount} NGN")
                    return {
                        'status': 'failed',
                        'transaction_ref': transaction_ref,
                        'message': msg,
                        'gateway_response': {}
                    }

            payload = {
                "type": "card",
                "amount": str(amount),
                "email": email,
                "tx_ref": transaction_ref,
                "currency": currency,
                "token": token,
                "customer": {
                    "email": email,
                    "name": customer_name or email.split("@")[0]
                },
                "meta": {
                    "order_reference": order_reference,
                    "installment_number": installment_num
                }
            }
            
            response = self._make_request("POST", self.CHARGE_CARD, payload)
            
            if response.get("status") == "success":
                charge_data = response.get("data", {})
                charge_status = charge_data.get("status", "")
                
                if charge_status in ["successful", "pending"]:
                    logger.info(
                        f"Card charged successfully: {transaction_ref} "
                        f"(Installment {installment_num})"
                    )
                    return {
                        'status': 'success',
                        'transaction_ref': transaction_ref,
                        'message': f"Charge successful: {charge_data.get('processor_response', 'Payment processed')}",
                        'gateway_response': response
                    }
                else:
                    error_msg = charge_data.get("processor_response", "Charge failed")
                    logger.warning(f"Card charge failed: {transaction_ref} - {error_msg}")
                    return {
                        'status': 'failed',
                        'transaction_ref': transaction_ref,
                        'message': error_msg,
                        'gateway_response': response
                    }
            else:
                error_msg = response.get("message", "Unknown error")
                logger.warning(f"Charge request failed: {error_msg}")
                return {
                    'status': 'failed',
                    'transaction_ref': transaction_ref,
                    'message': error_msg,
                    'gateway_response': response
                }
                
        except (FlutterwaveTimeoutError, FlutterwaveNetworkError) as e:
            logger.error(f"Network error during card charge: {e}")
            return {
                'status': 'failed',
                'transaction_ref': "",
                'message': f"Network error: {str(e)}"
            }
        except FlutterwaveServiceError as e:
            logger.error(f"Service error during card charge: {e}")
            return {
                'status': 'failed',
                'transaction_ref': "",
                'message': f"Service error: {str(e)}"
            }
    
    def verify_transaction(self, reference: str) -> Dict[str, Any]:
        """
        Verify if a transaction was successful
        
        Args:
            reference: Transaction reference to verify
            
        Returns:
            {
                'status': 'success' | 'failed' | 'pending',
                'verified': bool,
                'amount': str,
                'charge_amount': str,
                'message': str,
                'gateway_response': dict
            }
        """
        try:
            url = f"{self.VERIFY_TRANSACTION}/{reference}/verify"
            response = self._make_request("GET", url)
            
            if response.get("status") == "success":
                data = response.get("data", {})
                tx_status = data.get("status", "failed").lower()
                is_verified = tx_status == "successful"
                
                logger.info(f"Transaction verified: {reference} - Status: {tx_status}")
                
                return {
                    'status': tx_status,
                    'verified': is_verified,
                    'amount': data.get("amount", ""),
                    'charge_amount': data.get("charged_amount", ""),
                    'message': data.get("processor_response", ""),
                    'gateway_response': response
                }
            else:
                error_msg = response.get("message", "Verification failed")
                logger.warning(f"Transaction verification failed: {reference} - {error_msg}")
                
                return {
                    'status': 'failed',
                    'verified': False,
                    'amount': "",
                    'charge_amount': "",
                    'message': error_msg,
                    'gateway_response': response
                }
                
        except (FlutterwaveTimeoutError, FlutterwaveNetworkError) as e:
            logger.error(f"Network error during verification: {e}")
            return {
                'status': 'failed',
                'verified': False,
                'amount': "",
                'charge_amount': "",
                'message': f"Network error: {str(e)}"
            }
        except FlutterwaveServiceError as e:
            logger.error(f"Service error during verification: {e}")
            return {
                'status': 'failed',
                'verified': False,
                'amount': "",
                'charge_amount': "",
                'message': f"Service error: {str(e)}"
            }
    
    def handle_failed_payment(
        self,
        payment_obj,
        error_msg: str,
        auto_retry: bool = True
    ) -> Dict[str, Any]:
        """
        Handle payment failures with retry logic
        
        Args:
            payment_obj: ScheduledPayment model instance
            error_msg: Error message from failed payment
            auto_retry: Whether to attempt automatic retry
            
        Returns:
            {
                'status': 'marked_failed' | 'scheduled_retry' | 'max_retries_exceeded',
                'retry_scheduled': bool,
                'next_retry_date': datetime | None,
                'message': str
            }
        """
        from shop.models import ScheduledPayment
        
        try:
            # Mark payment as failed
            payment_obj.mark_as_failed(error_msg)
            
            # Check if we should retry
            if not auto_retry or payment_obj.retry_count >= self.MAX_RETRY_ATTEMPTS:
                logger.warning(
                    f"Payment #{payment_obj.id} exceeded max retry attempts "
                    f"({payment_obj.retry_count}/{self.MAX_RETRY_ATTEMPTS})"
                )
                
                # Update installment schedule status if all retries exhausted
                installment_schedule = payment_obj.installment_schedule
                installment_schedule.status = 'suspended'
                installment_schedule.save()
                
                return {
                    'status': 'max_retries_exceeded',
                    'retry_scheduled': False,
                    'next_retry_date': None,
                    'message': f"Payment failed after {self.MAX_RETRY_ATTEMPTS} retry attempts"
                }
            
            # Schedule next retry
            retry_index = min(payment_obj.retry_count - 1, len(self.RETRY_BACKOFF_MINUTES) - 1)
            retry_backoff = self.RETRY_BACKOFF_MINUTES[retry_index]
            next_retry = timezone.now() + timedelta(minutes=retry_backoff)
            
            logger.info(
                f"Payment #{payment_obj.id} scheduled for retry at {next_retry} "
                f"(Attempt {payment_obj.retry_count}/{self.MAX_RETRY_ATTEMPTS})"
            )
            
            return {
                'status': 'scheduled_retry',
                'retry_scheduled': True,
                'next_retry_date': next_retry,
                'message': f"Retry scheduled for {next_retry.strftime('%Y-%m-%d %H:%M:%S')}"
            }
            
        except Exception as e:
            logger.error(f"Error handling failed payment: {e}")
            raise FlutterwaveServiceError(f"Failed to handle payment failure: {str(e)}")
    
    def calculate_next_payment_date(
        self,
        start_date: datetime,
        payment_number: int,
        interval: str = "monthly"
    ) -> datetime:
        """
        Calculate next payment due date
        
        Args:
            start_date: Start date of installment plan
            payment_number: Which payment number (1st, 2nd, etc.)
            interval: Payment interval ('monthly', 'weekly', 'daily')
            
        Returns:
            datetime of next payment due date
        """
        if isinstance(start_date, str):
            start_date = datetime.fromisoformat(start_date)
        
        if interval == "monthly":
            return start_date + relativedelta(months=payment_number)
        elif interval == "weekly":
            return start_date + timedelta(weeks=payment_number)
        elif interval == "daily":
            return start_date + timedelta(days=payment_number)
        else:
            logger.warning(f"Unknown interval: {interval}, defaulting to monthly")
            return start_date + relativedelta(months=payment_number)
    
    def get_payment_status_display(self, status: str) -> str:
        """Get human-readable payment status"""
        status_map = {
            'success': 'Payment Successful',
            'failed': 'Payment Failed',
            'pending': 'Payment Pending',
            'processing': 'Processing',
            'timeout': 'Request Timeout',
            'network_error': 'Network Error',
        }
        return status_map.get(status, status)
