"""
Branch-specific M-Pesa service for handling payments with branch-specific configurations
"""
import requests
import json
import base64
from datetime import datetime, timedelta
from django.conf import settings
from django.utils import timezone
from decimal import Decimal
import logging

logger = logging.getLogger(__name__)


class BranchMpesaService:
    """
    M-Pesa service that uses branch-specific configurations
    """
    
    def __init__(self, branch=None):
        self.branch = branch
        self.consumer_key = self._get_consumer_key()
        self.consumer_secret = self._get_consumer_secret()
        self.business_short_code = self._get_shortcode()
        self.passkey = self._get_passkey()
        
        # Production URLs
        self.base_url = "https://api.safaricom.co.ke"
        self.oauth_url = f"{self.base_url}/oauth/v1/generate"
        self.c2b_register_url = f"{self.base_url}/mpesa/c2b/v1/registerurl"
        self.c2b_simulate_url = f"{self.base_url}/mpesa/c2b/v1/simulate"
        self.stk_push_url = f"{self.base_url}/mpesa/stkpush/v1/processrequest"
        self.stk_query_url = f"{self.base_url}/mpesa/stkpushquery/v1/query"
        self.transaction_status_url = f"{self.base_url}/mpesa/transactionstatus/v1/query"
        self.account_balance_url = f"{self.base_url}/mpesa/accountbalance/v1/query"
        self.reversal_url = f"{self.base_url}/mpesa/reversal/v1/request"
    
    def _get_consumer_key(self):
        """Get consumer key from branch - must be configured by admin"""
        if self.branch and self.branch.mpesa_consumer_key:
            return self.branch.mpesa_consumer_key
        raise ValueError(f"M-Pesa consumer key not configured for branch: {self.branch.name if self.branch else 'Unknown'}")
    
    def _get_consumer_secret(self):
        """Get consumer secret from branch - must be configured by admin"""
        if self.branch and self.branch.mpesa_consumer_secret:
            return self.branch.mpesa_consumer_secret
        raise ValueError(f"M-Pesa consumer secret not configured for branch: {self.branch.name if self.branch else 'Unknown'}")
    
    def _get_shortcode(self):
        """Get business shortcode from branch - must be configured by admin"""
        if self.branch and self.branch.mpesa_shortcode:
            return self.branch.mpesa_shortcode
        raise ValueError(f"M-Pesa shortcode not configured for branch: {self.branch.name if self.branch else 'Unknown'}")
    
    def _get_passkey(self):
        """Get passkey from branch - required for STK push"""
        if self.branch and self.branch.mpesa_passkey:
            return self.branch.mpesa_passkey
        return None  # Passkey is required only for STK push, not for C2B
    
    def get_access_token(self):
        """Get M-Pesa access token"""
        try:
            # Encode consumer key and secret
            credentials = f"{self.consumer_key}:{self.consumer_secret}"
            encoded_credentials = base64.b64encode(credentials.encode()).decode()
            
            headers = {
                'Authorization': f'Basic {encoded_credentials}',
                'Content-Type': 'application/json'
            }
            
            response = requests.get(self.oauth_url, headers=headers)
            response.raise_for_status()
            
            data = response.json()
            access_token = data.get('access_token')
            
            if not access_token:
                raise Exception("No access token received from M-Pesa")
            
            # Store token in database
            from .models import MpesaAccessToken, MpesaConfiguration
            
            # Get or create configuration
            config, created = MpesaConfiguration.objects.get_or_create(
                business_short_code=self.business_short_code,
                defaults={
                    'environment': 'production',
                    'consumer_key': self.consumer_key,
                    'consumer_secret': self.consumer_secret,
                    'passkey': self.passkey,
                    'validation_url': "https://branchbusinessadvance.co.ke/payments/callback/validation/",
                    'confirmation_url': "https://branchbusinessadvance.co.ke/payments/callback/confirmation/",
                }
            )
            
            # Store access token
            MpesaAccessToken.objects.create(
                configuration=config,
                access_token=access_token,
                expires_at=timezone.now() + timedelta(hours=1)
            )
            
            return access_token
            
        except Exception as e:
            logger.error(f"Error getting M-Pesa access token: {str(e)}")
            raise
    
    def register_c2b_urls(self, validation_url=None, confirmation_url=None):
        """Register C2B callback URLs with M-Pesa"""
        try:
            access_token = self.get_access_token()
            
            if not validation_url:
                validation_url = "https://branchbusinessadvance.co.ke/payments/callback/validation/"
            if not confirmation_url:
                confirmation_url = "https://branchbusinessadvance.co.ke/payments/callback/confirmation/"
            
            headers = {
                'Authorization': f'Bearer {access_token}',
                'Content-Type': 'application/json'
            }
            
            payload = {
                "ShortCode": self.business_short_code,
                "ResponseType": "Completed",
                "ConfirmationURL": confirmation_url,
                "ValidationURL": validation_url
            }
            
            response = requests.post(self.c2b_register_url, headers=headers, json=payload)
            response.raise_for_status()
            
            data = response.json()
            logger.info(f"C2B URLs registered successfully: {data}")
            
            return data
            
        except Exception as e:
            logger.error(f"Error registering C2B URLs: {str(e)}")
            raise
    
    def send_stk_push(self, phone_number, amount, account_reference, transaction_desc="Loan Payment"):
        """Send STK Push to customer"""
        try:
            access_token = self.get_access_token()
            
            # Format phone number
            if phone_number.startswith('0'):
                phone_number = f"254{phone_number[1:]}"
            elif not phone_number.startswith('254'):
                phone_number = f"254{phone_number}"
            
            # Generate timestamp
            timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
            
            # Generate password
            if not self.passkey:
                raise Exception("Passkey is required for STK Push")
            
            password_string = f"{self.business_short_code}{self.passkey}{timestamp}"
            password = base64.b64encode(password_string.encode()).decode()
            
            headers = {
                'Authorization': f'Bearer {access_token}',
                'Content-Type': 'application/json'
            }
            
            payload = {
                "BusinessShortCode": self.business_short_code,
                "Password": password,
                "Timestamp": timestamp,
                "TransactionType": "CustomerPayBillOnline",
                "Amount": int(amount),
                "PartyA": phone_number,
                "PartyB": self.business_short_code,
                "PhoneNumber": phone_number,
                "CallBackURL": f"https://branchbusinessadvance.co.ke/payments/mpesa/stk-callback/",
                "AccountReference": account_reference,
                "TransactionDesc": transaction_desc
            }
            
            response = requests.post(self.stk_push_url, headers=headers, json=payload)
            response.raise_for_status()
            
            data = response.json()
            logger.info(f"STK Push sent successfully: {data}")
            
            return data
            
        except Exception as e:
            logger.error(f"Error sending STK Push: {str(e)}")
            raise
    
    def query_stk_push(self, checkout_request_id):
        """Query STK Push status"""
        try:
            access_token = self.get_access_token()
            
            # Generate timestamp
            timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
            
            # Generate password
            if not self.passkey:
                raise Exception("Passkey is required for STK Query")
            
            password_string = f"{self.business_short_code}{self.passkey}{timestamp}"
            password = base64.b64encode(password_string.encode()).decode()
            
            headers = {
                'Authorization': f'Bearer {access_token}',
                'Content-Type': 'application/json'
            }
            
            payload = {
                "BusinessShortCode": self.business_short_code,
                "Password": password,
                "Timestamp": timestamp,
                "CheckoutRequestID": checkout_request_id
            }
            
            response = requests.post(self.stk_query_url, headers=headers, json=payload)
            response.raise_for_status()
            
            data = response.json()
            logger.info(f"STK Query result: {data}")
            
            return data
            
        except Exception as e:
            logger.error(f"Error querying STK Push: {str(e)}")
            raise
    
    def get_transaction_status(self, transaction_id):
        """Get transaction status from M-Pesa"""
        try:
            access_token = self.get_access_token()
            
            # Generate timestamp
            timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
            
            # Generate password
            if not self.passkey:
                raise Exception("Passkey is required for transaction status query")
            
            password_string = f"{self.business_short_code}{self.passkey}{timestamp}"
            password = base64.b64encode(password_string.encode()).decode()
            
            headers = {
                'Authorization': f'Bearer {access_token}',
                'Content-Type': 'application/json'
            }
            
            payload = {
                "Initiator": "testapi",
                "SecurityCredential": password,
                "CommandID": "TransactionStatusQuery",
                "TransactionID": transaction_id,
                "PartyA": self.business_short_code,
                "IdentifierType": "4",
                "ResultURL": f"https://branchbusinessadvance.co.ke/payments/mpesa/transaction-status-callback/",
                "QueueTimeOutURL": f"https://branchbusinessadvance.co.ke/payments/mpesa/transaction-status-timeout/",
                "Remarks": "Transaction status query",
                "Occasion": "Loan payment verification"
            }
            
            response = requests.post(self.transaction_status_url, headers=headers, json=payload)
            response.raise_for_status()
            
            data = response.json()
            logger.info(f"Transaction status query result: {data}")
            
            return data
            
        except Exception as e:
            logger.error(f"Error querying transaction status: {str(e)}")
            raise
    
    def get_account_balance(self):
        """Get account balance from M-Pesa"""
        try:
            access_token = self.get_access_token()
            
            # Generate timestamp
            timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
            
            # Generate password
            if not self.passkey:
                raise Exception("Passkey is required for account balance query")
            
            password_string = f"{self.business_short_code}{self.passkey}{timestamp}"
            password = base64.b64encode(password_string.encode()).decode()
            
            headers = {
                'Authorization': f'Bearer {access_token}',
                'Content-Type': 'application/json'
            }
            
            payload = {
                "Initiator": "testapi",
                "SecurityCredential": password,
                "CommandID": "AccountBalance",
                "PartyA": self.business_short_code,
                "IdentifierType": "4",
                "ResultURL": f"https://branchbusinessadvance.co.ke/payments/mpesa/account-balance-callback/",
                "QueueTimeOutURL": f"https://branchbusinessadvance.co.ke/payments/mpesa/account-balance-timeout/",
                "Remarks": "Account balance query"
            }
            
            response = requests.post(self.account_balance_url, headers=headers, json=payload)
            response.raise_for_status()
            
            data = response.json()
            logger.info(f"Account balance query result: {data}")
            
            return data
            
        except Exception as e:
            logger.error(f"Error querying account balance: {str(e)}")
            raise


