"""
Developer Payment Views
Allows company admins to pay the developer (PhinTech Solutions Company Ltd)
directly through the system using LipiaOnline / M-Pesa STK push.

Only superusers and admins can access these views.
Payment history shows ONLY payments initiated from this system.

© PhinTech Solutions Company Ltd — System v1.0
"""

import json
import logging
from decimal import Decimal

from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Sum, Count, Q
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST

from .developer_payment_models import DeveloperPayment
from .developer_payment_service import (
    generate_reference,
    initiate_developer_payment,
    process_lipia_callback,
)

logger = logging.getLogger(__name__)


def _admin_required(view_func):
    """Decorator: only superusers or users with role='admin' can access."""
    from functools import wraps

    @wraps(view_func)
    def wrapper(request, *args, **kwargs):
        if not request.user.is_authenticated:
            from django.conf import settings
            return redirect(settings.LOGIN_URL)
        is_admin = request.user.is_superuser or getattr(request.user, 'role', '') == 'admin'
        if not is_admin:
            messages.error(request, "Access denied. Admin privileges required.")
            return redirect('dashboard')
        return view_func(request, *args, **kwargs)

    return wrapper


# ---------------------------------------------------------------------------
# Developer Payment Dashboard / History
# ---------------------------------------------------------------------------

@login_required
@_admin_required
def developer_payments_dashboard(request):
    """
    Main dashboard: shows payment history and a form to initiate a new payment.
    Only payments made FROM THIS SYSTEM are shown.
    """
    payments_qs = DeveloperPayment.objects.select_related('initiated_by').order_by('-created_at')

    # --- Filters ---
    status_filter = request.GET.get('status', '')
    type_filter = request.GET.get('payment_type', '')
    date_from = request.GET.get('date_from', '')
    date_to = request.GET.get('date_to', '')
    search = request.GET.get('q', '').strip()

    if status_filter:
        payments_qs = payments_qs.filter(status=status_filter)
    if type_filter:
        payments_qs = payments_qs.filter(payment_type=type_filter)
    if date_from:
        payments_qs = payments_qs.filter(created_at__date__gte=date_from)
    if date_to:
        payments_qs = payments_qs.filter(created_at__date__lte=date_to)
    if search:
        payments_qs = payments_qs.filter(
            Q(reference__icontains=search)
            | Q(description__icontains=search)
            | Q(mpesa_receipt__icontains=search)
            | Q(payer_phone__icontains=search)
        )

    # --- Summary stats ---
    all_payments = DeveloperPayment.objects.all()
    stats = {
        'total_paid': all_payments.filter(status='completed').aggregate(
            total=Sum('amount'))['total'] or Decimal('0'),
        'total_count': all_payments.filter(status='completed').count(),
        'pending_count': all_payments.filter(status__in=['pending', 'processing']).count(),
        'failed_count': all_payments.filter(status='failed').count(),
        'this_month': all_payments.filter(
            status='completed',
            completed_at__year=timezone.now().year,
            completed_at__month=timezone.now().month,
        ).aggregate(total=Sum('amount'))['total'] or Decimal('0'),
    }

    # --- Pagination ---
    paginator = Paginator(payments_qs, 20)
    page_obj = paginator.get_page(request.GET.get('page'))

    context = {
        'page_obj': page_obj,
        'stats': stats,
        'status_choices': DeveloperPayment.STATUS_CHOICES,
        'payment_type_choices': DeveloperPayment.PAYMENT_TYPE_CHOICES,
        'filters': {
            'status': status_filter,
            'payment_type': type_filter,
            'date_from': date_from,
            'date_to': date_to,
            'q': search,
        },
        # Version info shown subtly in the UI
        'system_version': '1.0',
        'developer': 'PhinTech Solutions Company Ltd',
    }
    return render(request, 'payments/developer_payments.html', context)


# ---------------------------------------------------------------------------
# Initiate a new payment
# ---------------------------------------------------------------------------

