"""
Payment Service - Handles payment processing across multiple gateways
"""
from decimal import Decimal
from typing import Optional, Dict, Any
from django.db import transaction
from django.utils import timezone

from ..models import Payment, Invoice, CreditPackage, PaymentMethodConfig, CreditTransaction
from ....auth.models import Account


class PaymentService:
    """Service for processing payments across multiple gateways"""
    
    @staticmethod
    @transaction.atomic
    def create_stripe_payment(
        invoice: Invoice,
        stripe_payment_intent_id: str,
        stripe_charge_id: Optional[str] = None,
        metadata: Optional[Dict] = None
    ) -> Payment:
        """
        Create payment record for Stripe transaction
        """
        payment = Payment.objects.create(
            account=invoice.account,
            invoice=invoice,
            amount=invoice.total_amount,
            currency=invoice.currency,
            payment_method='stripe',
            status='pending',
            stripe_payment_intent_id=stripe_payment_intent_id,
            stripe_charge_id=stripe_charge_id,
            metadata=metadata or {}
        )
        
        return payment
    
    @staticmethod
    @transaction.atomic
    def create_paypal_payment(
        invoice: Invoice,
        paypal_order_id: str,
        metadata: Optional[Dict] = None
    ) -> Payment:
        """
        Create payment record for PayPal transaction
        """
        payment = Payment.objects.create(
            account=invoice.account,
            invoice=invoice,
            amount=invoice.total_amount,
            currency=invoice.currency,
            payment_method='paypal',
            status='pending',
            paypal_order_id=paypal_order_id,
            metadata=metadata or {}
        )
        
        return payment
    
    @staticmethod
    @transaction.atomic
    def create_manual_payment(
        invoice: Invoice,
        payment_method: str,  # 'bank_transfer' or 'local_wallet'
        transaction_reference: str,
        admin_notes: Optional[str] = None,
        metadata: Optional[Dict] = None
    ) -> Payment:
        """
        Create manual payment (bank transfer or local wallet)
        Requires admin approval
        """
        if payment_method not in ['bank_transfer', 'local_wallet', 'manual']:
            raise ValueError("Invalid manual payment method")
        
        meta = metadata or {}
        # propagate credit package metadata from invoice if present
        if invoice.metadata.get('credit_package_id'):
            meta.setdefault('credit_package_id', invoice.metadata.get('credit_package_id'))
            meta.setdefault('credit_amount', invoice.metadata.get('credit_amount'))

        payment = Payment.objects.create(
            account=invoice.account,
            invoice=invoice,
            amount=invoice.total_amount,
            currency=invoice.currency,
            payment_method=payment_method,
            status='pending_approval',
            transaction_reference=transaction_reference,
            admin_notes=admin_notes,
            metadata=meta
        )
        
        return payment
    
    @staticmethod
    @transaction.atomic
    def mark_payment_completed(
        payment: Payment,
        transaction_id: Optional[str] = None
    ) -> Payment:
        """
        Mark payment as completed and update invoice
        """
        from .invoice_service import InvoiceService
        
        payment.status = 'succeeded'
        payment.processed_at = timezone.now()
        
        if transaction_id:
            payment.transaction_reference = transaction_id
        
        payment.save()
        
        # Update invoice
        if payment.invoice:
            InvoiceService.mark_paid(
                payment.invoice,
                payment_method=payment.payment_method,
                transaction_id=transaction_id
            )
        
        # If payment is for credit package, add credits to account
        if payment.metadata.get('credit_package_id'):
            PaymentService._add_credits_for_payment(payment)
        
        return payment
    
    @staticmethod
    @transaction.atomic
    def mark_payment_failed(
        payment: Payment,
        failure_reason: Optional[str] = None
    ) -> Payment:
        """
        Mark payment as failed
        """
        payment.status = 'failed'
        payment.failure_reason = failure_reason
        payment.processed_at = timezone.now()
        payment.save()
        
        return payment
    
    @staticmethod
    @transaction.atomic
    def approve_manual_payment(
        payment: Payment,
        approved_by_user_id: int,
        admin_notes: Optional[str] = None
    ) -> Payment:
        """
        Approve manual payment (admin action)
        """
        if payment.status != 'pending_approval':
            raise ValueError("Payment is not pending approval")
        
        payment.status = 'succeeded'
        payment.processed_at = timezone.now()
        payment.approved_by_id = approved_by_user_id
        payment.approved_at = timezone.now()
        
        if admin_notes:
            payment.admin_notes = f"{payment.admin_notes}\n\nApproval notes: {admin_notes}" if payment.admin_notes else admin_notes
        
        payment.save()
        
        # Update invoice
        if payment.invoice:
            from .invoice_service import InvoiceService
            InvoiceService.mark_paid(
                payment.invoice,
                payment_method=payment.payment_method,
                transaction_id=payment.transaction_reference
            )
        
        # If payment is for credit package, add credits
        if payment.metadata.get('credit_package_id'):
            PaymentService._add_credits_for_payment(payment)
        
        # If account is inactive/suspended/trial, activate it on successful payment
        try:
            account = payment.account
            if account and account.status != 'active':
                account.status = 'active'
                account.save(update_fields=['status', 'updated_at'])
        except Exception:
            # Do not block payment approval if status update fails
            pass
        
        return payment
    
    @staticmethod
    @transaction.atomic
    def reject_manual_payment(
        payment: Payment,
        rejected_by_user_id: int,
        rejection_reason: str
    ) -> Payment:
        """
        Reject manual payment (admin action)
        """
        if payment.status != 'pending_approval':
            raise ValueError("Payment is not pending approval")
        
        payment.status = 'failed'
        payment.failure_reason = rejection_reason
        payment.processed_at = timezone.now()
        payment.admin_notes = f"{payment.admin_notes}\n\nRejected by user {rejected_by_user_id}: {rejection_reason}" if payment.admin_notes else f"Rejected: {rejection_reason}"
        payment.save()
        
        return payment
    
    @staticmethod
    def _add_credits_for_payment(payment: Payment) -> None:
        """
        Add credits to account after successful payment
        """
        credit_package_id = payment.metadata.get('credit_package_id')
        if not credit_package_id:
            return
        
        try:
            credit_package = CreditPackage.objects.get(id=credit_package_id)
        except CreditPackage.DoesNotExist:
            return
        
        # Update account balance
        account: Account = payment.account
        account.credits = (account.credits or 0) + credit_package.credits
        account.save(update_fields=['credits', 'updated_at'])

        # Create credit transaction
        CreditTransaction.objects.create(
            account=payment.account,
            amount=credit_package.credits,
            transaction_type='purchase',
            description=f"Purchased {credit_package.name}",
            reference_id=str(payment.id),
            metadata={
                'payment_id': payment.id,
                'credit_package_id': credit_package_id,
                'invoice_id': payment.invoice_id if payment.invoice else None
            }
        )
    
    @staticmethod
    def get_available_payment_methods(account: Account) -> Dict[str, Any]:
        """
        Get available payment methods for account's country
        """
        country_code = account.billing_country or 'US'
        
        # Get payment method configurations for country
        configs = PaymentMethodConfig.objects.filter(
            country_code=country_code,
            is_enabled=True
        ).order_by('sort_order')
        
        # Default methods if no config
        if not configs.exists():
            return {
                'methods': [
                    {
                        'id': 'stripe-default',
                        'type': 'stripe',
                        'name': 'Credit/Debit Card',
                        'display_name': 'Credit/Debit Card',
                        'instructions': 'Pay securely with your credit or debit card',
                        'is_enabled': True,
                    },
                    {
                        'id': 'paypal-default',
                        'type': 'paypal',
                        'name': 'PayPal',
                        'display_name': 'PayPal',
                        'instructions': 'Pay with your PayPal account',
                        'is_enabled': True,
                    }
                ],
                'stripe': True,
                'paypal': True,
                'bank_transfer': False,
                'local_wallet': False
            }
        
        # Build response from configs
        methods = []
        method_flags = {
            'stripe': False,
            'paypal': False,
            'bank_transfer': False,
            'local_wallet': False
        }
        
        for config in configs:
            method_flags[config.payment_method] = True
            method_data = {
                'id': f"{config.country_code}-{config.payment_method}-{config.id}",
                'type': config.payment_method,
                'name': config.display_name or config.get_payment_method_display(),
                'display_name': config.display_name or config.get_payment_method_display(),
                'instructions': config.instructions,
                'is_enabled': True,
            }
            
            # Add bank details if bank_transfer
            if config.payment_method == 'bank_transfer' and config.bank_name:
                method_data['bank_details'] = {
                    'bank_name': config.bank_name,
                    'account_number': config.account_number,
                    'routing_number': config.routing_number,
                    'swift_code': config.swift_code
                }
            
            # Add wallet details if local_wallet
            if config.payment_method == 'local_wallet' and config.wallet_type:
                method_data['wallet_details'] = {
                    'wallet_type': config.wallet_type,
                    'wallet_id': config.wallet_id
                }
            
            methods.append(method_data)
        
        return {
            'methods': methods,
            **method_flags
        }
    
    @staticmethod
    def get_pending_approvals() -> list:
        """
        Get all payments pending admin approval
        """
        return list(
            Payment.objects.filter(
                status='pending_approval'
            ).select_related('account', 'invoice').order_by('-created_at')
        )
    
    @staticmethod
    def refund_payment(
        payment: Payment,
        amount: Optional[Decimal] = None,
        reason: Optional[str] = None
    ) -> Payment:
        """
        Process refund for a payment
        
        TODO: Implement actual refund logic for Stripe/PayPal
        For now, just mark as refunded
        """
        if payment.status not in ['completed', 'succeeded']:
            raise ValueError("Can only refund succeeded/complete payments")
        
        refund_amount = amount or payment.amount
        
        if refund_amount > payment.amount:
            raise ValueError("Refund amount cannot exceed payment amount")
        
        # Create refund payment record
        refund = Payment.objects.create(
            account=payment.account,
            invoice=payment.invoice,
            amount=-refund_amount,  # Negative amount for refund
            currency=payment.currency,
            payment_method=payment.payment_method,
            status='refunded',
            processed_at=timezone.now(),
            refunded_at=timezone.now(),
            metadata={
                'refund_for_payment_id': payment.id,
                'refund_reason': reason,
                'original_amount': str(payment.amount)
            }
        )
        
        # Update original payment metadata
        meta = payment.metadata or {}
        meta['refunded'] = True
        meta['refund_payment_id'] = refund.id
        meta['refund_amount'] = str(refund_amount)
        if reason:
            meta['refund_reason'] = reason
        payment.metadata = meta
        payment.status = 'refunded'
        payment.refunded_at = timezone.now()
        payment.save(update_fields=['metadata', 'status', 'refunded_at', 'updated_at'])
        
        return refund
    
    @staticmethod
    def get_account_payments(
        account: Account,
        status: Optional[str] = None,
        limit: int = 50
    ) -> list:
        """
        Get payment history for account
        """
        queryset = Payment.objects.filter(account=account)
        
        if status:
            queryset = queryset.filter(status=status)
        
        return list(
            queryset.select_related('invoice')
            .order_by('-created_at')[:limit]
        )