class PaymentProcessor:
    """
    Enhanced payment processor with branch-specific M-Pesa integration
    """
    
    def __init__(self, branch=None):
        self.branch = branch
        self.mpesa_service = BranchMpesaService(branch)
    
    def process_c2b_payment(self, callback_data):
        """Process C2B payment callback"""
        try:
            from loans.models import MpesaTransaction
            
            # Extract data from callback
            trans_id = callback_data.get('TransID')
            trans_time = callback_data.get('TransTime')
            trans_amount = Decimal(str(callback_data.get('TransAmount', 0)))
            business_short_code = callback_data.get('BusinessShortCode')
            bill_ref_number = callback_data.get('BillRefNumber')
            invoice_number = callback_data.get('InvoiceNumber')
            org_account_balance = Decimal(str(callback_data.get('OrgAccountBalance', 0)))
            third_party_trans_id = callback_data.get('ThirdPartyTransID')
            msisdn = callback_data.get('MSISDN')
            first_name = callback_data.get('FirstName')
            middle_name = callback_data.get('MiddleName')
            last_name = callback_data.get('LastName')
            
            # Create or update M-Pesa transaction
            transaction, created = MpesaTransaction.objects.get_or_create(
                trans_id=trans_id,
                defaults={
                    'transaction_type': 'c2b',
                    'amount': trans_amount,
                    'phone_number': msisdn or '',
                    'trans_time': trans_time,
                    'business_short_code': business_short_code,
                    'bill_ref_number': bill_ref_number,
                    'invoice_number': invoice_number,
                    'org_account_balance': org_account_balance,
                    'third_party_trans_id': third_party_trans_id,
                    'msisdn': msisdn,
                    'first_name': first_name,
                    'middle_name': middle_name,
                    'last_name': last_name,
                    'status': 'confirmed',
                    'raw_confirmation_data': callback_data,
                    'is_automatic': True,
                    'payment_source': 'automatic'
                }
            )
            
            if not created:
                # Update existing transaction
                transaction.status = 'confirmed'
                transaction.raw_confirmation_data = callback_data
                transaction.save()
            
            # Process the payment
            success = transaction.process_payment()
            
            if success:
                logger.info(f"Payment processed successfully for transaction {trans_id}")
                return True
            else:
                logger.error(f"Payment processing failed for transaction {trans_id}")
                return False
                
        except Exception as e:
            logger.error(f"Error processing C2B payment: {str(e)}")
            return False
    
    def send_payment_request(self, borrower, amount, loan_number=None):
        """Send STK Push payment request to borrower"""
        try:
            # Use borrower's phone number
            phone_number = borrower.phone_number
            if not phone_number:
                raise Exception("Borrower has no phone number")
            
            # Use loan number as account reference if provided
            account_reference = loan_number or str(borrower.id)
            
            # Send STK Push
            result = self.mpesa_service.send_stk_push(
                phone_number=phone_number,
                amount=amount,
                account_reference=account_reference,
                transaction_desc=f"Loan payment for {borrower.get_full_name()}"
            )
            
            # Store the request for tracking
            from loans.models import MpesaTransaction
            
            MpesaTransaction.objects.create(
                transaction_type='stk_push',
                amount=amount,
                phone_number=phone_number,
                merchant_request_id=result.get('MerchantRequestID'),
                checkout_request_id=result.get('CheckoutRequestID'),
                status='pending',
                borrower=borrower,
                is_automatic=False,
                payment_source='automatic'
            )
            
            return result
            
        except Exception as e:
            logger.error(f"Error sending payment request: {str(e)}")
            raise
