#!/usr/bin/env python
"""
Fix unprocessed M-Pesa payment
Find transaction by trans_id pattern and process it if borrower matches
"""
import os
import sys
import django
from pathlib import Path

def setup_django():
    """Setup Django environment"""
    script_dir = Path(__file__).resolve().parent
    
    manage_py = script_dir / 'manage.py'
    if not manage_py.exists():
        manage_py = script_dir.parent / 'manage.py'
    
    if manage_py.exists():
        with open(manage_py, 'r') as f:
            content = f.read()
            if 'DJANGO_SETTINGS_MODULE' in content:
                import re
                match = re.search(r"os\.environ\.setdefault\(['\"]DJANGO_SETTINGS_MODULE['\"],\s*['\"]([^'\"]+)['\"]", content)
                if match:
                    os.environ.setdefault('DJANGO_SETTINGS_MODULE', match.group(1))
    
    settings_modules = [
        'config.settings',
        'settings',
        'project.settings',
        'branchsystem.settings',
        'branch_system.settings',
    ]
    
    for settings_module in settings_modules:
        try:
            os.environ.setdefault('DJANGO_SETTINGS_MODULE', settings_module)
            django.setup()
            print(f"✓ Django setup successful with settings: {settings_module}")
            return True
        except Exception as e:
            continue
    
    print("✗ Error: Could not setup Django. Please set DJANGO_SETTINGS_MODULE")
    return False

def normalize_phone(phone):
    """Normalize phone number for comparison - handles all Kenyan formats"""
    if not phone:
        return None
    
    # Remove all non-digits and spaces
    clean = ''.join(filter(str.isdigit, str(phone)))
    
    if not clean:
        return None
    
    # Handle Kenyan phone number formats:
    # 07XXXXXXXX, 01XXXXXXXX, 2547XXXXXXXX, +2547XXXXXXXX
    if clean.startswith('254') and len(clean) >= 12:
        # Already in international format: 254...
        return '+' + clean
    elif clean.startswith('0') and len(clean) >= 9:
        # Local format: 07XXXXXXXX or 01XXXXXXXX
        # Convert 0 -> +254 (e.g., 0712345678 -> +254712345678)
        return '+254' + clean[1:]
    elif len(clean) >= 9:
        # Assume it's already in correct format if it has enough digits
        if not clean.startswith('+'):
            return '+' + clean
        return clean
    else:
        # Return as-is if format is unclear
        return phone

