import json
import uuid

import requests as http_req

from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.db import transaction
from django.urls import reverse
from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt

from .models import Application, ApplicationDocument, Enrollment, Program
from .forms import ApplicationForm, ApplicationStatusForm
from .documents import DocumentUploadForm, save_application_document, document_checklist
from profiles.models import StudentProfile
from communications.utils import (
    notify_application_received,
    notify_application_approved,
    notify_application_rejected,
)

User = get_user_model()

_FEE_SUCCESS_STATUSES = {'successful', 'success', 'completed'}


def apply_view(request):
    """Public application form — no login required."""
    programs = Program.objects.filter(is_active=True)

    if request.method == 'POST':
        form = ApplicationForm(request.POST, request.FILES)
        if form.is_valid():
            application = form.save()
            _save_documents(application, request.FILES)
            # Store pk in session so the fee page can authenticate the applicant.
            request.session['pending_fee_app_pk'] = application.pk
            return redirect('admissions:application_fee', pk=application.pk)
    else:
        form = ApplicationForm()

    return render(request, 'admissions/apply.html', {
        'form': form,
        'programs': programs,
    })


def application_fee_view(request, pk):
    """Fee payment page shown immediately after form submission."""
    application = get_object_or_404(Application, pk=pk)

    # Allow access if: session owns this application, OR admin is logged in,
    # OR this application was already found via the status page session.
    session_ok = (
        request.session.get('pending_fee_app_pk') == pk
        or request.session.get('status_app_id') == pk
    )
    if not session_ok and not (request.user.is_authenticated and request.user.is_staff):
        messages.error(request, 'Please look up your application first.')
        return redirect('admissions:application_status')

    if application.application_fee_paid:
        # Already paid — send them to the status page.
        request.session['status_app_id'] = application.pk
        return redirect('admissions:application_status')

    return render(request, 'admissions/application_fee.html', {
        'application': application,
        'flw_public_key': settings.FLW_PUBLIC_KEY,
        'fee_amount': Application.APPLICATION_FEE,
    })


def application_fee_initiate(request, pk):
    """AJAX: create a Flutterwave tx for the application fee and return JSON."""
    if request.method != 'POST':
        return JsonResponse({'error': 'POST required.'}, status=405)

    application = get_object_or_404(Application, pk=pk)

    session_ok = (
        request.session.get('pending_fee_app_pk') == pk
        or request.session.get('status_app_id') == pk
    )
    if not session_ok and not (request.user.is_authenticated and request.user.is_staff):
        return JsonResponse({'error': 'Unauthorised.'}, status=403)

    if application.application_fee_paid:
        return JsonResponse({'error': 'Application fee already paid.'}, status=400)

    tx_ref = f'MLS-FEE-{uuid.uuid4().hex[:12].upper()}'
    application.application_fee_txref = tx_ref
    application.save(update_fields=['application_fee_txref'])

    return JsonResponse({
        'tx_ref': tx_ref,
        'public_key': settings.FLW_PUBLIC_KEY,
        'amount': Application.APPLICATION_FEE,
        'customer': {
            'email': application.email,
            'phone_number': application.phone_number,
            'name': application.full_name,
        },
        'description': f'Application fee — {application.reference_number}',
        'callback_url': request.build_absolute_uri(reverse('admissions:application_fee_callback')),
    })


