import logging
from datetime import date
from members.models import Member
from .models import (
    Contribution, ContributionType,
    PaymentMethod, MpesaTransaction
)
from .sms import send_contribution_sms

logger = logging.getLogger(__name__)


def normalize_phone(phone: str) -> str:
    """Normalize phone to 254XXXXXXXXX format"""
    phone = str(phone).strip().replace('+', '').replace(' ', '')
    if phone.startswith('0'):
        phone = '254' + phone[1:]
    return phone


def match_member_by_phone(phone: str):
    """Find member by phone — try last 9 digits for flexibility"""
    phone = normalize_phone(phone)
    member = Member.objects.filter(phone=phone).first()
    if not member:
        # Try matching last 9 digits
        member = Member.objects.filter(
            phone__endswith=phone[-9:]
        ).first()
    return member


def parse_contribution_type(bill_ref: str):
    """
    Parse the Paybill account number to get contribution type.
    Member sends e.g. "Tithe", "Offering", "Seed" as account number.
    """
    if not bill_ref:
        return ContributionType.objects.get_or_create(
            name='Offering'
        )[0]

    bill_ref_lower = bill_ref.strip().lower()

    # Try to match keyword in ContributionType
    for ct in ContributionType.objects.all():
        if ct.keyword and ct.keyword.lower() in bill_ref_lower:
            return ct
        if ct.name.lower() in bill_ref_lower:
            return ct

    # Default to Offering
    return ContributionType.objects.get_or_create(name='Offering')[0]


def handle_stk_callback(payload: dict):
    """
    Process STK Push callback from Safaricom.
    Called from stk_callback view.
    """
    data = payload.get('Body', {}).get('stkCallback', {})
    checkout_id = data.get('CheckoutRequestID')
    result_code = data.get('ResultCode')

    try:
        contribution = Contribution.objects.get(
            checkout_request_id=checkout_id
        )
    except Contribution.DoesNotExist:
        logger.error(f"No contribution found for checkout: {checkout_id}")
        return False

    if result_code == 0:
        # Payment successful
        items = {
            i['Name']: i['Value']
            for i in data.get('CallbackMetadata', {}).get('Item', [])
        }
        receipt = items.get('MpesaReceiptNumber', '')
        amount = items.get('Amount', contribution.amount)
        phone = str(items.get('PhoneNumber', contribution.phone_number))

        contribution.status = 'completed'
        contribution.mpesa_receipt = receipt
        contribution.phone_number = normalize_phone(phone)
        contribution.save()

        # Log raw transaction
        MpesaTransaction.objects.get_or_create(
            receipt_number=receipt,
            defaults={
                'transaction_type': 'STK',
                'phone_number': normalize_phone(phone),
                'amount': amount,
                'raw_payload': payload,
                'contribution': contribution,
            }
        )

        # Send SMS
        if contribution.member:
            category = (
                contribution.contribution_type.name
                if contribution.contribution_type
                else 'Contribution'
            )
            sent = send_contribution_sms(
                contribution.member,
                float(amount),
                category
            )
            if sent:
                contribution.sms_sent = True
                contribution.save(update_fields=['sms_sent'])

        logger.info(f"STK payment completed: {receipt} - KES {amount}")
        return True

    else:
        contribution.status = 'failed'
        contribution.save()
        logger.warning(
            f"STK payment failed: {checkout_id} "
            f"code={result_code}"
        )
        return False


def handle_c2b_confirmation(payload: dict):
    """
    Process C2B Paybill confirmation from Safaricom.
    Called from c2b_confirmation view.
    """
    phone = str(payload.get('MSISDN', ''))
    amount = payload.get('TransAmount', 0)
    receipt = payload.get('TransID', '')
    bill_ref = payload.get('BillRefNumber', '')
    trans_time = payload.get('TransTime', '')

    # Avoid duplicate processing
    if MpesaTransaction.objects.filter(receipt_number=receipt).exists():
        logger.info(f"Duplicate C2B ignored: {receipt}")
        return True

    # Match member by phone
    member = match_member_by_phone(phone)

    # Determine contribution type from account number
    contribution_type = parse_contribution_type(bill_ref)

    # Get or create payment method
    mpesa_method = PaymentMethod.objects.get_or_create(
        name='M-Pesa Paybill'
    )[0]

    # Save contribution
    contribution = Contribution.objects.create(
        member=member,
        amount=amount,
        contribution_type=contribution_type,
        payment_method=mpesa_method,
        source='C2B',
        status='completed',
        mpesa_receipt=receipt,
        phone_number=normalize_phone(phone),
        bill_ref_number=bill_ref,
        date=date.today(),
        notes=f"Paybill: {bill_ref}",
    )

    # Log raw transaction
    MpesaTransaction.objects.create(
        transaction_type='C2B',
        receipt_number=receipt,
        phone_number=normalize_phone(phone),
        amount=amount,
        bill_ref_number=bill_ref,
        raw_payload=payload,
        contribution=contribution,
    )

    # Send SMS
    if member:
        sent = send_contribution_sms(
            member,
            float(amount),
            contribution_type.name
        )
        if sent:
            contribution.sms_sent = True
            contribution.save(update_fields=['sms_sent'])
    else:
        logger.warning(
            f"C2B payment received but no member matched "
            f"for phone: {phone}"
        )

    logger.info(
        f"C2B payment saved: {receipt} - "
        f"KES {amount} - {bill_ref}"
    )
    return True