@login_required
@_admin_required
@require_POST
def initiate_developer_payment_view(request):
    """
    POST handler: validates form data, creates a DeveloperPayment record,
    then triggers the LipiaOnline STK push.
    """
    try:
        amount_raw = request.POST.get('amount', '').strip()
        phone = request.POST.get('phone', '').strip()
        payment_type = request.POST.get('payment_type', 'maintenance')
        description = request.POST.get('description', '').strip()

        # --- Validation ---
        errors = []
        if not amount_raw:
            errors.append("Amount is required.")
        else:
            try:
                amount = Decimal(amount_raw)
                if amount < Decimal('1'):
                    errors.append("Amount must be at least KES 1.")
            except Exception:
                errors.append("Invalid amount.")
                amount = None

        if not phone:
            errors.append("Phone number is required.")

        if not description:
            errors.append("Description / purpose is required.")

        valid_types = [c[0] for c in DeveloperPayment.PAYMENT_TYPE_CHOICES]
        if payment_type not in valid_types:
            payment_type = 'other'

        if errors:
            for err in errors:
                messages.error(request, err)
            return redirect('payments:developer_payments')

        # --- Create payment record (pending) ---
        reference = generate_reference()
        payment = DeveloperPayment.objects.create(
            amount=amount,
            payment_type=payment_type,
            description=description,
            reference=reference,
            initiated_by=request.user,
            payer_phone=phone,
            status='pending',
        )

        # --- Trigger STK push ---
        result = initiate_developer_payment(
            phone_number=phone,
            amount=float(amount),
            reference=reference,
            description=description[:50],   # STK prompt is short
        )

        # Update record with initiation response
        payment.raw_initiation_response = result.get('raw_response', {})
        if result['success']:
            payment.status = 'processing'
            payment.lipia_checkout_id = result.get('checkout_id')
            payment.lipia_transaction_id = result.get('transaction_reference')
            payment.save(update_fields=[
                'status', 'lipia_checkout_id', 'lipia_transaction_id',
                'raw_initiation_response', 'updated_at'
            ])
            messages.success(
                request,
                f"✅ STK push sent to {phone}. "
                f"Reference: {reference}. "
                f"Please check your phone and enter your M-Pesa PIN to complete the payment."
            )
        else:
            payment.status = 'failed'
            payment.failure_reason = result.get('message', 'STK initiation failed')
            payment.save(update_fields=[
                'status', 'failure_reason', 'raw_initiation_response', 'updated_at'
            ])
            messages.error(
                request,
                f"❌ Failed to send STK push: {result.get('message')}. "
                f"Payment reference {reference} has been saved — you can retry."
            )

    except Exception as exc:
        logger.error(f"[DevPayment] Error in initiate view: {exc}", exc_info=True)
        messages.error(request, f"An unexpected error occurred: {exc}")

    return redirect('payments:developer_payments')


# ---------------------------------------------------------------------------
# Retry a failed / pending payment
# ---------------------------------------------------------------------------

@login_required
@_admin_required
@require_POST
def retry_developer_payment(request, payment_id):
    """Re-send the STK push for a failed or pending payment."""
    payment = get_object_or_404(DeveloperPayment, id=payment_id)

    if payment.status == 'completed':
        messages.warning(request, "This payment is already completed.")
        return redirect('payments:developer_payments')

    result = initiate_developer_payment(
        phone_number=payment.payer_phone,
        amount=float(payment.amount),
        reference=payment.reference,
        description=payment.description[:50],
    )

    payment.raw_initiation_response = result.get('raw_response', {})
    if result['success']:
        payment.status = 'processing'
        payment.lipia_checkout_id = result.get('checkout_id')
        payment.lipia_transaction_id = result.get('transaction_reference')
        payment.failure_reason = None
        payment.save(update_fields=[
            'status', 'lipia_checkout_id', 'lipia_transaction_id',
            'failure_reason', 'raw_initiation_response', 'updated_at'
        ])
        messages.success(request, f"✅ STK push re-sent to {payment.payer_phone}.")
    else:
        payment.status = 'failed'
        payment.failure_reason = result.get('message', 'Retry failed')
        payment.save(update_fields=['status', 'failure_reason', 'raw_initiation_response', 'updated_at'])
        messages.error(request, f"❌ Retry failed: {result.get('message')}")

    return redirect('payments:developer_payments')