@csrf_exempt
def application_fee_callback(request):
    """Flutterwave redirect after application fee payment."""
    _SUCCESS = _FEE_SUCCESS_STATUSES

    resp_raw = request.GET.get('resp', '')
    if resp_raw:
        try:
            flw_data = json.loads(resp_raw)
            tx = flw_data.get('data', {})
            status = tx.get('status', '').lower()
            tx_ref = tx.get('txRef', '') or tx.get('tx_ref', '')
            transaction_id = str(tx.get('id', ''))
        except (ValueError, KeyError):
            status, tx_ref, transaction_id = '', '', ''
    else:
        status = request.GET.get('status', '').lower()
        tx_ref = request.GET.get('tx_ref', '')
        transaction_id = request.GET.get('transaction_id', '')

    application = Application.objects.filter(application_fee_txref=tx_ref).first()

    if status not in _SUCCESS:
        messages.error(request, 'Payment was not completed. Please try again.')
        if application:
            return redirect('admissions:application_fee', pk=application.pk)
        return redirect('admissions:application_status')

    # Verify with Flutterwave API if we don't already have pre-verified data.
    flw_verified = flw_data if resp_raw else None
    if flw_verified is None:
        try:
            api_resp = http_req.get(
                f'https://api.flutterwave.com/v3/transactions/{transaction_id}/verify',
                headers={'Authorization': f'Bearer {settings.FLW_SECRET_KEY}'},
                timeout=15,
            )
            flw_verified = api_resp.json()
        except Exception:
            messages.error(request, f'Could not verify payment. Quote reference {tx_ref} when contacting us.')
            if application:
                return redirect('admissions:application_fee', pk=application.pk)
            return redirect('admissions:application_status')

    if flw_verified.get('status', '').lower() not in _SUCCESS:
        messages.error(request, 'Payment verification failed. Please try again or contact us.')
        if application:
            return redirect('admissions:application_fee', pk=application.pk)
        return redirect('admissions:application_status')

    if application and not application.application_fee_paid:
        application.application_fee_paid = True
        application.application_fee_paid_at = timezone.now()
        application.application_fee_transaction_id = str(transaction_id)
        application.status = Application.PENDING
        application.save(update_fields=[
            'application_fee_paid', 'application_fee_paid_at',
            'application_fee_transaction_id', 'status', 'updated_at',
        ])
        notify_application_received(application)
        # Clear the fee session key; set status session so they land on their result.
        request.session.pop('pending_fee_app_pk', None)
        request.session['status_app_id'] = application.pk

    messages.success(
        request,
        f'Payment of UGX {Application.APPLICATION_FEE:,} received! '
        f'Your application ({application.reference_number if application else tx_ref}) '
        'is now under review. You will be notified by email.'
    )
    return redirect('admissions:application_status')


def application_status_view(request):
    """Public status check by reference number + email."""
    form = ApplicationStatusForm(request.POST or None)
    application = None
    error = None

    if request.method == 'POST' and form.is_valid():
        ref = form.cleaned_data['reference_number'].strip().upper()
        email = form.cleaned_data['email'].strip().lower()

        try:
            pk = int(ref.replace('MLS-APP-', ''))
            application = Application.objects.get(pk=pk, email__iexact=email)
            # Remember the verified application so document uploads on this
            # page can prove ownership without re-entering the reference.
            request.session['status_app_id'] = application.pk
        except (ValueError, Application.DoesNotExist):
            error = 'No application found with that reference number and email combination.'
            request.session.pop('status_app_id', None)
    elif request.method == 'GET' and request.session.get('status_app_id'):
        # After an upload redirect, keep showing the verified application.
        application = Application.objects.filter(pk=request.session['status_app_id']).first()

    documents = None
    uploads_allowed = False
    if application:
        documents = document_checklist(application)
        uploads_allowed = application.status in (Application.PENDING, Application.UNDER_REVIEW) and application.application_fee_paid

    return render(request, 'admissions/application_status.html', {
        'form': form,
        'application': application,
        'error': error,
        'documents': documents,
        'uploads_allowed': uploads_allowed,
    })


def application_document_upload(request):
    """Applicant uploads/replaces a supporting document from the Status page.

    Ownership is proven by the session entry set after a successful status
    lookup. Only allowed while the application is pending or under review.
    """
    app_id = request.session.get('status_app_id')
    if request.method != 'POST' or not app_id:
        return redirect('admissions:application_status')

    application = get_object_or_404(Application, pk=app_id)
    if application.status not in (Application.PENDING, Application.UNDER_REVIEW):
        messages.error(request, 'Documents can no longer be changed for this application.')
        return redirect('admissions:application_status')

    form = DocumentUploadForm(request.POST, request.FILES)
    if form.is_valid():
        _, created = save_application_document(
            application, form.cleaned_data['document_type'], form.cleaned_data['file']
        )
        label = dict(ApplicationDocument.DOCUMENT_TYPE_CHOICES)[form.cleaned_data['document_type']]
        messages.success(request, f'{label} {"uploaded" if created else "replaced"} successfully.')
    else:
        first_error = next(iter(form.errors.values()))[0] if form.errors else 'Could not upload the file.'
        messages.error(request, first_error)
    return redirect('admissions:application_status')


def _save_documents(application, files):
    """Save uploaded documents to ApplicationDocument."""
    doc_field_map = {
        'o_level_file': ApplicationDocument.O_LEVEL,
        'a_level_file': ApplicationDocument.A_LEVEL,
        'certificate_file': ApplicationDocument.CERTIFICATE,
        'national_id_file': ApplicationDocument.NATIONAL_ID,
        'passport_photo_file': ApplicationDocument.PASSPORT_PHOTO,
    }
    for field_name, doc_type in doc_field_map.items():
        if field_name in files:
            ApplicationDocument.objects.create(
                application=application,
                document_type=doc_type,
                file=files[field_name],
            )