def find_and_process_transaction():
    """Find and process the unprocessed transaction"""
    from loans.models import MpesaTransaction
    from payments.models import MpesaCallback
    from django.contrib.auth import get_user_model
    from django.utils import timezone
    from datetime import datetime
    
    User = get_user_model()
    
    print("\n" + "="*70)
    print("FINDING UNPROCESSED M-PESA TRANSACTION")
    print("="*70)
    
    # First, check callbacks for unprocessed confirmation callbacks
    print("\nChecking M-Pesa callbacks...")
    callbacks = MpesaCallback.objects.filter(
        callback_type='confirmation',
        processed=False
    ).order_by('-created_at')[:10]
    
    transactions = []
    
    for callback in callbacks:
        print(f"\n--- Callback: {callback.id} ---")
        print(f"Created: {callback.created_at}")
        print(f"Processed: {callback.processed}")
        print(f"Has transaction: {callback.transaction is not None}")
        
        # Check if callback has transaction
        if callback.transaction:
            transaction = callback.transaction
            print(f"Transaction ID: {transaction.trans_id}")
            print(f"Transaction Status: {transaction.status}")
            print(f"Borrower: {transaction.borrower or 'Not matched'}")
            
            if not transaction.borrower:
                transactions.append(transaction)
        else:
            # Try to extract transaction data from callback
            print("⚠ Callback has no linked transaction, checking raw data...")
            raw_data = callback.raw_data
            
            # Parse JSON if raw_data is a string
            if raw_data and isinstance(raw_data, str):
                try:
                    import json
                    raw_data = json.loads(raw_data)
                except:
                    print("⚠ Could not parse raw_data as JSON")
                    continue
            
            if raw_data and isinstance(raw_data, dict):
                trans_id = raw_data.get('TransID') or raw_data.get('TransId')
                if trans_id:
                    # Try to find or create transaction
                    try:
                        transaction, created = MpesaTransaction.objects.get_or_create(
                            trans_id=trans_id,
                            defaults={
                                'transaction_type': raw_data.get('TransactionType', 'Pay Bill'),
                                'amount': float(raw_data.get('TransAmount', 0)),
                                'phone_number': raw_data.get('MSISDN', '')[:17],  # Truncate if too long
                                'msisdn': raw_data.get('MSISDN', '')[:17],
                                'trans_time': raw_data.get('TransTime'),
                                'business_short_code': raw_data.get('BusinessShortCode'),
                                'bill_ref_number': raw_data.get('BillRefNumber'),
                                'invoice_number': raw_data.get('InvoiceNumber'),
                                'org_account_balance': raw_data.get('OrgAccountBalance'),
                                'third_party_trans_id': raw_data.get('ThirdPartyTransID'),
                                'first_name': raw_data.get('FirstName'),
                                'middle_name': raw_data.get('MiddleName'),
                                'last_name': raw_data.get('LastName'),
                                'raw_confirmation_data': raw_data,
                                'status': 'confirmed'
                            }
                        )
                        callback.transaction = transaction
                        callback.save()
                        
                        if created:
                            print(f"✓ Created transaction: {transaction.trans_id}")
                        else:
                            print(f"✓ Found existing transaction: {transaction.trans_id}")
                        
                        if not transaction.borrower:
                            transactions.append(transaction)
                    except Exception as e:
                        print(f"✗ Error creating transaction: {e}")
    
    # Also search for transactions directly
    if not transactions:
        print("\nSearching for transactions with trans_id pattern 'TK5019CLK2'...")
        direct_transactions = MpesaTransaction.objects.filter(
            trans_id__icontains='TK5019CLK2'
        )
        if direct_transactions.exists():
            transactions.extend(list(direct_transactions))
        
        if not transactions:
            print("\nSearching for recent unprocessed transactions...")
            direct_transactions = MpesaTransaction.objects.filter(
                status__in=['pending', 'confirmed', 'pending_approval'],
                borrower__isnull=True
            ).order_by('-created_at')[:5]
            transactions.extend(list(direct_transactions))
    
    if not transactions:
        print("\n✗ No unprocessed transactions found")
        return
    
    print(f"\n{'='*70}")
    print(f"Found {len(transactions)} unprocessed transaction(s)")
    print(f"{'='*70}")
    
    for transaction in transactions:
        print(f"\n--- Transaction: {transaction.trans_id} ---")
        print(f"Amount: KES {transaction.amount}")
        print(f"Phone: {transaction.phone_number or transaction.msisdn}")
        print(f"Bill Ref (ID): {transaction.bill_ref_number}")
        print(f"Status: {transaction.status}")
        print(f"Borrower: {transaction.borrower or 'Not matched'}")
        
        # Normalize phone from transaction
        tx_phone = transaction.msisdn or transaction.phone_number
        tx_phone_normalized = normalize_phone(tx_phone)
        tx_id_number = transaction.bill_ref_number
        
        print(f"\nNormalized phone: {tx_phone_normalized}")
        print(f"ID number from payment: {tx_id_number}")
        
        # Try to find borrower
        borrower = None
        match_method = None
        
        # Method 1: Match by ID number AND phone
        if tx_id_number and tx_phone_normalized:
            try:
                borrower_by_id = User.objects.get(id_number=tx_id_number, role='borrower')
                borrower_phone_normalized = normalize_phone(borrower_by_id.phone_number)
                
                print(f"\nFound borrower by ID: {borrower_by_id.get_full_name()}")
                print(f"  Borrower phone: {borrower_by_id.phone_number} (normalized: {borrower_phone_normalized})")
                print(f"  Payment phone: {tx_phone} (normalized: {tx_phone_normalized})")
                
                if borrower_phone_normalized == tx_phone_normalized:
                    borrower = borrower_by_id
                    match_method = "ID number and phone number"
                    print("  ✓ Phone numbers match!")
                else:
                    print("  ⚠ Phone numbers don't match exactly")
                    # Still use this borrower if ID matches
                    borrower = borrower_by_id
                    match_method = "ID number (phone differs)"
            except User.DoesNotExist:
                print(f"\n⚠ No borrower found with ID number: {tx_id_number}")
        
        # Method 2: Match by phone only if ID didn't work
        if not borrower and tx_phone_normalized:
            # Generate all possible phone variants for Kenyan numbers
            phone_variants = set()
            phone_variants.add(tx_phone_normalized)
            
            # If normalized format is +254..., generate variants
            if tx_phone_normalized.startswith('+254') and len(tx_phone_normalized) > 4:
                phone_variants.add(tx_phone_normalized[1:])  # Remove +: 254...
                phone_variants.add('0' + tx_phone_normalized[4:])  # Local: 07... or 01...
            # If format is 254..., add variants
            elif tx_phone_normalized.startswith('254') and len(tx_phone_normalized) > 3:
                phone_variants.add('+' + tx_phone_normalized)  # Add +
                phone_variants.add('0' + tx_phone_normalized[3:])  # Local: 07... or 01...
            # If format is 07... or 01..., add variants
            elif tx_phone_normalized.startswith('0') and len(tx_phone_normalized) >= 9:
                phone_variants.add('+254' + tx_phone_normalized[1:])  # International: +2547...
                phone_variants.add('254' + tx_phone_normalized[1:])  # Without +
            
            # Also try the original phone from transaction
            if tx_phone:
                phone_variants.add(tx_phone)
                phone_variants.add(normalize_phone(tx_phone))
            
            print(f"\nTrying phone variants: {list(phone_variants)[:5]}...")  # Show first 5
            
            for variant in phone_variants:
                try:
                    borrower = User.objects.get(phone_number=variant, role='borrower')
                    match_method = f"Phone number ({variant})"
                    print(f"\n✓ Found borrower by phone: {borrower.get_full_name()}")
                    break
                except User.DoesNotExist:
                    continue
                except User.MultipleObjectsReturned:
                    borrowers = User.objects.filter(phone_number=variant, role='borrower')
                    borrower = borrowers.first()
                    match_method = f"Phone number ({variant}) - multiple found, using first"
                    print(f"\n⚠ Multiple borrowers with phone {variant}, using first: {borrower.get_full_name()}")
                    break
        
        if borrower:
            print(f"\n" + "="*70)
            print(f"MATCH FOUND!")
            print("="*70)
            print(f"Borrower: {borrower.get_full_name()}")
            print(f"Phone: {borrower.phone_number}")
            print(f"ID Number: {borrower.id_number}")
            print(f"Match Method: {match_method}")
            print(f"\nLinking transaction to borrower...")
            
            # Link transaction to borrower
            transaction.borrower = borrower
            transaction.processing_notes = f"Manually matched by {match_method}"
            transaction.save()
            
            print("✓ Transaction linked to borrower")
            
            # Process the payment
            print("\nProcessing payment...")
            try:
                success = transaction.process_payment()
                transaction.refresh_from_db()
                
                if success:
                    print("✓ Payment processed successfully!")
                    if transaction.repayment:
                        print(f"✓ Repayment created: {transaction.repayment.id}")
                    if hasattr(transaction, 'unconfirmed_payment'):
                        print(f"⚠ Note: UnconfirmedPayment record exists: {transaction.unconfirmed_payment.id}")
                else:
                    print("⚠ Payment processing returned False")
                    print(f"Processing notes: {transaction.processing_notes}")
            except Exception as e:
                print(f"✗ Error processing payment: {e}")
                import traceback
                traceback.print_exc()
            
            return  # Process only the first matching transaction
    
    print("\n⚠ No matching borrower found for any transaction")

def main():
    """Main function"""
    print("="*70)
    print("FIX UNPROCESSED M-PESA PAYMENT")
    print("="*70)
    
    if not setup_django():
        sys.exit(1)
    
    find_and_process_transaction()
    
    print("\n" + "="*70)
    print("DONE")
    print("="*70)

if __name__ == '__main__':
    main()