# ---------------------------------------------------------------------------
# Cancel a pending payment
# ---------------------------------------------------------------------------

@login_required
@_admin_required
@require_POST
def cancel_developer_payment(request, payment_id):
    """Cancel a pending/processing payment that hasn't completed yet."""
    payment = get_object_or_404(DeveloperPayment, id=payment_id)

    if payment.status == 'completed':
        messages.warning(request, "Cannot cancel a completed payment.")
    elif payment.status == 'cancelled':
        messages.warning(request, "Payment is already cancelled.")
    else:
        payment.status = 'cancelled'
        payment.admin_notes = (
            (payment.admin_notes or '') +
            f"\nCancelled by {request.user.get_full_name()} on {timezone.now().strftime('%Y-%m-%d %H:%M')}"
        ).strip()
        payment.save(update_fields=['status', 'admin_notes', 'updated_at'])
        messages.success(request, f"Payment {payment.reference} cancelled.")

    return redirect('payments:developer_payments')


# ---------------------------------------------------------------------------
# Payment detail view
# ---------------------------------------------------------------------------

@login_required
@_admin_required
def developer_payment_detail(request, payment_id):
    """View full details of a single developer payment."""
    payment = get_object_or_404(
        DeveloperPayment.objects.select_related('initiated_by'),
        id=payment_id
    )
    context = {
        'payment': payment,
        'system_version': '1.0',
        'developer': 'PhinTech Solutions Company Ltd',
    }
    return render(request, 'payments/developer_payment_detail.html', context)


# ---------------------------------------------------------------------------
# LipiaOnline Webhook / Callback  (no auth — called by LipiaOnline servers)
# ---------------------------------------------------------------------------

@csrf_exempt
def lipia_online_callback(request):
    """
    Receives payment status callbacks from LipiaOnline.

    IMPORTANT: Only processes callbacks whose order_id matches a payment
    we initiated from this system. All other callbacks are silently ignored,
    so payments made by other users to the same LipiaOnline link are never
    recorded here.
    """
    if request.method not in ('POST', 'GET'):
        return JsonResponse({'status': 'method not allowed'}, status=405)

    try:
        # Try JSON body first, fall back to POST form data
        if request.content_type and 'application/json' in request.content_type:
            try:
                callback_data = json.loads(request.body.decode('utf-8'))
            except json.JSONDecodeError:
                callback_data = request.POST.dict()
        else:
            callback_data = request.POST.dict() or json.loads(request.body.decode('utf-8') or '{}')

        logger.info(f"[DevPayment] Callback received | keys={list(callback_data.keys())}")

        result = process_lipia_callback(callback_data)

        return JsonResponse({
            'status': 'ok',
            'processed': result['processed'],
            'message': result['message'],
        })

    except Exception as exc:
        logger.error(f"[DevPayment] Callback processing error: {exc}", exc_info=True)
        # Always return 200 to prevent LipiaOnline from retrying indefinitely
        return JsonResponse({'status': 'ok', 'error': 'internal error'})


# ---------------------------------------------------------------------------
# AJAX: check payment status (polling from the UI)
# ---------------------------------------------------------------------------

@login_required
@_admin_required
def check_payment_status(request, payment_id):
    """AJAX endpoint — returns current status of a developer payment."""
    payment = get_object_or_404(DeveloperPayment, id=payment_id)
    return JsonResponse({
        'status': payment.status,
        'status_display': payment.get_status_display(),
        'mpesa_receipt': payment.mpesa_receipt or '',
        'completed_at': payment.completed_at.strftime('%Y-%m-%d %H:%M:%S') if payment.completed_at else '',
    })
