from sqlalchemy import func
from ast import FunctionDef
from symtable import Function
from flask import Flask, jsonify, render_template, url_for, request, redirect, json
from datetime import datetime, timedelta
from flask_mail import Mail, Message
from sendgrid.helpers.mail import *

import pymysql, os, math, requests, uuid, sendgrid, base64

# File imports
from routes import app
from routes import db, FromCache, db_cache

from routes.bookings_urls import get_booking_status_id, currencyPostProcessor, convertAmount
from database.booking_activity_log import BookingActivity
from database.booking_types import BookingType
from database.bookings import Booking
from database.booking_status import BookingStatus
from database.booking_payments import BookingPayment
from database.booking_payments_failure import BookingPaymentFailure
from database.donation import Donation
from database.facility import Facility
from database.inventory import Inventory
from database.booking_details import Detail
from database.transaction import Transaction
from database.gatepass import Gatepass
from database.gatepass_guests import GatepassGuest
from database.gatepass_vehicles import GatepassVehicle
from database.vehicle import Vehicle
from database.salesforce import SalesforceData
from database.payment_gateways import PaymentGateway
from database.payment_methods import PaymentMethod
from database.invoice import Invoice
from database.booking_bank_slips import BookingBankSlip
from database.booking_activity_log import BookingActivity
from database.partner import Partner
from database.checkout_transaction import CheckoutTransaction
from functions.booking_snippets import bookingTotal
from functions.validation import fieldValidation
from variables import *
from routes.print_urls import currencyPostProcessorInvoice
from functions.booking_snippets import getBookingSessionUser


def close(self):
    self.session.close()


def currencyHandler(currencyTo, currencyFrom, amount):
    if currencyTo == currencyFrom:
        return amount
    elif currencyTo != currencyFrom:
        if currencyFrom == "162fface-f5f1-41de-913b-d2bb784dda3a":
            get_rate = requests.get(get_buy_sell_rate.format(currencyTo))

            try:
                buying = get_rate.json()["data"][0]["currency_buy_amount"]
                selling = get_rate.json()["data"][0]["currency_sell_amount"]

                value = float(amount) / float(buying)
                return float(value)
            except Exception:
                value = 0
                return float(value)

        elif currencyTo == "162fface-f5f1-41de-913b-d2bb784dda3a":
            get_rate = requests.get(get_buy_sell_rate.format(currencyFrom))

            try:
                buying = get_rate.json()["data"][0]["currency_buy_amount"]
                selling = get_rate.json()["data"][0]["currency_sell_amount"]

                value = float(amount) * float(selling)
                return float(value)
            except Exception:
                value = 0
                return float(value)

        else:
            get_from_rate = requests.get(
                get_buy_sell_rate.format(currencyFrom))
            get_to_rate = requests.get(get_buy_sell_rate.format(currencyTo))

            try:
                from_buying = get_from_rate.json(
                )["data"][0]["currency_buy_amount"]
                from_selling = get_from_rate.json(
                )["data"][0]["currency_sell_amount"]

                to_buying = get_to_rate.json(
                )["data"][0]["currency_buy_amount"]
                to_selling = get_to_rate.json(
                )["data"][0]["currency_sell_amount"]

                temp_from = float(amount) * float(from_selling)
                temp_to = float(temp_from) / float(to_buying)
                value = temp_to
                return float(value)
            except Exception:
                value = 0
                return float(value)


###############
#### iVeri ####
###############
@app.route("/iveri/bookings/payments/<booking_id>", methods=["POST"])
def get_iveri_posted_data(booking_id):
    get_booking = db.session.query(Booking) \
        .join(Detail, Booking.booking_public_id == Detail.booking_id) \
        .join(BookingStatus, Booking.status == BookingStatus.booking_status_public_id) \
        .add_columns(Booking.booking_public_id, Booking.booking_type, Booking.booking_check_in_date, \
                     Booking.booking_check_out_date, Booking.actual_booking_check_in_date,
                     Booking.actual_booking_check_out_date, \
                     Booking.booking_done_by, Booking.checked_in, Booking.checked_out, Booking.session_id, \
                     Booking.created_at, Booking.updated_at, Booking.status, Booking.booking_ref_code, \
                     Booking.payment_status, Booking.updated_at, Booking.currency, \
                     BookingStatus.booking_status_name, \
                     Detail.first_name, Detail.last_name, Detail.email_address, Detail.phone_number, \
                     Detail.address, Detail.additional_note) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == booking_id) \
        .first()

    booking_details = {}
    bookingTotal(booking_details, booking_id)

    get_booking2 = db.session.query(Booking) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == booking_id) \
        .first()

    if not get_booking:
        output = []
        output.append("The selected booking does not appear to exist.")
        return jsonify({"message": output}), 200

    total = booking_details["total_cost"]

    email_data = {}
    email_data["recipient"] = get_booking.email_address
    email_data["sender"] = "reservations@olpejetaconservancy.org"
    email_data[
        "subject"] = "Booking Payment (#" + get_booking.booking_ref_code + ")"

    email_data["today"] = datetime.now().strftime("%B %Y")
    email_data["booking_ref_code"] = get_booking.booking_ref_code
    email_data["first_name"] = get_booking.first_name
    email_data["last_name"] = get_booking.last_name
    email_data["check_in_date"] = get_booking.booking_check_in_date.strftime(
        "%A, %d %b %Y")
    email_data["check_out_date"] = get_booking.booking_check_out_date.strftime(
        "%A, %d %b %Y")
    email_data["booking_ref_code"] = get_booking.booking_ref_code
    email_data["client"] = get_booking.first_name + " " + get_booking.last_name

    get_booking2.payment_status = 1
    get_booking2.updated_at = datetime.now()
    get_booking2.status = get_booking_status_id("Confirmed")

    try:
        amount = int(request.json["amount"]) / 100
    except Exception as e:
        return jsonify({"message": e}), 422

    try:
        card = request.json["card_number"]

        card_first = card[:4]
        card_last = card[-4:]
    except Exception:
        card_first = None
        card_last = None

    get_exchange_rate = requests.get(
        get_buy_sell_rate.format(get_booking.currency))
    buying_rate = get_exchange_rate.json()["data"][0]["currency_buy_amount"]
    selling_rate = get_exchange_rate.json()["data"][0]["currency_sell_amount"]

    get_past_payments = db.session.query(Transaction) \
        .filter(Transaction.deletion_marker == None) \
        .filter(Transaction.booking_id == booking_id) \
        .all()

    if len(get_past_payments) == 0:
        converted_transaction_amount = currencyHandler(get_booking.currency,
                                                       get_booking.currency,
                                                       amount)

        balance = float(total) - float(converted_transaction_amount)

    elif len(get_past_payments) > 0:
        total_payments = []
        for each_payment in get_past_payments:
            if each_payment.transaction_payment_currency:
                converted_transaction_total = currencyHandler(
                    get_booking.currency,
                    each_payment.transaction_payment_currency,
                    each_payment.transaction_total)
            else:
                converted_transaction_total = currencyHandler(
                    get_booking.currency, get_booking.currency,
                    each_payment.transaction_total)

            total_payments.append(float(converted_transaction_total))

        past_total = sum(total_payments)

        if past_total == total:
            message = []
            message.append("The booking has already been paid for")
            return jsonify({"message": message}), 200

        converted_transaction_amount = currencyHandler(get_booking.currency,
                                                       get_booking.currency,
                                                       amount)

        pending = float(total) - float(past_total)
        balance = pending - float(converted_transaction_amount)

    transaction_id = str(uuid.uuid4())

    payment = BookingPayment(
        booking_payment_public_id=str(uuid.uuid4()),
        booking_id=booking_id,
        transaction_id=transaction_id,
        payment_method="73414fc2-5bf7-42d1-9cdf-08fee8a0b08e",
        phone_number=None,
        mpesa_reference=None,
        card_first_four=card_first,
        card_last_four=card_last,
        booking_amount=total,
        amount_paid=amount,
        session_id=None)

    db.session.add(payment)

    transaction = Transaction(
        transaction_booking_public_id=transaction_id,
        booking_id=booking_id,
        transaction_original_cost=total,
        transaction_total=amount,
        transaction_total_currency=get_booking.currency,
        transaction_balance=balance,
        transaction_payment_method="33ad20be-3a89-4deb-ab71-7510ba51677e",
        transaction_payment_currency=get_booking.currency,
        payment_currency_buying_rate_at_time=buying_rate,
        payment_currency_selling_rate_at_time=selling_rate,
        transaction_date=datetime.now(),
        session_id="iVeri",
        created_at=datetime.now(),
        updated_at=datetime.now())

    db.session.add(transaction)

    get_to_invoice = db.session.query(Invoice) \
        .filter(Invoice.deletion_marker == None) \
        .filter(Invoice.booking_id == booking_id) \
        .first()

    if get_to_invoice:
        get_to_invoice.deletion_marker = 1
        get_to_invoice.updated_at = datetime.now()

    get_all_inventory = db.session.query(Inventory) \
        .filter(Inventory.deletion_marker == None) \
        .filter(Inventory.booking_id == booking_id) \
        .all()

    if get_all_inventory:
        for single_inventory in get_all_inventory:
            single_inventory.inventory_payment_method = "33ad20be-3a89-4deb-ab71-7510ba51677e"
            single_inventory.updated_at = datetime.now()

    else:
        pass

    get_all_facilities = db.session.query(Facility) \
        .filter(Facility.deletion_marker == None) \
        .filter(Facility.booking_id == booking_id) \
        .all()

    if get_all_facilities:
        for single_facility in get_all_facilities:
            single_facility.facility_payment_method = "33ad20be-3a89-4deb-ab71-7510ba51677e"
            single_facility.updated_at = datetime.now()

    else:
        pass

    gatepass = db.session.query(Gatepass) \
        .filter(Gatepass.deletion_marker == None) \
        .filter(Gatepass.booking_id == booking_id) \
        .first()

    gatepass_id = gatepass.gatepass_public_id

    get_all_guests = db.session.query(GatepassGuest) \
        .filter(GatepassGuest.deletion_marker == None) \
        .filter(GatepassGuest.gatepass_id == gatepass_id) \
        .all()

    if get_all_guests:
        for single_guest in get_all_guests:
            single_guest.gatepass_guest_payment_method = "33ad20be-3a89-4deb-ab71-7510ba51677e"
            single_guest.updated_at = datetime.now()

    else:
        pass

    get_all_vehicles = db.session.query(GatepassVehicle) \
        .filter(GatepassVehicle.deletion_marker == None) \
        .filter(GatepassVehicle.gatepass_id == gatepass_id) \
        .all()

    if get_all_vehicles:
        for single_vehicle in get_all_vehicles:
            single_vehicle.gatepass_vehicle_payment_method = "33ad20be-3a89-4deb-ab71-7510ba51677e"
            single_vehicle.updated_at = datetime.now()

    else:
        pass

    try:
        db.session.commit()
        close(db)

        try:
            send_email(email_data)
        except Exception:
            pass

        output = []
        output.append(
            "Payment for the booking has been successfully carried out.")
        return jsonify({"message": output}), 200

    except Exception as e:
        print(e)
        db.session.rollback()
        close(db)

        output = []
        output.append("There was a slight issue paying for the booking.")
        return jsonify({"message": output}), 422


@app.route("/iveri/bookings/payments-failure/<booking_id>")
def get_iveri_failure_posted_data(booking_id):
    get_booking = db.session.query(Booking) \
        .join(Detail, Booking.booking_public_id == Detail.booking_id) \
        .join(BookingStatus, Booking.status == BookingStatus.booking_status_public_id) \
        .add_columns(Booking.booking_public_id, Booking.booking_type, Booking.booking_check_in_date, \
                     Booking.booking_check_out_date, Booking.actual_booking_check_in_date,
                     Booking.actual_booking_check_out_date, \
                     Booking.booking_done_by, Booking.checked_in, Booking.checked_out, Booking.session_id, \
                     Booking.created_at, Booking.updated_at, Booking.status, Booking.booking_ref_code, \
                     Booking.payment_status, Booking.updated_at, Booking.currency, \
                     BookingStatus.booking_status_name, \
                     Detail.first_name, Detail.last_name, Detail.email_address, Detail.phone_number, \
                     Detail.address, Detail.additional_note) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == booking_id) \
        .first()

    try:
        card = request.json["card_number"]
    except Exception:
        card = None

    try:
        failure_reason = request.json["failure_reason"]
    except Exception:
        failure_reason = None

    email_data = {}
    email_data["recipient"] = get_booking.email_address
    email_data["sender"] = "reservations@olpejetaconservancy.org"
    email_data[
        "subject"] = "Booking Payment Failure (#" + get_booking.booking_ref_code + ")"

    email_data["today"] = datetime.now().strftime("%B %Y")
    email_data["booking_ref_code"] = get_booking.booking_ref_code
    email_data["first_name"] = get_booking.first_name
    email_data["last_name"] = get_booking.last_name
    email_data["check_in_date"] = get_booking.booking_check_in_date.strftime(
        "%A, %d %b %Y")
    email_data["check_out_date"] = get_booking.booking_check_out_date.strftime(
        "%A, %d %b %Y")
    email_data["booking_ref_code"] = get_booking.booking_ref_code
    email_data["client"] = get_booking.first_name + " " + get_booking.last_name

    booking_payment_failure_public_id = str(uuid.uuid4())

    payment = BookingPaymentFailure(
        booking_id=booking_id,
        card_number=card,
        booking_payment_failure_public_id=booking_payment_failure_public_id,
        failure_reason=failure_reason,
        session_id=None)

    db.session.add(payment)

    try:
        db.session.commit()
        close(db)
        output = []
        output.append("Payment for the booking has failed.")
        return jsonify({"message": output}), 200

    except Exception as e:
        print(e)
        db.session.rollback()
        close(db)

        output = []
        output.append("Payment for the booking has  failed.")
        return jsonify({"message": output}), 422


@app.route("/ui-iveri/booking-payment", methods=["POST"])
def ui_iveri_payment():
    validation_list = [{
        "field": "booking_id"
    }, {
        "field": "amount"
    }, {
        "field": "card_number"
    }, {
        "field": "session_id"
    }]

    messages = fieldValidation(request.json, validation_list)
    if messages:
        return jsonify({"messages": messages}), 422

    booking_id = request.json["booking_id"]

    get_booking = db.session.query(Booking) \
        .join(Detail, Booking.booking_public_id == Detail.booking_id) \
        .join(BookingStatus, Booking.status == BookingStatus.booking_status_public_id) \
        .add_columns(Booking.booking_public_id, Booking.booking_type, Booking.booking_check_in_date, \
                     Booking.booking_check_out_date, Booking.actual_booking_check_in_date,
                     Booking.actual_booking_check_out_date, \
                     Booking.booking_done_by, Booking.checked_in, Booking.checked_out, Booking.session_id, \
                     Booking.created_at, Booking.updated_at, Booking.status, Booking.booking_ref_code, \
                     Booking.payment_status, Booking.updated_at, Booking.currency, \
                     BookingStatus.booking_status_name, \
                     Detail.first_name, Detail.last_name, Detail.email_address, Detail.phone_number, \
                     Detail.address, Detail.additional_note) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == booking_id) \
        .first()

    booking_details = {}
    bookingTotal(booking_details, booking_id)

    get_booking2 = db.session.query(Booking) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == booking_id) \
        .first()

    if not get_booking:
        output = []
        output.append("The selected booking does not appear to exist.")
        return jsonify({"message": output}), 200

    total = booking_details["total_cost"]

    email_data = {}
    email_data["recipient"] = get_booking.email_address
    email_data["sender"] = "reservations@olpejetaconservancy.org"
    email_data[
        "subject"] = "Booking Payment (#" + get_booking.booking_ref_code + ")"

    email_data["today"] = datetime.now().strftime("%B %Y")
    email_data["booking_ref_code"] = get_booking.booking_ref_code
    email_data["first_name"] = get_booking.first_name
    email_data["last_name"] = get_booking.last_name
    email_data["check_in_date"] = get_booking.booking_check_in_date.strftime(
        "%A, %d %b %Y")
    email_data["check_out_date"] = get_booking.booking_check_out_date.strftime(
        "%A, %d %b %Y")
    email_data["booking_ref_code"] = get_booking.booking_ref_code
    email_data["client"] = get_booking.first_name + " " + get_booking.last_name

    get_booking2.payment_status = 1
    get_booking2.updated_at = datetime.now()
    get_booking2.status = get_booking_status_id("Confirmed")

    # This SHOULD NOT be posted in cents
    amount = request.json["amount"]

    card = request.json["card_number"]

    card_first = card[:4]
    card_last = card[-4:]

    get_exchange_rate = requests.get(
        get_buy_sell_rate.format(get_booking.currency))
    buying_rate = get_exchange_rate.json()["data"][0]["currency_buy_amount"]
    selling_rate = get_exchange_rate.json()["data"][0]["currency_sell_amount"]

    get_past_payments = db.session.query(Transaction) \
        .filter(Transaction.deletion_marker == None) \
        .filter(Transaction.booking_id == booking_id) \
        .all()

    if len(get_past_payments) == 0:
        converted_transaction_amount = currencyHandler(get_booking.currency,
                                                       get_booking.currency,
                                                       amount)

        balance = float(total) - float(converted_transaction_amount)

    elif len(get_past_payments) > 0:
        total_payments = []
        for each_payment in get_past_payments:
            if each_payment.transaction_payment_currency:
                converted_transaction_total = currencyHandler(
                    get_booking.currency,
                    each_payment.transaction_payment_currency,
                    each_payment.transaction_total)
            else:
                converted_transaction_total = currencyHandler(
                    get_booking.currency, get_booking.currency,
                    each_payment.transaction_total)

            total_payments.append(float(converted_transaction_total))

        past_total = sum(total_payments)

        if past_total == total:
            message = []
            message.append("The booking has already been paid for")
            return jsonify({"message": message}), 200

        converted_transaction_amount = currencyHandler(get_booking.currency,
                                                       get_booking.currency,
                                                       amount)

        pending = float(total) - float(past_total)
        balance = pending - float(converted_transaction_amount)

    transaction_id = str(uuid.uuid4())

    payment = BookingPayment(
        booking_payment_public_id=str(uuid.uuid4()),
        booking_id=booking_id,
        transaction_id=transaction_id,
        payment_method="73414fc2-5bf7-42d1-9cdf-08fee8a0b08e",
        phone_number=None,
        mpesa_reference=None,
        card_first_four=card_first,
        card_last_four=card_last,
        booking_amount=total,
        amount_paid=amount,
        session_id=request.json["session_id"])

    db.session.add(payment)

    transaction = Transaction(
        transaction_booking_public_id=transaction_id,
        booking_id=booking_id,
        transaction_original_cost=total,
        transaction_total=amount,
        transaction_total_currency=get_booking.currency,
        transaction_balance=balance,
        transaction_payment_method="33ad20be-3a89-4deb-ab71-7510ba51677e",
        transaction_payment_currency=get_booking.currency,
        payment_currency_buying_rate_at_time=buying_rate,
        payment_currency_selling_rate_at_time=selling_rate,
        transaction_date=datetime.now(),
        session_id=request.json["session_id"],
        created_at=datetime.now(),
        updated_at=datetime.now())

    db.session.add(transaction)

    get_to_invoice = db.session.query(Invoice) \
        .filter(Invoice.deletion_marker == None) \
        .filter(Invoice.booking_id == booking_id) \
        .first()

    if get_to_invoice:
        get_to_invoice.deletion_marker = 1
        get_to_invoice.updated_at = datetime.now()

    get_all_inventory = db.session.query(Inventory) \
        .filter(Inventory.deletion_marker == None) \
        .filter(Inventory.booking_id == booking_id) \
        .all()

    if get_all_inventory:
        for single_inventory in get_all_inventory:
            single_inventory.inventory_payment_method = "33ad20be-3a89-4deb-ab71-7510ba51677e"
            single_inventory.updated_at = datetime.now()

    else:
        pass

    get_all_facilities = db.session.query(Facility) \
        .filter(Facility.deletion_marker == None) \
        .filter(Facility.booking_id == booking_id) \
        .all()

    if get_all_facilities:
        for single_facility in get_all_facilities:
            single_facility.facility_payment_method = "33ad20be-3a89-4deb-ab71-7510ba51677e"
            single_facility.updated_at = datetime.now()

    else:
        pass

    gatepass = db.session.query(Gatepass) \
        .filter(Gatepass.deletion_marker == None) \
        .filter(Gatepass.booking_id == booking_id) \
        .first()

    gatepass_id = gatepass.gatepass_public_id

    get_all_guests = db.session.query(GatepassGuest) \
        .filter(GatepassGuest.deletion_marker == None) \
        .filter(GatepassGuest.gatepass_id == gatepass_id) \
        .all()

    if get_all_guests:
        for single_guest in get_all_guests:
            single_guest.gatepass_guest_payment_method = "33ad20be-3a89-4deb-ab71-7510ba51677e"
            single_guest.updated_at = datetime.now()

    else:
        pass

    get_all_vehicles = db.session.query(GatepassVehicle) \
        .filter(GatepassVehicle.deletion_marker == None) \
        .filter(GatepassVehicle.gatepass_id == gatepass_id) \
        .all()

    if get_all_vehicles:
        for single_vehicle in get_all_vehicles:
            single_vehicle.gatepass_vehicle_payment_method = "33ad20be-3a89-4deb-ab71-7510ba51677e"
            single_vehicle.updated_at = datetime.now()

    else:
        pass

    try:
        db.session.commit()
        close(db)

        try:
            send_email(email_data)
        except Exception:
            pass

        output = []
        output.append(
            "Payment for the booking has been successfully carried out.")
        return jsonify({"message": output}), 200

    except Exception as e:
        print(e)
        db.session.rollback()
        close(db)

        output = []
        output.append("There was a slight issue paying for the booking.")
        return jsonify({"message": output}), 422


@app.route("/bookings/payment/new", methods=["POST"])
def pay_for_booking():
    messages = []

    try:
        request.json["booking_id"].strip()
        if not request.json["booking_id"]:
            messages.append("Booking ID is empty.")
    except KeyError as e:
        messages.append("Booking ID is missing.")

    if request.json["session_id"] == "Mpesa":
        get_booking_id = db.session.query(Booking) \
            .filter(Booking.deletion_marker == None) \
            .filter(Booking.booking_ref_code == request.json["booking_id"]) \
            .first()

        booking_id = get_booking_id.booking_public_id
        booking_ref_code = get_booking_id.booking_ref_code

    else:
        booking_id = request.json["booking_id"]
        booking_ref_code = request.json["booking_ref_code"]


    try:
        request.json["payment_method"].strip()
        if not request.json["payment_method"]:
            messages.append("Payment method is empty.")
    except KeyError as e:
        messages.append("Payment method is missing.")

    try:
        str(request.json["booking_amount"]).strip()
        if not request.json["booking_amount"]:
            messages.append("Booking amount is empty.")
    except KeyError as e:
        messages.append("Booking amount is missing.")

    try:
        str(request.json["payment_amount"]).strip()
        if not request.json["payment_amount"]:
            messages.append("Payment amount is empty.")
    except KeyError as e:
        messages.append("Payment amount is missing.")

    try:
        request.json["session_id"].strip()
        session_id = request.json["session_id"]
    except KeyError as e:
        session_id = None

    if messages:
        return jsonify({"messages": messages}), 422

    if float(request.json["payment_amount"]) <= 0:
        message = []
        message.append("The payment amount cannot be less than or equal to 0.")
        return jsonify({"message": message}), 422

    try:
        phone_number = request.json["phone"]
    except (KeyError) as phone_error:
        print(str(phone_error))
        phone_number = None

    try:
        mpesa_reference = request.json["mpesa_ref"]
    except (KeyError) as mpesa_error:
        print(str(mpesa_error))
        mpesa_reference = None

    try:
        card_first_four = request.json["first_four"]
        card_last_four = request.json["last_four"]
    except (KeyError) as card_error:
        print(str(card_error))
        card_first_four = None
        card_last_four = None

    # added to manage subsequent payment for a card payment then paybill payment to avoid getting gateway detais
    get_gateway = db.session.query(PaymentGateway) \
        .filter(PaymentGateway.payment_method == request.json["payment_method"]) \
        .filter(PaymentGateway.deletion_marker == None) \
        .first()
    try:
        if get_gateway:
            payment_gateway = request.json["payment_gateway"][
                "payment_gateway_public_id"]
        else:
            payment_gateway = None
    except Exception:
        payment_gateway = None

    payment_method = db.session.query(PaymentMethod) \
        .filter(PaymentMethod.deletion_marker == None) \
        .filter(PaymentMethod.payment_method_public_id == request.json["payment_method"]) \
        .first()

    if payment_method:
        pass
    else:
        message = []
        message.append(
            "The selected payment method does not exist in the system.")
        return jsonify({"message": request.json["payment_method"]}), 422

    return_bookings = db.session.query(Booking) \
        .join(BookingType, Booking.booking_type == BookingType.booking_type_public_id) \
        .join(BookingStatus, Booking.status == BookingStatus.booking_status_public_id) \
        .join(Detail, Booking.booking_public_id == Detail.booking_id) \
        .add_columns(Booking.booking_public_id, Booking.booking_type, Booking.booking_check_in_date, \
                     Booking.booking_check_out_date, Booking.actual_booking_check_in_date,
                     Booking.actual_booking_check_out_date, \
                     Booking.booking_done_by, Booking.checked_in, Booking.checked_out, Booking.booking_ref_code,
                     Booking.session_id, \
                     Booking.created_at, Booking.updated_at, Booking.status, Booking.payment_status, \
                     Booking.currency, \
                     BookingType.booking_type_name, \
                     BookingStatus.booking_status_name, \
                     Detail.first_name, Detail.last_name, Detail.email_address, Detail.phone_number, \
                     Detail.address, Detail.additional_note) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == booking_id) \
        .all()

    if not return_bookings:
        output = []
        output.append(
            "The selected booking does not appear to exist in the system.")
        return jsonify({"message": output}), 200
    return_data = {}
    bookingTotal(return_data, booking_id)

    total = round(return_data["total_cost"], 2)

    ##### Handling requests from the Online checkout service
    if request.json["session_id"] == "Mpesa":
        check_payment = db.session.query(BookingPayment) \
            .filter(BookingPayment.mpesa_reference == mpesa_reference) \
            .first()

        if check_payment:
            message = []
            message.append("The booking has already been paid for.")
            return jsonify({"message": message}), 200

        else:
            transaction_id = str(uuid.uuid4())

            ## 1c9d01cc-ef31-4757-a390-10c765fcecab
            # payment_method = request.json["payment_method"],
            payment = BookingPayment(
                booking_payment_public_id=str(uuid.uuid4()),
                booking_id=booking_id,
                transaction_id=transaction_id,
                payment_method="1c9d01cc-ef31-4757-a390-10c765fcecab",
                payment_currency="162fface-f5f1-41de-913b-d2bb784dda3a",
                phone_number=phone_number,
                mpesa_reference=mpesa_reference,
                card_first_four=card_first_four,
                card_last_four=card_last_four,
                booking_amount=total,
                amount_paid=request.json["payment_amount"],
                session_id=session_id)

            db.session.add(payment)
            #Add donations payment status below this line

            donations_amount = db.session.query(func.sum(Donation.amount)) \
                .filter(Donation.deletion_marker == None) \
                .filter(Donation.booking_public_id == booking_ref_code) \
                .scalar() or 0

            payment_excess = request.json["payment_amount"] - round(return_data["total_cost"], 2)

            if round(donations_amount, 2) <= round(payment_excess, 2):
                db.session.query(Donation) \
                    .filter(Donation.deletion_marker == None) \
                    .filter(Donation.booking_public_id == booking_ref_code) \
                    .update({Donation.payment_status: 1})
            
            #Get sum of transaction_original_cost for a specific booking
            transaction_original_cost = db.session.query(FunctionDef.sum(Transaction.transaction_original_cost)) \
            .filter(Transaction.deletion_marker == None) \
            .filter(Transaction.booking_id == return_bookings.booking_public_id) \
            .scalar()

            # Get sum of transaction_total for the same booking
            transaction_total = db.session.query(FunctionDef.sum(Transaction.transaction_total)) \
            .filter(Transaction.deletion_marker == None) \
            .filter(Transaction.booking_id == return_bookings.booking_public_id) \
            .scalar()

            # Get sum of donations amount for the booking
            donations_amount = db.session.query(FunctionDef.sum(Donation.amount)) \
            .filter(Donation.booking_id == request.json["booking_id"]) \
            .scalar()

            # Calculate the transaction balance
            transaction_balance = transaction_total - transaction_original_cost

            # Check if balance and donations match
            if transaction_balance > 0 and transaction_balance == donations_amount:
                # Update Donation to set payment_status = 1 where booking_id matches
                db.session.query(Donation) \
                .filter(Donation.booking_id == request.json["booking_id"]) \
                .update({Donation.payment_status: 1})
                # Commit the update to the database
                db.session.commit()
                
            print("@#####transaction_balance", transaction_balance, flush=True)
            print("@#####donations_amount", donations_amount, flush=True)
            print("@#####transaction_original_cost", transaction_original_cost, flush=True)
   
            return_booking = db.session.query(Booking) \
                .filter(Booking.deletion_marker == None) \
                .filter(Booking.booking_public_id == booking_id) \
                .first()

            get_past_payments = db.session.query(Transaction) \
                .filter(Transaction.deletion_marker == None) \
                .filter(Transaction.booking_id == booking_id) \
                .all()

            if len(get_past_payments) == 0:
                balance = float(total) - float(request.json["payment_amount"])

                ## Buying and selling rate are set to 1 since Mpesa payment is assumed to be in KES
                transaction = Transaction(
                    transaction_booking_public_id=transaction_id,
                    booking_id=booking_id,
                    transaction_original_cost=total,
                    transaction_total=request.json["payment_amount"],
                    transaction_balance=balance,
                    transaction_payment_method=
                    "1c9d01cc-ef31-4757-a390-10c765fcecab",
                    transaction_payment_currency=
                    "162fface-f5f1-41de-913b-d2bb784dda3a",
                    transaction_date=datetime.now(),
                    transaction_total_currency=return_booking.currency,
                    payment_currency_buying_rate_at_time=1,
                    payment_currency_selling_rate_at_time=1,
                    session_id=session_id,
                    created_at=datetime.now(),
                    updated_at=datetime.now())

                db.session.add(transaction)

                get_to_invoice = db.session.query(Invoice) \
                    .filter(Invoice.deletion_marker == None) \
                    .filter(Invoice.booking_id == booking_id) \
                    .first()

                if get_to_invoice:
                    get_to_invoice.deletion_marker = 1
                    get_to_invoice.updated_at = datetime.now()

                if float(total) <= float(request.json["payment_amount"]):
                    return_booking.payment_status = 1
                    return_booking.updated_at = datetime.now()
                    return_booking.status = get_booking_status_id("Confirmed")
                ## To prevent update of booking session_id
                # return_booking.session_id = session_id
                elif float(total) > float(request.json["payment_amount"]):
                    return_booking.payment_status = 2
                    return_booking.updated_at = datetime.now()
                    return_booking.status = get_booking_status_id("Deposit")
            ## To prevent update of booking session_id
            # return_booking.session_id = session_id

            elif len(get_past_payments) > 0:
                total_payments = []
                for each_payment in get_past_payments:
                    total_payments.append(float(
                        each_payment.transaction_total))

                past_total = sum(total_payments)

                pending = float(total) - past_total
                balance = pending - float(request.json["payment_amount"])

                ## Buying and selling rate are set to 1 since Mpesa payment is assumed to be in KES
                transaction = Transaction(
                    transaction_booking_public_id=transaction_id,
                    booking_id=booking_id,
                    transaction_original_cost=total,
                    transaction_total=request.json["payment_amount"],
                    transaction_balance=balance,
                    transaction_payment_method=
                    "1c9d01cc-ef31-4757-a390-10c765fcecab",
                    transaction_payment_currency=
                    "162fface-f5f1-41de-913b-d2bb784dda3a",
                    transaction_date=datetime.now(),
                    transaction_total_currency=return_booking.currency,
                    payment_currency_buying_rate_at_time=1,
                    payment_currency_selling_rate_at_time=1,
                    session_id=session_id,
                    created_at=datetime.now(),
                    updated_at=datetime.now())

                db.session.add(transaction)

                get_to_invoice = db.session.query(Invoice) \
                    .filter(Invoice.deletion_marker == None) \
                    .filter(Invoice.booking_id == booking_id) \
                    .first()

                if get_to_invoice:
                    get_to_invoice.deletion_marker = 1
                    get_to_invoice.updated_at = datetime.now()

                if pending <= float(request.json["payment_amount"]):
                    return_booking.payment_status = 1
                    return_booking.updated_at = datetime.now()
                    return_booking.status = get_booking_status_id("Confirmed")
                ## To prevent update of booking session_id
                # return_booking.session_id = session_id
                elif pending > float(request.json["payment_amount"]):
                    return_booking.payment_status = 2
                    return_booking.updated_at = datetime.now()
                    return_booking.status = get_booking_status_id("Deposit")
            ## To prevent update of booking session_id
            # return_booking.session_id = session_id

            get_all_inventory = db.session.query(Inventory) \
                .filter(Inventory.deletion_marker == None) \
                .filter(Inventory.booking_id == booking_id) \
                .all()

            if get_all_inventory:
                for single_inventory in get_all_inventory:
                    single_inventory.inventory_payment_method = "1c9d01cc-ef31-4757-a390-10c765fcecab"
                    single_inventory.updated_at = datetime.now()

            else:
                pass

            get_all_facilities = db.session.query(Facility) \
                .filter(Facility.deletion_marker == None) \
                .filter(Facility.booking_id == booking_id) \
                .all()

            if get_all_facilities:
                for single_facility in get_all_facilities:
                    single_facility.facility_payment_method = "1c9d01cc-ef31-4757-a390-10c765fcecab"
                    single_facility.updated_at = datetime.now()

            else:
                pass

            gatepass = db.session.query(Gatepass) \
                .filter(Gatepass.deletion_marker == None) \
                .filter(Gatepass.booking_id == booking_id) \
                .first()

            gatepass_id = gatepass.gatepass_public_id

            get_all_guests = db.session.query(GatepassGuest) \
                .filter(GatepassGuest.deletion_marker == None) \
                .filter(GatepassGuest.gatepass_id == gatepass_id) \
                .all()

            if get_all_guests:
                for single_guest in get_all_guests:
                    single_guest.gatepass_guest_payment_method = "1c9d01cc-ef31-4757-a390-10c765fcecab"
                    single_guest.updated_at = datetime.now()

            else:
                pass

            get_all_vehicles = db.session.query(GatepassVehicle) \
                .filter(GatepassVehicle.deletion_marker == None) \
                .filter(GatepassVehicle.gatepass_id == gatepass_id) \
                .all()

            if get_all_vehicles:
                for single_vehicle in get_all_vehicles:
                    single_vehicle.gatepass_vehicle_payment_method = "1c9d01cc-ef31-4757-a390-10c765fcecab"
                    single_vehicle.updated_at = datetime.now()

            else:
                pass

            try:
                db.session.commit()
                close(db)
                message = []
                message.append("The booking has been paid for.")
                return jsonify({"message": message}), 200

            except Exception as e:
                db.session.rollback()
                close(db)
                message = []
                message.append("There was an error paying for the booking.")
                return jsonify({"message": message, "error": str(e)}), 422

    ##### Handling requests from the UI
    elif request.json[
        "payment_method"] == "cbff45f3-4f12-41da-8b4a-bf308112f032":
        check_existing_payment = db.session.query(BookingPayment) \
            .filter(BookingPayment.deletion_marker == None) \
            .filter(BookingPayment.payment_method == request.json["payment_method"]) \
            .filter(BookingPayment.booking_id == request.json["booking_id"]) \
            .filter(BookingPayment.amount_paid == request.json["payment_amount"]) \
            .first()

        if not check_existing_payment:
            message = []
            message.append(
                "The Mpesa payment was not successful. Please try again.")
            return jsonify({"message": message}), 422

        else:
            message = []
            message.append("Mpesa payment successful.")
            return jsonify({"message": message}), 200

    elif request.json[
        "payment_method"] != "cbff45f3-4f12-41da-8b4a-bf308112f032":
        transaction_id = str(uuid.uuid4())

        payment = BookingPayment(booking_payment_public_id=str(uuid.uuid4()),
                                 booking_id=request.json["booking_id"],
                                 transaction_id=transaction_id,
                                 payment_method=request.json["payment_method"],
                                 payment_currency=request.json["currency_id"],
                                 phone_number=phone_number,
                                 mpesa_reference=mpesa_reference,
                                 card_first_four=card_first_four,
                                 card_last_four=card_last_four,
                                 booking_amount=total,
                                 amount_paid=request.json["payment_amount"],
                                 session_id=session_id)

        db.session.add(payment)

        return_booking = db.session.query(Booking) \
            .filter(Booking.deletion_marker == None) \
            .filter(Booking.booking_public_id == request.json["booking_id"]) \
            .first()

        get_past_payments = db.session.query(Transaction) \
            .filter(Transaction.deletion_marker == None) \
            .filter(Transaction.booking_id == request.json["booking_id"]) \
            .all()

        get_exchange_rate = requests.get(
            get_buy_sell_rate.format(request.json["currency_id"]))
        buying_rate = get_exchange_rate.json(
        )["data"][0]["currency_buy_amount"]
        selling_rate = get_exchange_rate.json(
        )["data"][0]["currency_sell_amount"]

        if len(get_past_payments) == 0:
            converted_transaction_amount = currencyHandler(
                return_booking.currency, request.json["currency_id"],
                request.json["payment_amount"])

            balance = float(request.json["booking_amount"]) - float(
                converted_transaction_amount)

            transaction = Transaction(
                transaction_booking_public_id=transaction_id,
                booking_id=request.json["booking_id"],
                transaction_original_cost=request.json["booking_amount"],
                transaction_total=request.json["payment_amount"],
                transaction_balance=balance,
                transaction_payment_method=request.json["payment_method"],
                transaction_payment_gateway=payment_gateway,
                transaction_payment_currency=request.json["currency_id"],
                transaction_date=datetime.now(),
                transaction_total_currency=return_booking.currency,
                payment_currency_buying_rate_at_time=buying_rate,
                payment_currency_selling_rate_at_time=selling_rate,
                session_id=session_id,
                created_at=datetime.now(),
                updated_at=datetime.now())

            db.session.add(transaction)

            get_to_invoice = db.session.query(Invoice) \
                .filter(Invoice.deletion_marker == None) \
                .filter(Invoice.booking_id == request.json["booking_id"]) \
                .first()

            if get_to_invoice:
                get_to_invoice.deletion_marker = 1
                get_to_invoice.updated_at = datetime.now()

            # if float(request.json["booking_amount"]) <= float(request.json["payment_amount"]):
            if float(request.json["booking_amount"]) <= float(
                    converted_transaction_amount):
                return_booking.payment_status = 1
                return_booking.updated_at = datetime.now()
                return_booking.status = get_booking_status_id("Confirmed")
            ## To prevent update of booking session_id
            # return_booking.session_id = session_id
            elif float(request.json["booking_amount"]) > float(
                    converted_transaction_amount):
                # Prevents negligible amounts like 0.0233 from indicating that a booking has not been paid for
                if round(balance) <= 0:
                    return_booking.payment_status = 1
                else:
                    return_booking.payment_status = 2
                return_booking.updated_at = datetime.now()
                return_booking.status = get_booking_status_id("Deposit")
        ## To prevent update of booking session_id
        # return_booking.session_id = session_id

        elif len(get_past_payments) > 0:
            total_payments = []
            for each_payment in get_past_payments:
                if each_payment.transaction_payment_currency:
                    converted_transaction_total = currencyHandler(
                        return_booking.currency,
                        each_payment.transaction_payment_currency,
                        each_payment.transaction_total)
                else:
                    converted_transaction_total = currencyHandler(
                        return_booking.currency, return_booking.currency,
                        each_payment.transaction_total)

                total_payments.append(float(converted_transaction_total))

            past_total = sum(total_payments)

            converted_transaction_amount = currencyHandler(
                return_booking.currency, request.json["currency_id"],
                request.json["payment_amount"])

            pending = float(request.json["booking_amount"]) - float(past_total)
            balance = pending - float(converted_transaction_amount)

            transaction = Transaction(
                transaction_booking_public_id=transaction_id,
                booking_id=request.json["booking_id"],
                transaction_original_cost=request.json["booking_amount"],
                transaction_total=request.json["payment_amount"],
                transaction_balance=balance,
                transaction_payment_method=request.json["payment_method"],
                transaction_payment_gateway=payment_gateway,
                transaction_payment_currency=request.json["currency_id"],
                transaction_date=datetime.now(),
                transaction_total_currency=return_booking.currency,
                payment_currency_buying_rate_at_time=buying_rate,
                payment_currency_selling_rate_at_time=selling_rate,
                session_id=session_id,
                created_at=datetime.now(),
                updated_at=datetime.now())

            db.session.add(transaction)

            get_to_invoice = db.session.query(Invoice) \
                .filter(Invoice.deletion_marker == None) \
                .filter(Invoice.booking_id == request.json["booking_id"]) \
                .first()

            if get_to_invoice:
                get_to_invoice.deletion_marker = 1
                get_to_invoice.updated_at = datetime.now()

            if pending <= float(converted_transaction_amount):
                return_booking.payment_status = 1
                return_booking.updated_at = datetime.now()
                return_booking.status = get_booking_status_id("Confirmed")
            ## To prevent update of booking session_id
            # return_booking.session_id = session_id
            elif pending > float(converted_transaction_amount):
                # return_booking.payment_status = 2
                # Prevents negligible amounts like 0.0233 from indicating that a booking has not been paid for
                if round(balance) <= 0:
                    return_booking.payment_status = 1
                else:
                    return_booking.payment_status = 2
                return_booking.updated_at = datetime.now()
                return_booking.status = get_booking_status_id("Deposit")
        ## To prevent update of booking session_id
        # return_booking.session_id = session_id

        get_all_inventory = db.session.query(Inventory) \
            .filter(Inventory.deletion_marker == None) \
            .filter(Inventory.booking_id == request.json["booking_id"]) \
            .all()

        if get_all_inventory:
            for single_inventory in get_all_inventory:
                single_inventory.inventory_payment_method = request.json[
                    "payment_method"]
                single_inventory.updated_at = datetime.now()

        else:
            pass

        get_all_facilities = db.session.query(Facility) \
            .filter(Facility.deletion_marker == None) \
            .filter(Facility.booking_id == request.json["booking_id"]) \
            .all()

        if get_all_facilities:
            for single_facility in get_all_facilities:
                single_facility.facility_payment_method = request.json[
                    "payment_method"]
                single_facility.updated_at = datetime.now()

        else:
            pass

        gatepass = db.session.query(Gatepass) \
            .filter(Gatepass.deletion_marker == None) \
            .filter(Gatepass.booking_id == request.json["booking_id"]) \
            .first()

        gatepass_id = gatepass.gatepass_public_id

        get_all_guests = db.session.query(GatepassGuest) \
            .filter(GatepassGuest.deletion_marker == None) \
            .filter(GatepassGuest.gatepass_id == gatepass_id) \
            .all()

        if get_all_guests:
            for single_guest in get_all_guests:
                single_guest.gatepass_guest_payment_method = request.json[
                    "payment_method"]
                single_guest.updated_at = datetime.now()

        else:
            pass

        get_all_vehicles = db.session.query(GatepassVehicle) \
            .filter(GatepassVehicle.deletion_marker == None) \
            .filter(GatepassVehicle.gatepass_id == gatepass_id) \
            .all()

        if get_all_vehicles:
            for single_vehicle in get_all_vehicles:
                single_vehicle.gatepass_vehicle_payment_method = request.json[
                    "payment_method"]
                single_vehicle.updated_at = datetime.now()

        else:
            pass

        try:
            db.session.commit()
            close(db)
            message = []
            message.append("The booking has been paid for.")
            return jsonify({"message": message}), 200

        except Exception as e:
            db.session.rollback()
            close(db)

            message = []
            message.append("There was an error paying for the booking.")

            return jsonify({"message": message, "error": str(e)}), 422


@app.route("/bookings/payment/defer", methods=["POST"])
def defer_booking_payment():
    messages = []

    try:
        request.json["booking_id"].strip()
        if not request.json["booking_id"]:
            messages.append("Booking ID is empty.")
    except KeyError as e:
        messages.append("Booking ID is missing.")

    try:
        request.json["session_id"].strip()
        if not request.json["session_id"]:
            messages.append("Session ID is empty.")
    except KeyError as e:
        messages.append("Session ID is missing.")

    if messages:
        return jsonify({"messages": messages}), 422

    get_booking = db.session.query(Booking) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == request.json["booking_id"]) \
        .first()

    if not get_booking:
        message = []
        message.append("The selected booking does not appear to exist.")
        return jsonify({"message": message}), 422

    check_if_partner_booking = db.session.query(Partner) \
        .filter(Partner.deletion_marker == None) \
        .filter(Partner.booking_id == get_booking.booking_public_id) \
        .first()

    if not check_if_partner_booking:
        get_booking.status = get_booking_status_id("Unconfirmed")

    else:
        get_partner = requests.get(
            get_partner_details.format(check_if_partner_booking.partner_id))

        if get_partner.json()["type_public_id"] == "90b00b2b":
            get_booking.status = get_booking_status_id("To Invoice")
            ## To Invoice
            get_booking.payment_status = 4

            invoice = Invoice(invoice_public_id=str(uuid.uuid4()),
                              booking_id=get_booking.booking_public_id,
                              session_id=request.json["session_id"],
                              created_at=datetime.now())

            db.session.add(invoice)

        else:
            get_booking.status = get_booking_status_id("Unconfirmed")

    try:
        db.session.commit()
        close(db)

        message = []
        message.append("Booking payment deferred.")
        return jsonify({"message": message}), 200

    except Exception:
        db.session.rollback()
        close(db)

        message = []
        message.append("Unable to defer the booking payment. Try again later.")
        return jsonify({"message": message}), 422


@app.route("/bookings/paid", methods=["POST"])
def mark_booking_as_paid():
    messages = []

    try:
        request.json["booking_id"].strip()
        if not request.json["booking_id"]:
            messages.append("Booking ID is empty.")
    except KeyError as e:
        messages.append("Booking ID is missing.")

    try:
        request.json["session_id"].strip()
        if not request.json["session_id"]:
            messages.append("Session ID is empty.")
    except KeyError as e:
        messages.append("Session ID is missing.")

    if messages:
        return jsonify({"messages": messages}), 422

    get_booking = db.session.query(Booking) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == request.json["booking_id"]) \
        .filter(Booking.status != get_booking_status_id("Cancelled")) \
        .first()

    if not get_booking:
        message = []
        message.append(
            "The selected booking either does not exist or has been cancelled."
        )
        return jsonify({"message": message}), 412

    get_booking.status = get_booking_status_id("Confirmed")
    get_booking.payment_status = 1
    get_booking.updated_at = datetime.now()

    booking_activity = BookingActivity(
        booking_activity_public_id=str(uuid.uuid4()),
        booking_id=request.json["booking_id"],
        booking_activity_description="Booking marked as paid",
        session_id=request.json["session_id"],
        created_at=datetime.now())

    db.session.add(booking_activity)

    try:
        db.session.commit()
        close(db)

        message = []
        message.append("Booking marked as paid.")
        return jsonify({"message": message}), 200

    except Exception:
        db.session.rollback()
        close(db)

        message = []
        message.append("Unable to mark the booking as paid. Try again later.")
        return jsonify({"message": message}), 422


@app.route("/bookings/compliments", methods=["POST"])
def mark_as_complimentary():
    messages = []

    try:
        request.json["booking_id"].strip()
        if not request.json["booking_id"]:
            messages.append("Booking ID is empty.")
    except KeyError as e:
        messages.append("Booking ID is missing.")

    try:
        request.json["session_id"].strip()
        if not request.json["session_id"]:
            messages.append("Session ID is empty.")
    except KeyError as e:
        messages.append("Session ID is missing.")

    if messages:
        return jsonify({"messages": messages}), 422

    get_booking = db.session.query(Booking) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == request.json["booking_id"]) \
        .filter(Booking.status != get_booking_status_id("Cancelled")) \
        .first()

    if not get_booking:
        message = []
        message.append(
            "The selected booking either does not exist or has been cancelled."
        )
        return jsonify({"message": message}), 412

    get_booking_gatepass = db.session.query(Gatepass) \
        .filter(Gatepass.deletion_marker == None) \
        .filter(Gatepass.booking_id == request.json["booking_id"]) \
        .filter(Gatepass.status != get_booking_status_id("Updated")) \
        .first()

    get_gatepass_guests = db.session.query(GatepassGuest) \
        .filter(GatepassGuest.deletion_marker == None) \
        .filter(GatepassGuest.gatepass_id == get_booking_gatepass.gatepass_public_id) \
        .all()

    for single_gatepass_guest in get_gatepass_guests:
        single_gatepass_guest.gatepass_discount_rate = 100
        single_gatepass_guest.updated_at = datetime.now()

    get_gatepass_vehicles = db.session.query(GatepassVehicle) \
        .filter(GatepassVehicle.deletion_marker == None) \
        .filter(GatepassVehicle.gatepass_id == get_booking_gatepass.gatepass_public_id) \
        .all()

    for single_gatepass_vehicle in get_gatepass_vehicles:
        single_gatepass_vehicle.gatepass_vehicle_discount_rate = 100
        single_gatepass_vehicle.updated_at = datetime.now()

    get_booking_inventory = db.session.query(Inventory) \
        .filter(Inventory.deletion_marker == None) \
        .filter(Inventory.booking_id == request.json["booking_id"]) \
        .filter(Inventory.status != get_booking_status_id("Updated")) \
        .all()

    for single_inventory in get_booking_inventory:
        single_inventory.inventory_discount_rate = 100
        single_inventory.updated_at = datetime.now()

    get_booking_facilities = db.session.query(Facility) \
        .filter(Facility.deletion_marker == None) \
        .filter(Facility.booking_id == request.json["booking_id"]) \
        .filter(Facility.status != get_booking_status_id("Updated")) \
        .all()

    for single_facility in get_booking_facilities:
        single_facility.facility_discount_rate = 100
        single_facility.updated_at = datetime.now()

    get_booking.status = get_booking_status_id("Complimentary")
    ## Complimentary
    get_booking.payment_status = 3
    get_booking.updated_at = datetime.now()

    booking_activity = BookingActivity(
        booking_activity_public_id=str(uuid.uuid4()),
        booking_id=request.json["booking_id"],
        booking_activity_description="Booking marked as free-of-charge",
        session_id=request.json["session_id"],
        created_at=datetime.now())

    db.session.add(booking_activity)

    try:
        db.session.commit()
        close(db)

        message = []
        message.append("Booking marked as free-of-charge.")
        return jsonify({"message": message}), 200

    except Exception:
        db.session.rollback()
        close(db)

        message = []
        message.append(
            "Unable to mark the booking as free-of-charge. Try again later.")
        return jsonify({"message": message}), 422


@app.route("/bookings/payment/mpesa/new", methods=["POST"])
def pay_for_booking_mpesa():
    messages = []

    try:
        request.json["phone"].strip()
        if not request.json["phone"]:
            messages.append("Phone number is empty.")
    except KeyError as e:
        messages.append("Phone number is missing.")

    try:
        request.json["booking_id"].strip()
        if not request.json["booking_id"]:
            messages.append("Booking ID is empty.")
    except KeyError as e:
        messages.append("Booking ID is missing.")

    try:
        # str(request.json["payment_amount"]).strip()
        # if not request.json["payment_amount"]:
        # 	messages.append("Payment amount is empty.")
        data_payment_amount = request.json["payment_amount"]
    except KeyError as e:
        # messages.append("Payment amount is missing.")
        data_payment_amount = None

    try:
        request.json["session_id"].strip()
        if not request.json["session_id"]:
            messages.append("Session ID is empty.")
    except KeyError as e:
        messages.append("Session ID is missing.")

    if messages:
        message = []
        message.append("You appear to be missing some data. Please try again.")
        return jsonify({"messages": messages}), 422

    return_bookings = db.session.query(Booking) \
        .join(BookingType, Booking.booking_type == BookingType.booking_type_public_id) \
        .join(BookingStatus, Booking.status == BookingStatus.booking_status_public_id) \
        .join(Detail, Booking.booking_public_id == Detail.booking_id) \
        .add_columns(Booking.booking_public_id, Booking.booking_type, Booking.booking_check_in_date, \
                     Booking.booking_check_out_date, Booking.actual_booking_check_in_date,
                     Booking.actual_booking_check_out_date, \
                     Booking.booking_done_by, Booking.checked_in, Booking.checked_out, Booking.booking_ref_code,
                     Booking.session_id, \
                     Booking.created_at, Booking.updated_at, Booking.status, Booking.payment_status, \
                     Booking.currency, Booking.currency_buying_rate_at_time, Booking.currency_selling_rate_at_time, \
                     BookingType.booking_type_name, \
                     BookingStatus.booking_status_name, \
                     Detail.first_name, Detail.last_name, Detail.email_address, Detail.phone_number, \
                     Detail.address, Detail.additional_note) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == request.json["booking_id"]) \
        .first()

    if not return_bookings:
        output = []
        output.append(
            "The selected booking does not appear to exist in the system.")
        return jsonify({"message": output}), 200
    else:
        output = []
        # id_array = []
        # inventory_total_cost_array = []
        # total_cost_array = []

        # for single in return_bookings:
        # guest_array = []
        # guest_sum = []

        mpesa_transaction_ref = return_bookings.booking_ref_code

        booking_currency = return_bookings.currency
        buying_rate = return_bookings.currency_buying_rate_at_time
        selling_rate = return_bookings.currency_selling_rate_at_time

        booking_details = {}
        bookingTotal(booking_details, return_bookings.booking_public_id)

    ## Handling UI requests
    if data_payment_amount:
        json_payment_amount = request.json["payment_amount"]

        # payment_amount = currencyHandler("162fface-f5f1-41de-913b-d2bb784dda3a", booking_currency, json_payment_amount)
        payment_amount = currencyHandler(
            "162fface-f5f1-41de-913b-d2bb784dda3a",
            "162fface-f5f1-41de-913b-d2bb784dda3a", json_payment_amount)

    ## Public portal
    else:
        get_all_booking_donations = db.session.query(Donation) \
            .filter(Donation.deletion_marker == None) \
            .filter(Donation.booking_id == request.json["booking_id"]) \
            .all()

        donations = []
        if get_all_booking_donations:
            for single_donation in get_all_booking_donations:
                donation_amount = currencyPostProcessor("162fface-f5f1-41de-913b-d2bb784dda3a",
                                                        single_donation.donation_currency,
                                                        single_donation.donation_amount, \
                                                        single_donation.currency_buying_amount,
                                                        single_donation.currency_selling_amount)

                donations.append(donation_amount)

        donation_total = sum(donations)

        # payment_amount = round(sum(total_cost_array), 2)
        converted_amount = currencyPostProcessor(
            "162fface-f5f1-41de-913b-d2bb784dda3a", booking_currency,
            booking_details["total_cost"], buying_rate, selling_rate)
        payment_amount = math.ceil(
            round((converted_amount + donation_total), 2))
        


    online_payment_donations = []
    try:
        online_payment_donation = request.json["public_donations"]
        if len(online_payment_donation) > 0:
            for single_online_donation in online_payment_donation:

                try:
                    transaction_booking_date = return_bookings.created_at.strftime(
                        "%Y-%m-%d")
                    online_donation_amount = currencyPostProcessorInvoice("162fface-f5f1-41de-913b-d2bb784dda3a",
                                                                          single_online_donation[
                                                                              'new_donation_currency_id'],
                                                                          single_online_donation['new_donation_amount'], \
                                                                          single_online_donation[
                                                                              'new_donation_currency_buy_rate'],
                                                                          single_online_donation[
                                                                              'new_donation_currency_sell_rate'],
                                                                          transaction_booking_date)

                    online_payment_donations.append(online_donation_amount)
                    json_data = {
                        "first_name":
                            return_bookings.first_name,
                        "sur_name":
                            return_bookings.last_name,
                        "email":
                            return_bookings.email_address,
                        "phone_number":
                            return_bookings.phone_number,
                        "frequency":
                            "n4383h334",
                        "amount":
                            single_online_donation['new_donation_amount'],
                        "payment_method":
                            "enjkr4k4",
                        "payment_option":
                            "798e770d-5d6",
                        "anonymity":
                            "74a27ee4",
                        "gender":
                            "Female",
                        "title":
                            "Ms.",
                        "city":
                            return_bookings.city,
                        "dob":
                            "1900-01-01",
                        "country":
                            return_bookings.country,
                        "currency":
                            single_online_donation['new_donation_currency_id'],
                        "booking_public_id":
                            request.json["booking_id"],
                        "booking_ref_code":
                            return_bookings.booking_ref_code
                    }
                    donation_request = requests.post(post_donation,
                                                     json=json_data)
                    try:
                        donation = Donation(
                            donation_public_id=donation_request.json()
                            ["donation_ref_code"],
                            booking_id=request.json["booking_id"],
                            donation_currency=single_online_donation[
                                'new_donation_currency_id'],
                            donation_cause="798e770d-5d6",
                            donation_source='Mpesa Checkout',
                            donation_amount=single_online_donation[
                                'new_donation_amount'],
                            currency_buying_amount=single_online_donation[
                                'new_donation_currency_buy_rate'],
                            currency_selling_amount=single_online_donation[
                                'new_donation_currency_sell_rate'],
                            created_at=datetime.now())

                        db.session.add(donation)
                        db.session.commit()
                    except Exception as e:
                        print('error while saving checkout donation')
                        print(e)
                except Exception as e:
                    print('error')
                    print(e)

            online_donation_total = sum(online_payment_donations)
            payment_amount = math.ceil(payment_amount + online_donation_total)
    
    except KeyError as e:
        pass
   
    get_past_payments = db.session.query(Transaction) \
        .filter(Transaction.deletion_marker == None) \
        .filter(Transaction.booking_id == return_bookings.booking_public_id) \
        .all()
    if len(get_past_payments) > 0:
        total_payments = []
        for each_payment in get_past_payments:
            if each_payment.transaction_payment_currency:
                converted_transaction_total = currencyHandler(
                    return_bookings.currency,
                    each_payment.transaction_payment_currency,
                    each_payment.transaction_total)
            else:
                converted_transaction_total = currencyHandler(
                    return_bookings.currency, return_bookings.currency,
                    each_payment.transaction_total)

            total_payments.append(float(converted_transaction_total))

        past_total = sum(total_payments)

        if past_total == payment_amount:
            message = []
            message.append("The booking has already been paid for.")
            return jsonify({"message": message}), 200
   
    if int(payment_amount) <= 70000:
        try:
            payload = {
                "phone_number": request.json["phone"],
                "payment_to": "Booking",
                "amount": payment_amount,
                "reference": mpesa_transaction_ref,
                "booking_id": request.json["booking_id"],
                "transaction_reference": "B-" + mpesa_transaction_ref
            }
            details = {
                'urls': {
                    'oauthUrl': "https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials",
                    'STKPush': "https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest",
                    'STKPushQuery': "https://api.safaricom.co.ke/mpesa/stkpushquery/v1/query",
                    
                    # 'oauthUrl': "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials",
                    # 'STKPush': "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest",
                    # 'STKPushQuery': "https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query",
                },
                'consumer_key': "Kw6sgSpRhHNavlhiXbdSdGh7JygIMAUQ",
                'consumer_secret': "7GU4h4te21WxuKLt",
                'short_code': 964550,
                'main_url': "https://167.99.85.35:5019",
                'server_ip': "bookings.olpejetaconservancy.org",
                'pass_key': "e50a04ce1d7ebcfd693cd90da55a4313104e579e4c1317c8c921b42ed7288de6",
            }
            url = details.get("urls").get("oauthUrl")
            auth = "Basic " + base64.b64encode(
                f"{details.get('consumer_key')}:{details.get('consumer_secret')}".encode()
            ).decode()

            headers = {
                'Authorization': auth,
            }
            # Attempt to get OAuth access token
            response = requests.get(url, headers=headers)
            if response.status_code == 200:
                try:
                    access_token = response.json().get("access_token")
                    phone_number = int(payload.get('phone_number'))
                    amount = payload.get('amount')
                    reference = payload.get('reference')
                    payment_to = payload.get('payment_to')
                    transaction_reference = payload.get('transaction_reference', None)
                    time_stamp = datetime.now().strftime("%Y%m%d%H%M%S")
                    password = base64.b64encode(
                        f"{details['short_code']}{details['pass_key']}{time_stamp}".encode()).decode(
                        'utf-8')

                    headers = {
                        'Content-Type': 'application/json',
                        'Authorization': f'Bearer {access_token}'
                    }

                    mpesa_payload = {
                        "BusinessShortCode": details.get("short_code"),
                        "Password": password,
                        "Timestamp": time_stamp,
                        "TransactionType": "CustomerPayBillOnline",
                        "Amount": amount,
                        "PartyA": phone_number,
                        "PartyB": details.get("short_code"),
                        "PhoneNumber": phone_number,
                        "CallBackURL": "https://bookings.olpejetaconservancy.org:5005/mpesa/callback",
                        "AccountReference": transaction_reference if transaction_reference else "No reference",
                        "TransactionDesc": "Some description here"
                    }

                    print("@#####m-pesa mpesa_payload", mpesa_payload, flush=True)
                    # Make the POST request to initiate STK push
                    response = requests.post(details.get('urls').get('STKPush'), headers=headers, json=mpesa_payload)
                    if response.status_code == 200:
                        try:
                            # Assuming body contains the response from Mpesa
                            body = response.json()
                            # Build and save transaction record to the database using SQLAlchemy
                            checkout_transaction_id = str(uuid.uuid4())
                            next_id = get_next_checkout_transaction_id()
                            print("next_id", next_id, flush=True)

                            transaction = CheckoutTransaction(
                                checkout_transaction_id=next_id,
                                checkout_transaction_public_id=str(uuid.uuid4()),
                                payment_to=payment_to,
                                reference=reference,
                                phone_number=phone_number,
                                amount=amount,
                                merchant_request_id=body.get("MerchantRequestID"),
                                checkout_request_id=body.get("CheckoutRequestID"),
                                created_at=datetime.now(),
                                updated_at=datetime.now()
                            )
                            db.session.add(transaction)
                            db.session.commit()

                            #Add donations payment status below this line
                            # Get sum of transaction_original_cost for a specific booking
                            transaction_original_cost = db.session.query(FunctionDef.sum(Transaction.transaction_original_cost)) \
                            .filter(Transaction.deletion_marker == None) \
                            .filter(Transaction.booking_id == return_bookings.booking_public_id) \
                            .scalar()

                            # Get sum of transaction_total for the same booking
                            transaction_total = db.session.query(FunctionDef.sum(Transaction.transaction_total)) \
                            .filter(Transaction.deletion_marker == None) \
                            .filter(Transaction.booking_id == return_bookings.booking_public_id) \
                            .scalar()

                            # Get sum of donations amount for the booking
                            donations_amount = db.session.query(FunctionDef.sum(Donation.amount)) \
                            .filter(Donation.booking_id == request.json["booking_id"]) \
                            .scalar()

                            # Calculate the transaction balance
                            transaction_balance = transaction_total - transaction_original_cost

                            # Check if balance and donations match
                            if transaction_balance > 0 and transaction_balance == donations_amount:
                                # Update Donation to set payment_status = 1 where booking_id matches
                                db.session.query(Donation) \
                                .filter(Donation.booking_id == request.json["booking_id"]) \
                                .update({Donation.payment_status: 1})
                                # Commit the update to the database
                                db.session.commit()
                            
                            print("i saved data successfully", flush=True)
                            
                            # Print all the attributes of the transaction to troubleshoot missing attributes
                            print(f"""
                            CheckoutTransaction:
                            checkout_transaction_id: {transaction.checkout_transaction_id}
                            checkout_transaction_public_id: {transaction.checkout_transaction_public_id}
                            payment_to: {transaction.payment_to}
                            reference: {transaction.reference}
                            phone_number: {transaction.phone_number}
                            amount: {transaction.amount}
                            merchant_request_id: {transaction.merchant_request_id}
                            checkout_request_id: {transaction.checkout_request_id}
                            created_at: {transaction.created_at}
                            updated_at: {transaction.updated_at}
                            """, flush=True)

                            return jsonify({"message": "Check mobile phone to complete payment"}), 200
                        except Exception as e:
                            print("unable to save data", str(e), flush=True)
                            return jsonify({"message": "Failed to save transaction", "error": str(e)}), 500
                    else:
                        print("status is 500", flush=True)
                        return jsonify({"message": "Failed to process payment"}), 500
                except Exception as e:
                    print("status is exception one", str(e), flush=True)
                    return jsonify({"message": "Error processing payment", "error": str(e)}), 500
            else:
                print("status is exception two", response.text, flush=True)
                return jsonify({"message": "Failed to authenticate", "error": response.text}), response.status_code
        except Exception as e:
            print("major exceprion at mpesa", str(e), flush=True)
            message = []
            message.append(
                "There was an error connecting to the Mpesa payment service. Try again later."
            )
            return jsonify({"message": message}), 422

    elif int(payment_amount) > 70000:
        message = []
        message.append(
            "The amount is greater than KES 70,000. Try another method.")
        return jsonify({
            "message": message,
            "amount": float(payment_amount),
            "currency": booking_currency
        }), 422


@app.route("/iveri-donation/new", methods=["POST"])
def new_iveri_donations():
    return_bookings = db.session.query(Booking) \
        .join(BookingType, Booking.booking_type == BookingType.booking_type_public_id) \
        .join(BookingStatus, Booking.status == BookingStatus.booking_status_public_id) \
        .join(Detail, Booking.booking_public_id == Detail.booking_id) \
        .add_columns(Booking.booking_public_id, Booking.booking_type, Booking.booking_check_in_date, \
                     Booking.booking_check_out_date, Booking.actual_booking_check_in_date,
                     Booking.actual_booking_check_out_date, \
                     Booking.booking_done_by, Booking.checked_in, Booking.checked_out, Booking.booking_ref_code,
                     Booking.session_id, \
                     Booking.created_at, Booking.updated_at, Booking.status, Booking.payment_status, \
                     Booking.currency, Booking.currency_buying_rate_at_time, Booking.currency_selling_rate_at_time, \
                     BookingType.booking_type_name, \
                     BookingStatus.booking_status_name, \
                     Detail.first_name, Detail.last_name, Detail.email_address, Detail.phone_number, \
                     Detail.address, Detail.additional_note, Detail.city, Detail.additional_note, Detail.country) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == request.json["booking_id"]) \
        .first()

    if not return_bookings:
        output = []
        output.append(
            "The selected booking does not appear to exist in the system.")
        return jsonify({"message": output}), 200
    else:
        output = []

    online_payment_donation = request.json["public_donations"]
    try:
        online_payment_donation = request.json["public_donations"]
    except Exception as e:
        online_payment_donation = []

    if len(online_payment_donation) > 0:
        for single_online_donation in online_payment_donation:

            try:
                json_data = {
                    "first_name": return_bookings.first_name,
                    "sur_name": return_bookings.last_name,
                    "email": return_bookings.email_address,
                    "phone_number": return_bookings.phone_number,
                    "frequency": "n4383h334",
                    "amount": single_online_donation['new_donation_amount'],
                    "payment_method": "enjkr4k4",
                    "payment_option": "798e770d-5d6",
                    "anonymity": "74a27ee4",
                    "gender": "Female",
                    "title": "Ms.",
                    "city": return_bookings.city,
                    "dob": "1900-01-01",
                    "country": return_bookings.country,
                    "currency":
                        single_online_donation['new_donation_currency_id'],
                    "booking_public_id": request.json["booking_id"],
                    "booking_ref_code": return_bookings.booking_ref_code
                }
                donation_request = requests.post(post_donation, json=json_data)

                try:
                    donation = Donation(
                        donation_public_id=donation_request.json()
                        ["donation_ref_code"],
                        booking_id=request.json["booking_id"],
                        donation_currency=single_online_donation[
                            'new_donation_currency_id'],
                        donation_cause="798e770d-5d6",
                        donation_source='Iveri',
                        donation_amount=single_online_donation[
                            'new_donation_amount'],
                        currency_buying_amount=return_bookings.
                        currency_buying_rate_at_time,
                        currency_selling_amount=return_bookings.
                        currency_selling_rate_at_time,
                        created_at=datetime.now())
                    db.session.add(donation)

                    db.session.commit()
                except Exception as e:

                    print(e)

            except Exception as e:

                print(e)

    return jsonify({"message": 'Successfully saved donation'}), 201


@app.route("/mail_test")
def mail_test():
    random_array = []
    error_array = []

    sg = sendgrid.SendGridAPIClient(apikey=app.config["SENDGRID_API_KEY"])

    recipient = [
        "kkagunda@gmail.com", "kenneth.kagunda@olpejetaconservancy.org",
        "kkagunda2022@gmail.com", "kagundakk@yahoo.com"
    ]
    sender = "reservations@olpejetaconservancy.org"

    for single in recipient:
        from_email = Email(sender)
        to_email = Email(single)
        subject = "Multi-receiving email"
        content = Content("text/html", "Hello. This is a test email. <br/>Asante...")
        mail = Mail(from_email, subject, to_email, content)

        try:
            response = sg.client.mail.send.post(request_body=mail.get())

            # return jsonify({'message' : str(response)})
            random_array.append(str(response))

        except Exception as e:
            # return jsonify({"message": str(e)})
            error_array.append(str(e))

    if random_array:
        return jsonify({"message": random_array})
    elif error_array:
        return jsonify({"data": error_array})


##############
#### Mail ####
##############
def send_email(email_data):
    recipient = email_data["recipient"]
    sender = email_data["sender"]

    booking_details = {}
    booking_details["booking_ref_code"] = email_data["booking_ref_code"]
    booking_details["first_name"] = email_data["first_name"]
    booking_details["last_name"] = email_data["last_name"]
    booking_details["check_in_date"] = email_data["check_in_date"]
    booking_details["check_out_date"] = email_data["check_out_date"]
    booking_details[
        "client"] = email_data["first_name"] + " " + email_data["last_name"]
    booking_details["copyright_year"] = datetime.now().strftime("%Y")

    sg = sendgrid.SendGridAPIClient(apikey=app.config["SENDGRID_API_KEY"])

    from_email = Email(sender)
    to_email = Email(recipient)
    subject = email_data["subject"]
    content = Content(
        "text/html",
        render_template("confirmation_with_activities.html",
                        data=booking_details))
    mail = Mail(from_email, subject, to_email, content)

    try:
        response = sg.client.mail.send.post(request_body=mail.get())

        return jsonify({'message': str(response)})

    except Exception as e:
        return jsonify({"message": str(e)})


#######################
#### Booking Payment failure ####
#######################
@app.route("/bookings/payment/failure/<booking_id>")
def view_payment_failure(booking_id):
    return_booking = db.session.query(BookingPaymentFailure) \
        .filter(Booking.deletion_marker == None) \
        .filter(BookingPaymentFailure.booking_id == booking_id) \
        .first()

    if not return_booking:
        message = []
        message.append("The selected booking does not appear to exist.")
        return jsonify({"message": message}), 422

    return_data = {}
    return_data["booking_id"] = return_booking.booking_id
    return_data["card_number"] = return_booking.card_number
    return_data["reason"] = return_booking.failure_reason
    return_data["created_at"] = return_booking.created_at

    return jsonify({"data": return_data}), 200


#############
#### Ref ####
#############
def get_booking_status_id(status_name):
    status_id = db.session.query(BookingStatus) \
        .filter(BookingStatus.deletion_marker == None) \
        .filter(BookingStatus.booking_status_name == status_name) \
        .all()

    for single in status_id:
        booking_status_id = single.booking_status_public_id

        return booking_status_id


#######################
#### Donation payment status ####
#######################
@app.route("/donation/payment/status/<booking_ref>")
def view_donation_payment_status(booking_ref):
    return_booking = db.session.query(Booking) \
        .join(BookingType, Booking.booking_type == BookingType.booking_type_public_id) \
        .join(BookingStatus, Booking.status == BookingStatus.booking_status_public_id) \
        .add_columns(Booking.booking_public_id, Booking.booking_type, Booking.booking_check_in_date, \
                     Booking.booking_check_out_date, Booking.actual_booking_check_in_date,
                     Booking.actual_booking_check_out_date, \
                     Booking.booking_done_by, Booking.checked_in, Booking.checked_out, Booking.booking_ref_code,
                     Booking.session_id, \
                     Booking.created_at, Booking.updated_at, Booking.status, Booking.payment_status, \
                     Booking.currency, Booking.deletion_marker, Booking.currency_buying_rate_at_time, \
                     Booking.currency_selling_rate_at_time, \
                     BookingType.booking_type_name, \
                     BookingStatus.booking_status_name, ) \
        .filter(Booking.booking_ref_code == booking_ref) \
        .options(FromCache(db_cache)) \
        .first()
    return_data = {}
    if return_booking:
        if return_booking.payment_status == 1:
            return_data["booking_payment_status"] = "Paid"
        if return_booking.payment_status == 2:
            return_data["booking_payment_status"] = "Incomplete Payment"
        if return_booking.payment_status == 3:
            return_data["booking_payment_status"] = "Complimentary"
        if return_booking.payment_status == 4:
            return_data["booking_payment_status"] = "To Invoice"
        elif not return_booking.payment_status:
            return_data["booking_payment_status"] = "Not Paid"
    else:
        return_data["booking_payment_status"] = "Booking Not Found"

    return jsonify({"data": return_data}), 200


@app.route("/bookings/payment/failure")
def view_payments_failure():
    try:
        page = int(request.args["page"])
    except Exception:
        page = 1

    try:
        items = int(request.args["items"])
    except Exception:
        items = 50

    if page == 0:
        failure_payments = db.session.query(BookingPaymentFailure) \
            .filter(BookingPaymentFailure.deletion_marker == None) \
            .order_by(BookingPaymentFailure.created_at.desc()) \
            .options(FromCache(db_cache)) \
            .all()

    else:
        failure_payments = db.session.query(BookingPaymentFailure) \
            .filter(BookingPaymentFailure.deletion_marker == None) \
            .order_by(BookingPaymentFailure.created_at.desc()) \
            .options(FromCache(db_cache)) \
            .paginate(page, items, False) \
            .items
    if not failure_payments:
        message = []
        message.append("No failed iveri payments at the moment.")
        return jsonify({"message": message}), 422

    output = []
    for single in failure_payments:
        # get booking

        return_data = {}
        return_booking = db.session.query(Booking) \
            .filter(Booking.booking_public_id == single.booking_id) \
            .first()
        if return_booking:
            return_data["booking_id"] = single.booking_id
            return_data["booking_ref_code"] = return_booking.booking_ref_code
            return_data["card_number"] = single.card_number
            return_data["reason"] = single.failure_reason
            return_data["created_at"] = single.created_at
            output.append(return_data)
    return jsonify({"data": output}), 200


@app.route("/bookings/bankslip/new", methods=["POST"])
def save_bank_slip_details():
    messages = []
    try:
        request.json["first_name"].strip()
        if not request.json["first_name"]:
            messages.append("First name is empty.")
    except KeyError as e:
        messages.append("First  name is missing.")

    try:
        request.json["last_name"].strip()
        if not request.json["last_name"]:
            messages.append("Last name is empty.")
    except KeyError as e:
        messages.append("Last  name is missing.")

    try:
        request.json["slip_reference"].strip()
        if not request.json["slip_reference"]:
            messages.append("Slip reference  is empty.")
    except KeyError as e:
        messages.append("Slip reference is missing.")

    try:
        request.json["currency_id"].strip()
        if not request.json["currency_id"]:
            messages.append("Currency is empty.")
    except KeyError as e:
        messages.append("Currency reference is missing.")

    try:
        request.json["payment_amount"].strip()
        if not request.json["payment_amount"]:
            messages.append("Amount is empty.")
    except KeyError as e:
        messages.append("Amount reference is missing.")

    try:
        request.json["booking_id"].strip()
        if not request.json["booking_id"]:
            messages.append("Booking ID is empty.")
    except KeyError as e:
        messages.append("Booking ID is missing.")

    try:
        request.json["session_id"].strip()
        if not request.json["session_id"]:
            messages.append("Session ID is empty.")
    except KeyError as e:
        messages.append("Session ID is missing.")

    if messages:
        message = []
        message.append("You appear to be missing some data. Please try again.")
        return jsonify({"messages": messages}), 422
    bank_slip_details = BookingBankSlip(
        booking_bank_slip_public_id=str(uuid.uuid4()),
        first_name=request.json["first_name"],
        last_name=request.json["last_name"],
        slip_reference=request.json["slip_reference"],
        booking_id=request.json["booking_id"],
        currency_id=request.json["currency_id"],
        payment_amount=request.json["payment_amount"],
        session_id=request.json["session_id"],
        created_at=datetime.now())

    db.session.add(bank_slip_details)

    try:
        db.session.commit()
        close(db)
        message = []
        message.append("Bank Slip details successfully saved")

        return jsonify({"message": message}), 201

    except Exception as e:
        db.session.rollback()
        close(db)

        message = []
        message.append(
            "An error occurred while saving bank slip details.")

        return jsonify({"message": message, "error": str(e)}), 422


@app.route("/bookings/bankslip/view")
def view_all_booking_slip():
    try:
        page = int(request.args["page"])
    except Exception:
        page = 1

    try:
        items = int(request.args["items"])
    except Exception:
        items = 50

    if page == 0:
        return_bank_slip = db.session.query(BookingBankSlip) \
            .filter(BookingBankSlip.deletion_marker == None) \
            .order_by(BookingBankSlip.created_at.desc()) \
            .options(FromCache(db_cache)) \
            .all()

    else:
        return_bank_slip = db.session.query(BookingBankSlip) \
            .filter(BookingBankSlip.deletion_marker == None) \
            .order_by(BookingBankSlip.created_at.desc()) \
            .options(FromCache(db_cache)) \
            .paginate(page, items, False) \
            .items

    if not return_bank_slip:
        if page == 0:
            output = []
            output.append("There are currently nobank slips in the system.")
            return jsonify({"message": output}), 200
        else:
            output = []
            output.append("There are currently no morebank slips in the system.")
            return jsonify({"message": output}), 200

    else:
        data = []
        for single in return_bank_slip:
            get_booking = db.session.query(Booking) \
                .filter(Booking.booking_public_id == single.booking_id) \
                .first()
            return_data = {}
            return_data[
                "booking_bank_slip_public_id"] = single.booking_bank_slip_public_id
            return_data["first_name"] = single.first_name
            return_data["last_name"] = single.last_name
            return_data["slip_reference"] = single.slip_reference
            return_data["booking_ref_code"] = get_booking.booking_ref_code
            return_data["booking_public_id"] = single.booking_id
            return_data["payment_amount"] = single.payment_amount
            return_data["receipt_details"] = single.receipt_details
            return_data["created_at"] = single.created_at
            return_data["status"] = single.status
            currency = requests.get(get_currency.format(single.currency_id))

            try:
                return_data["currency_name"] = currency.json(
                )["data"][0]["currency_name"]
            except Exception:
                return_data["currency_name"] = ""

            return_data["currency_id"] = single.currency_id

            session_id = single.session_id
            user_info = {}
            try:
                getBookingSessionUser(session_id, user_info)
                return_data["session_user"] = user_info["session_user"]
                return_data["session_id"] = user_info["session_id"]
            except Exception as e:
                return_data["session_user"] = ''
                return_data["session_id"] = ''

            data.append(return_data)
        return jsonify({"data": data}), 200


@app.route("/bookings/bankslip/allocate", methods=["POST"])
def allocate_bank_slip():
    messages = []

    try:
        request.json["booking_id"].strip()
        if not request.json["booking_id"]:
            messages.append("Booking ID is empty.")
    except KeyError as e:
        messages.append("Booking ID is missing.")

    if request.json["session_id"] == "Mpesa":
        get_booking_id = db.session.query(Booking) \
            .filter(Booking.deletion_marker == None) \
            .filter(Booking.booking_ref_code == request.json["booking_id"]) \
            .first()

        booking_id = get_booking_id.booking_public_id

    else:
        booking_id = request.json["booking_id"]

    try:
        request.json["payment_method"].strip()
        if not request.json["payment_method"]:
            messages.append("Payment method is empty.")
    except KeyError as e:
        messages.append("Payment method is missing.")

    try:
        str(request.json["payment_amount"]).strip()
        if not request.json["payment_amount"]:
            messages.append("Payment amount is empty.")
    except KeyError as e:
        messages.append("Payment amount is missing.")

    try:
        request.json["session_id"].strip()
        # if not request.json["session_id"]:
        # 	messages.append("Session ID is empty.")
        session_id = request.json["session_id"]
    except KeyError as e:
        # messages.append("Session ID is missing.")
        session_id = None

    if messages:
        return jsonify({"messages": messages}), 422

    if float(request.json["payment_amount"]) <= 0:
        message = []
        message.append("The payment amount cannot be less than or equal to 0.")
        return jsonify({"message": message}), 422

    try:
        phone_number = request.json["phone"]
    except (KeyError) as phone_error:
        phone_number = None

    try:
        mpesa_reference = request.json["mpesa_ref"]
    except (KeyError) as mpesa_error:
        mpesa_reference = None

    try:
        card_first_four = request.json["first_four"]
        card_last_four = request.json["last_four"]
    except (KeyError) as card_error:
        card_first_four = None
        card_last_four = None

    try:
        payment_gateway = request.json["payment_gateway"][
            "payment_gateway_public_id"]
    except Exception:
        payment_gateway = None

    payment_method = db.session.query(PaymentMethod) \
        .filter(PaymentMethod.deletion_marker == None) \
        .filter(PaymentMethod.payment_method_public_id == request.json["payment_method"]) \
        .first()

    if payment_method:
        pass
    else:
        message = []
        message.append(
            "The selected payment method does not exist in the system.")
        return jsonify({"message": request.json["payment_method"]}), 422

    return_bookings = db.session.query(Booking) \
        .join(BookingType, Booking.booking_type == BookingType.booking_type_public_id) \
        .join(BookingStatus, Booking.status == BookingStatus.booking_status_public_id) \
        .join(Detail, Booking.booking_public_id == Detail.booking_id) \
        .add_columns(Booking.booking_public_id, Booking.booking_type, Booking.booking_check_in_date, \
                     Booking.booking_check_out_date, Booking.actual_booking_check_in_date,
                     Booking.actual_booking_check_out_date, \
                     Booking.booking_done_by, Booking.checked_in, Booking.checked_out, Booking.booking_ref_code,
                     Booking.session_id, \
                     Booking.created_at, Booking.updated_at, Booking.status, Booking.payment_status, \
                     Booking.currency, \
                     BookingType.booking_type_name, \
                     BookingStatus.booking_status_name, \
                     Detail.first_name, Detail.last_name, Detail.email_address, Detail.phone_number, \
                     Detail.address, Detail.additional_note) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == booking_id) \
        .all()

    if not return_bookings:
        output = []
        output.append(
            "The selected booking does not appear to exist in the system.")
        return jsonify({"message": output}), 200
    return_data = {}
    bookingTotal(return_data, booking_id)

    total = round(return_data["total_cost"], 2)
    transaction_id = str(uuid.uuid4())

    payment = BookingPayment(booking_payment_public_id=str(uuid.uuid4()),
                             booking_id=request.json["booking_id"],
                             transaction_id=transaction_id,
                             payment_method=request.json["payment_method"],
                             payment_currency=request.json["currency_id"],
                             phone_number=phone_number,
                             mpesa_reference=mpesa_reference,
                             card_first_four=card_first_four,
                             card_last_four=card_last_four,
                             booking_amount=total,
                             amount_paid=request.json["payment_amount"],
                             session_id=session_id)

    db.session.add(payment)

    return_booking = db.session.query(Booking) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == request.json["booking_id"]) \
        .first()

    get_past_payments = db.session.query(Transaction) \
        .filter(Transaction.deletion_marker == None) \
        .filter(Transaction.booking_id == request.json["booking_id"]) \
        .all()

    date = return_booking.created_at.strftime("%Y-%m-%d")
    post_data = {"currency_id": request.json["currency_id"], "date": date}
    get_exchange_rate = requests.post(get_currency_rate_at_time,
                                      json=post_data)

    buying_rate = get_exchange_rate.json()["data"][0]["buy_amount"]
    selling_rate = get_exchange_rate.json()["data"][0]["sell_amount"]

    if len(get_past_payments) == 0:
        converted_transaction_amount = currencyPostProcessorInvoice(return_booking.currency,
                                                                    request.json["currency_id"],
                                                                    request.json["payment_amount"], \
                                                                    buying_rate, selling_rate, date)

        balance = float(total) - float(
            converted_transaction_amount)

        transaction = Transaction(
            transaction_booking_public_id=transaction_id,
            booking_id=request.json["booking_id"],
            transaction_original_cost=request.json["booking_amount"],
            transaction_total=request.json["payment_amount"],
            transaction_balance=balance,
            transaction_payment_method=request.json["payment_method"],
            transaction_payment_gateway=payment_gateway,
            transaction_payment_currency=request.json["currency_id"],
            transaction_date=datetime.now(),
            transaction_total_currency=return_booking.currency,
            payment_currency_buying_rate_at_time=buying_rate,
            payment_currency_selling_rate_at_time=selling_rate,
            session_id=session_id,
            created_at=datetime.now(),
            updated_at=datetime.now())

        db.session.add(transaction)

        get_to_invoice = db.session.query(Invoice) \
            .filter(Invoice.deletion_marker == None) \
            .filter(Invoice.booking_id == request.json["booking_id"]) \
            .first()

        if get_to_invoice:
            get_to_invoice.deletion_marker = 1
            get_to_invoice.updated_at = datetime.now()

        if float(total) <= float(
                converted_transaction_amount):
            return_booking.payment_status = 1
            return_booking.updated_at = datetime.now()
            return_booking.status = get_booking_status_id("Confirmed")
        elif float(total) > float(
                converted_transaction_amount):
            if round(balance) <= 0:
                return_booking.payment_status = 1
            else:
                return_booking.payment_status = 2
            return_booking.updated_at = datetime.now()
            return_booking.status = get_booking_status_id("Deposit")

    elif len(get_past_payments) > 0:
        total_payments = []
        for each_payment in get_past_payments:
            if each_payment.transaction_payment_currency:
                converted_transaction_total = currencyPostProcessorInvoice(
                    return_booking.currency,
                    each_payment.transaction_payment_currency,
                    each_payment.transaction_total,
                    each_payment.payment_currency_buying_rate_at_time,
                    each_payment.payment_currency_selling_rate_at_time,
                    date)
            else:
                converted_transaction_total = currencyPostProcessorInvoice(
                    return_booking.currency,
                    each_payment.transaction_payment_currency,
                    each_payment.transaction_total,
                    each_payment.payment_currency_buying_rate_at_time,
                    each_payment.payment_currency_selling_rate_at_time,
                    date)

            total_payments.append(float(converted_transaction_total))

        past_total = sum(total_payments)

        converted_transaction_amount = currencyPostProcessorInvoice(
            return_booking.currency, request.json["currency_id"],
            request.json["payment_amount"], buying_rate, selling_rate,
            date)

        pending = float(total) - float(past_total)
        balance = pending - float(converted_transaction_amount)

        transaction = Transaction(
            transaction_booking_public_id=transaction_id,
            booking_id=request.json["booking_id"],
            transaction_original_cost=total,
            transaction_total=request.json["payment_amount"],
            transaction_balance=balance,
            transaction_payment_method=request.json["payment_method"],
            transaction_payment_gateway=payment_gateway,
            transaction_payment_currency=request.json["currency_id"],
            transaction_date=datetime.now(),
            transaction_total_currency=return_booking.currency,
            payment_currency_buying_rate_at_time=buying_rate,
            payment_currency_selling_rate_at_time=selling_rate,
            session_id=session_id,
            created_at=datetime.now(),
            updated_at=datetime.now())

        db.session.add(transaction)

        get_to_invoice = db.session.query(Invoice) \
            .filter(Invoice.deletion_marker == None) \
            .filter(Invoice.booking_id == request.json["booking_id"]) \
            .first()

        if get_to_invoice:
            get_to_invoice.deletion_marker = 1
            get_to_invoice.updated_at = datetime.now()

        if pending <= float(converted_transaction_amount):
            return_booking.payment_status = 1
            return_booking.updated_at = datetime.now()
            return_booking.status = get_booking_status_id("Confirmed")
        elif pending > float(converted_transaction_amount):
            if round(balance) <= 0:
                return_booking.payment_status = 1
            else:
                return_booking.payment_status = 2
            return_booking.updated_at = datetime.now()
            return_booking.status = get_booking_status_id("Deposit")

    get_all_inventory = db.session.query(Inventory) \
        .filter(Inventory.deletion_marker == None) \
        .filter(Inventory.booking_id == request.json["booking_id"]) \
        .all()

    if get_all_inventory:
        for single_inventory in get_all_inventory:
            single_inventory.inventory_payment_method = request.json[
                "payment_method"]
            single_inventory.updated_at = datetime.now()
    else:
        pass

        get_all_facilities = db.session.query(Facility) \
            .filter(Facility.deletion_marker == None) \
            .filter(Facility.booking_id == request.json["booking_id"]) \
            .all()

        if get_all_facilities:
            for single_facility in get_all_facilities:
                single_facility.facility_payment_method = request.json[
                    "payment_method"]
                single_facility.updated_at = datetime.now()
        else:
            pass
        gatepass = db.session.query(Gatepass) \
            .filter(Gatepass.deletion_marker == None) \
            .filter(Gatepass.booking_id == request.json["booking_id"]) \
            .first()

        gatepass_id = gatepass.gatepass_public_id

        get_all_guests = db.session.query(GatepassGuest) \
            .filter(GatepassGuest.deletion_marker == None) \
            .filter(GatepassGuest.gatepass_id == gatepass_id) \
            .all()

        if get_all_guests:
            for single_guest in get_all_guests:
                single_guest.gatepass_guest_payment_method = request.json[
                    "payment_method"]
                single_guest.updated_at = datetime.now()

        else:
            pass

        get_all_vehicles = db.session.query(GatepassVehicle) \
            .filter(GatepassVehicle.deletion_marker == None) \
            .filter(GatepassVehicle.gatepass_id == gatepass_id) \
            .all()

        if get_all_vehicles:
            for single_vehicle in get_all_vehicles:
                single_vehicle.gatepass_vehicle_payment_method = request.json[
                    "payment_method"]
                single_vehicle.updated_at = datetime.now()

        else:
            pass

        try:
            return_bank_slip = db.session.query(BookingBankSlip) \
                .filter(BookingBankSlip.deletion_marker == None) \
                .filter(BookingBankSlip.booking_bank_slip_public_id == request.json["booking_slip_public_id"]) \
                .first()
            return_bank_slip.status = 2
            return_bank_slip.receipt_details = request.json["mpesa_ref"]

            try:
                db.session.commit()
                close(db)
                message = []
                message.append("The receipt has successfully been allocated.")
                return jsonify({"message": message}), 200
            except Exception as e:
                db.session.rollback()
                close(db)

                message = []
                message.append("There was an error allocating the recipe.")

                return jsonify({"message": message, "error": str(e)}), 422

        except Exception as e:
            message = []
            message.append("There was an error allocating the receipt.")

            return jsonify({"message": message, "error": str(e)}), 422


@app.route("/bookings/payments")
def view_all_booking_payments():
    try:
        page = int(request.args["page"])
    except Exception:
        page = 1

    try:
        items = int(request.args["items"])
    except Exception:
        items = 50

    if page == 0:
        booking_payments = db.session.query(BookingPayment) \
            .filter(BookingPayment.deletion_marker == None) \
            .order_by(BookingPayment.created_at.desc()) \
            .options(FromCache(db_cache)) \
            .all()

    else:
        booking_payments = db.session.query(BookingPayment) \
            .filter(BookingPayment.deletion_marker == None) \
            .order_by(BookingPayment.created_at.desc()) \
            .options(FromCache(db_cache)) \
            .paginate(page, items, False) \
            .items

    if not booking_payments:
        message = []
        message.append("Currently no booking payments available in the system.")
        return jsonify({"message": message}), 422

    output = []
    for single in booking_payments:
        # get booking

        return_data = {}
        return_booking = db.session.query(Booking) \
            .filter(Booking.booking_public_id == single.booking_id) \
            .first()
        if return_booking:
            return_data["booking_id"] = single.booking_id
            return_data["booking_ref_code"] = return_booking.booking_ref_code

        payment_method = db.session.query(PaymentMethod) \
            .filter(PaymentMethod.deletion_marker == None) \
            .filter(PaymentMethod.payment_method_public_id == single.payment_method) \
            .first()
        try:
            return_data["payment_method_name"] = payment_method.payment_method_name
        except Exception as e:
            return_data["payment_method_name"] = ""

        user_info = {}

        getBookingSessionUser(single.session_id, user_info)
        try:
            return_data["session_user"] = user_info["session_user"]
            return_data["session_id"] = user_info["session_id"]
        except Exception as e:
            return_data["session_user"] = ""
            return_data["session_id"] = single.session_id
        currency = requests.get(get_currency.format(single.payment_currency))

        try:
            return_data["currency"] = currency.json(
            )["data"][0]["currency_name"]
        except Exception:
            return_data["currency"] = ""
        return_data["currency_id"] = single.payment_currency
        return_data["amount_paid"] = float(single.amount_paid)
        return_data["mpesa_reference"] = single.mpesa_reference
        return_data["card_first_four"] = single.card_first_four
        return_data["card_last_four"] = single.card_last_four
        return_data["created_at"] = single.created_at
        output.append(return_data)
    return jsonify({"data": output}), 200

@app.route("/mpesa/callback", methods=["POST", "GET"])
def update_booking_mpesa_payment():
    print("update_booking_mpesa_payment calllback was called", flush=True)
    if request.method == "POST":
        req_data = None  # Initialize req_data to None at the start
        
        # try:
        #     raw_body = request.body.decode('utf-8')
        #     print("Raw Body: ", raw_body, flush=True)  # This will print the raw body as a string
        #     req_data = request.json(force=True)  # Force it to parse JSON even if content-type is incorrect
        #     if req_data is None:
        #         #raise ValueError("No JSON data received.")
        #         print("Received request data:", req_data)
        # except Exception as e:
        #     print(f"Failed to parse JSON: {e}", flush=True)
        #     print("Request data during exception:", req_data)  # Print req_data even if it's None
        #     #return jsonify({"error": "Invalid JSON"}), 400  # Return an error response

        print("update_booking_mpesa_payment callback was called", flush=True)
        print(datetime.now(), flush=True)

        try:
            # Get raw data and JSON data from the request
            raw_data = request.data  # Raw request body
            print("Raw request body:", raw_data.decode('utf-8'), flush=True)  # Decoding for better readability
            
            req_data = request.get_json()  # Use get_json() to get parsed JSON
            print("Parsed request JSON:", req_data, flush=True)
        except Exception as e:
            print("Error at request body or JSON parsing:", e, flush=True)

        try:
            body = request.get_json()#response.json()
            print(f"was  response", body, flush=True)
        except Exception as e:
            print(f"was not response", flush=True)
            pass

        merchant_request_id = req_data["Body"]["stkCallback"]["MerchantRequestID"]
        checkout_request_id = req_data["Body"]["stkCallback"]["CheckoutRequestID"]
        result_code = req_data["Body"]["stkCallback"]["ResultCode"]
        
        # print the result_code in the mpesa/callback url to see if result_code has any value
        
        print(f"Result Code: {result_code}", flush=True)
        if result_code == 0:
            print(
                "////////////////////////////////////////////////////////\n" +
                "////////////////////////////////////////////////////////\n" +
                "///////////////////bookings/payment/mpesa/new ////////////////////\n" +
                "////////////////////////////////////////////////////////\n" +
                "////////////////////////////////////////////////////////\n"
            )
            print("SUCCESSFUL PAYMENT", flush=True)
            db.session.query(CheckoutTransaction) \
                .filter_by(merchant_request_id=merchant_request_id, checkout_request_id=checkout_request_id) \
                .update({'payment': True})
            db.session.commit()
            # Find operation
            updated_transaction = CheckoutTransaction.query \
                .filter_by(merchant_request_id=merchant_request_id, checkout_request_id=checkout_request_id) \
                .first()
            print("updated_transaction", updated_transaction, flush=True)

            if updated_transaction:
                booking_id = updated_transaction.reference
                phone_number = updated_transaction.phone_number
                mpesa_ref = updated_transaction.merchant_request_id
                amount = updated_transaction.amount

                booking_payload = {
                    "booking_id": booking_id,
                    "payment_method": "cbff45f3-4f12-41da-8b4a-bf308112f032",
                    "phone": phone_number,
                    "mpesa_ref": mpesa_ref,
                    "booking_amount": amount,
                    "payment_amount": amount,
                    "session_id": "Mpesa",
                }
                print("booking_payload", booking_payload, flush=True)

                update_mpesa_paid_booking(booking_payload)

                try:
                    message = []
                    message.append("Mpesa payment successful.")
                    return jsonify({"message": message}), 200

                except requests.RequestException as e:
                    return jsonify({"error": f"Error fetching payment data: {str(e)}"}), 500
            else:
                return jsonify({"error": f"updated_transaction transaction with merchant_request_id{merchant_request_id}"
                                        f" and  checkout_request_id{checkout_request_id} does not exist"}), 400

    return jsonify({"message": "Callback processed"}), 200


def get_next_checkout_transaction_id():
    max_id = db.session.query(db.func.max(CheckoutTransaction.checkout_transaction_id)).scalar()
    return (max_id or 0) + 1


def update_mpesa_paid_booking(booking_payload):
    messages = []
    print("update_mpesa_paid_booking", booking_payload, flush=True)
    try:
        booking_payload["booking_id"].strip()
        if not booking_payload["booking_id"]:
            messages.append("Booking ID is empty.")
    except KeyError as e:
        messages.append("Booking ID is missing.")

    if booking_payload["session_id"] == "Mpesa":
        get_booking_id = db.session.query(Booking) \
            .filter(Booking.deletion_marker == None) \
            .filter(Booking.booking_ref_code == booking_payload["booking_id"]) \
            .first()

        booking_id = get_booking_id.booking_public_id

    else:
        booking_id = booking_payload["booking_id"]

    try:
        booking_payload["payment_method"].strip()
        if not booking_payload["payment_method"]:
            messages.append("Payment method is empty.")
    except KeyError as e:
        messages.append("Payment method is missing.")

    try:
        str(booking_payload["booking_amount"]).strip()
        if not booking_payload["booking_amount"]:
            messages.append("Booking amount is empty.")
    except KeyError as e:
        messages.append("Booking amount is missing.")

    try:
        str(booking_payload["payment_amount"]).strip()
        if not booking_payload["payment_amount"]:
            messages.append("Payment amount is empty.")
    except KeyError as e:
        messages.append("Payment amount is missing.")

    try:
        booking_payload["session_id"].strip()
        session_id = booking_payload["session_id"]
    except KeyError as e:
        session_id = None

    if messages:
        return jsonify({"messages": messages}), 422

    if float(booking_payload["payment_amount"]) <= 0:
        message = []
        message.append("The payment amount cannot be less than or equal to 0.")
        return jsonify({"message": message}), 422

    try:
        phone_number = booking_payload["phone"]
    except (KeyError) as phone_error:
        print(str(phone_error))
        phone_number = None

    try:
        mpesa_reference = booking_payload["mpesa_ref"]
    except (KeyError) as mpesa_error:
        print(str(mpesa_error))
        mpesa_reference = None

    try:
        card_first_four = booking_payload["first_four"]
        card_last_four = booking_payload["last_four"]
    except (KeyError) as card_error:
        print(str(card_error))
        card_first_four = None
        card_last_four = None

    #added to manage subsequent payment for a card payment then paybill payment to avoid getting gateway detais
    get_gateway = db.session.query(PaymentGateway) \
        .filter(PaymentGateway.payment_method == booking_payload["payment_method"]) \
        .filter(PaymentGateway.deletion_marker == None) \
        .first()
    try:
        if get_gateway:
            payment_gateway = booking_payload["payment_gateway"][
                "payment_gateway_public_id"]
        else:
            payment_gateway = None
    except Exception:
        payment_gateway = None

    payment_method = db.session.query(PaymentMethod) \
        .filter(PaymentMethod.deletion_marker == None) \
        .filter(PaymentMethod.payment_method_public_id == booking_payload["payment_method"]) \
        .first()

    if payment_method:
        pass
    else:
        message = []
        message.append(
            "The selected payment method does not exist in the system.")
        return jsonify({"message": booking_payload["payment_method"]}), 422

    return_bookings = db.session.query(Booking) \
        .join(BookingType, Booking.booking_type == BookingType.booking_type_public_id) \
        .join(BookingStatus, Booking.status == BookingStatus.booking_status_public_id) \
        .join(Detail, Booking.booking_public_id == Detail.booking_id) \
        .add_columns(Booking.booking_public_id, Booking.booking_type, Booking.booking_check_in_date, \
                     Booking.booking_check_out_date, Booking.actual_booking_check_in_date,
                     Booking.actual_booking_check_out_date, \
                     Booking.booking_done_by, Booking.checked_in, Booking.checked_out, Booking.booking_ref_code,
                     Booking.session_id, \
                     Booking.created_at, Booking.updated_at, Booking.status, Booking.payment_status, \
                     Booking.currency, \
                     BookingType.booking_type_name, \
                     BookingStatus.booking_status_name, \
                     Detail.first_name, Detail.last_name, Detail.email_address, Detail.phone_number, \
                     Detail.address, Detail.additional_note) \
        .filter(Booking.deletion_marker == None) \
        .filter(Booking.booking_public_id == booking_id) \
        .all()

    if not return_bookings:
        output = []
        output.append(
            "The selected booking does not appear to exist in the system.")
        return jsonify({"message": output}), 200
    return_data = {}
    bookingTotal(return_data, booking_id)

    total = round(return_data["total_cost"], 2)

    ##### Handling requests from the Online checkout service
    if booking_payload["session_id"] == "Mpesa":
        check_payment = db.session.query(BookingPayment) \
            .filter(BookingPayment.mpesa_reference == mpesa_reference) \
            .first()

        if check_payment:
            message = []
            message.append("The booking has already been paid for.")
            return jsonify({"message": message}), 200

        else:
            transaction_id = str(uuid.uuid4())

            ## 1c9d01cc-ef31-4757-a390-10c765fcecab
            # payment_method = booking_payload["payment_method"],
            payment = BookingPayment(
                booking_payment_public_id=str(uuid.uuid4()),
                booking_id=booking_id,
                transaction_id=transaction_id,
                payment_method="1c9d01cc-ef31-4757-a390-10c765fcecab",
                payment_currency="162fface-f5f1-41de-913b-d2bb784dda3a",
                phone_number=phone_number,
                mpesa_reference=mpesa_reference,
                card_first_four=card_first_four,
                card_last_four=card_last_four,
                booking_amount=total,
                amount_paid=booking_payload["payment_amount"],
                session_id=session_id)

            db.session.add(payment)

            return_booking = db.session.query(Booking) \
                .filter(Booking.deletion_marker == None) \
                .filter(Booking.booking_public_id == booking_id) \
                .first()

            get_past_payments = db.session.query(Transaction) \
                .filter(Transaction.deletion_marker == None) \
                .filter(Transaction.booking_id == booking_id) \
                .all()

            if len(get_past_payments) == 0:
                balance = float(total) - float(booking_payload["payment_amount"])

                ## Buying and selling rate are set to 1 since Mpesa payment is assumed to be in KES
                transaction = Transaction(
                    transaction_booking_public_id=transaction_id,
                    booking_id=booking_id,
                    transaction_original_cost=total,
                    transaction_total=booking_payload["payment_amount"],
                    transaction_balance=balance,
                    transaction_payment_method=
                    "1c9d01cc-ef31-4757-a390-10c765fcecab",
                    transaction_payment_currency=
                    "162fface-f5f1-41de-913b-d2bb784dda3a",
                    transaction_date=datetime.now(),
                    transaction_total_currency=return_booking.currency,
                    payment_currency_buying_rate_at_time=1,
                    payment_currency_selling_rate_at_time=1,
                    session_id=session_id,
                    created_at=datetime.now(),
                    updated_at=datetime.now())

                db.session.add(transaction)

                get_to_invoice = db.session.query(Invoice) \
                    .filter(Invoice.deletion_marker == None) \
                    .filter(Invoice.booking_id == booking_id) \
                    .first()

                if get_to_invoice:
                    get_to_invoice.deletion_marker = 1
                    get_to_invoice.updated_at = datetime.now()

                if float(total) <= float(booking_payload["payment_amount"]):
                    return_booking.payment_status = 1
                    return_booking.updated_at = datetime.now()
                    return_booking.status = get_booking_status_id("Confirmed")
                ## To prevent update of booking session_id
                # return_booking.session_id = session_id
                elif float(total) > float(booking_payload["payment_amount"]):
                    return_booking.payment_status = 2
                    return_booking.updated_at = datetime.now()
                    return_booking.status = get_booking_status_id("Deposit")
            ## To prevent update of booking session_id
            # return_booking.session_id = session_id

            elif len(get_past_payments) > 0:
                total_payments = []
                for each_payment in get_past_payments:
                    total_payments.append(float(
                        each_payment.transaction_total))

                past_total = sum(total_payments)

                pending = float(total) - past_total
                balance = pending - float(booking_payload["payment_amount"])

                ## Buying and selling rate are set to 1 since Mpesa payment is assumed to be in KES
                transaction = Transaction(
                    transaction_booking_public_id=transaction_id,
                    booking_id=booking_id,
                    transaction_original_cost=total,
                    transaction_total=booking_payload["payment_amount"],
                    transaction_balance=balance,
                    transaction_payment_method=
                    "1c9d01cc-ef31-4757-a390-10c765fcecab",
                    transaction_payment_currency=
                    "162fface-f5f1-41de-913b-d2bb784dda3a",
                    transaction_date=datetime.now(),
                    transaction_total_currency=return_booking.currency,
                    payment_currency_buying_rate_at_time=1,
                    payment_currency_selling_rate_at_time=1,
                    session_id=session_id,
                    created_at=datetime.now(),
                    updated_at=datetime.now())

                db.session.add(transaction)

                get_to_invoice = db.session.query(Invoice) \
                    .filter(Invoice.deletion_marker == None) \
                    .filter(Invoice.booking_id == booking_id) \
                    .first()

                if get_to_invoice:
                    get_to_invoice.deletion_marker = 1
                    get_to_invoice.updated_at = datetime.now()

                if pending <= float(booking_payload["payment_amount"]):
                    return_booking.payment_status = 1
                    return_booking.updated_at = datetime.now()
                    return_booking.status = get_booking_status_id("Confirmed")
                ## To prevent update of booking session_id
                # return_booking.session_id = session_id
                elif pending > float(booking_payload["payment_amount"]):
                    return_booking.payment_status = 2
                    return_booking.updated_at = datetime.now()
                    return_booking.status = get_booking_status_id("Deposit")
            ## To prevent update of booking session_id
            # return_booking.session_id = session_id

            get_all_inventory = db.session.query(Inventory) \
                .filter(Inventory.deletion_marker == None) \
                .filter(Inventory.booking_id == booking_id) \
                .all()

            if get_all_inventory:
                for single_inventory in get_all_inventory:
                    single_inventory.inventory_payment_method = "1c9d01cc-ef31-4757-a390-10c765fcecab"
                    single_inventory.updated_at = datetime.now()

            else:
                pass

            get_all_facilities = db.session.query(Facility) \
                .filter(Facility.deletion_marker == None) \
                .filter(Facility.booking_id == booking_id) \
                .all()

            if get_all_facilities:
                for single_facility in get_all_facilities:
                    single_facility.facility_payment_method = "1c9d01cc-ef31-4757-a390-10c765fcecab"
                    single_facility.updated_at = datetime.now()

            else:
                pass

            gatepass = db.session.query(Gatepass) \
                .filter(Gatepass.deletion_marker == None) \
                .filter(Gatepass.booking_id == booking_id) \
                .first()

            gatepass_id = gatepass.gatepass_public_id

            get_all_guests = db.session.query(GatepassGuest) \
                .filter(GatepassGuest.deletion_marker == None) \
                .filter(GatepassGuest.gatepass_id == gatepass_id) \
                .all()

            if get_all_guests:
                for single_guest in get_all_guests:
                    single_guest.gatepass_guest_payment_method = "1c9d01cc-ef31-4757-a390-10c765fcecab"
                    single_guest.updated_at = datetime.now()

            else:
                pass

            get_all_vehicles = db.session.query(GatepassVehicle) \
                .filter(GatepassVehicle.deletion_marker == None) \
                .filter(GatepassVehicle.gatepass_id == gatepass_id) \
                .all()

            if get_all_vehicles:
                for single_vehicle in get_all_vehicles:
                    single_vehicle.gatepass_vehicle_payment_method = "1c9d01cc-ef31-4757-a390-10c765fcecab"
                    single_vehicle.updated_at = datetime.now()

            else:
                pass

            try:
                db.session.commit()
                close(db)
                message = []
                message.append("The booking has been paid for.")
                return jsonify({"message": message}), 200

            except Exception as e:
                db.session.rollback()
                close(db)
                message = []
                message.append("There was an error paying for the booking.")
                return jsonify({"message": message, "error": str(e)}), 422

    ##### Handling requests from the UI
    elif booking_payload[
        "payment_method"] == "cbff45f3-4f12-41da-8b4a-bf308112f032":
        check_existing_payment = db.session.query(BookingPayment) \
            .filter(BookingPayment.deletion_marker == None) \
            .filter(BookingPayment.payment_method == booking_payload["payment_method"]) \
            .filter(BookingPayment.booking_id == booking_payload["booking_id"]) \
            .filter(BookingPayment.amount_paid == booking_payload["payment_amount"]) \
            .first()

        if not check_existing_payment:
            message = []
            message.append(
                "The Mpesa payment was not successful. Please try again.")
            return jsonify({"message": message}), 422

        else:
            message = []
            message.append("Mpesa payment successful.")
            return jsonify({"message": message}), 200

    elif booking_payload[
        "payment_method"] != "cbff45f3-4f12-41da-8b4a-bf308112f032":
        transaction_id = str(uuid.uuid4())

        payment = BookingPayment(booking_payment_public_id=str(uuid.uuid4()),
                                 booking_id=booking_payload["booking_id"],
                                 transaction_id=transaction_id,
                                 payment_method=booking_payload["payment_method"],
                                 payment_currency=booking_payload["currency_id"],
                                 phone_number=phone_number,
                                 mpesa_reference=mpesa_reference,
                                 card_first_four=card_first_four,
                                 card_last_four=card_last_four,
                                 booking_amount=total,
                                 amount_paid=booking_payload["payment_amount"],
                                 session_id=session_id)

        db.session.add(payment)

        return_booking = db.session.query(Booking) \
            .filter(Booking.deletion_marker == None) \
            .filter(Booking.booking_public_id == booking_payload["booking_id"]) \
            .first()

        get_past_payments = db.session.query(Transaction) \
            .filter(Transaction.deletion_marker == None) \
            .filter(Transaction.booking_id == booking_payload["booking_id"]) \
            .all()

        get_exchange_rate = requests.get(
            get_buy_sell_rate.format(booking_payload["currency_id"]))
        buying_rate = get_exchange_rate.json(
        )["data"][0]["currency_buy_amount"]
        selling_rate = get_exchange_rate.json(
        )["data"][0]["currency_sell_amount"]

        if len(get_past_payments) == 0:
            converted_transaction_amount = currencyHandler(
                return_booking.currency, booking_payload["currency_id"],
                booking_payload["payment_amount"])

            balance = float(booking_payload["booking_amount"]) - float(
                converted_transaction_amount)

            transaction = Transaction(
                transaction_booking_public_id=transaction_id,
                booking_id=booking_payload["booking_id"],
                transaction_original_cost=booking_payload["booking_amount"],
                transaction_total=booking_payload["payment_amount"],
                transaction_balance=balance,
                transaction_payment_method=booking_payload["payment_method"],
                transaction_payment_gateway=payment_gateway,
                transaction_payment_currency=booking_payload["currency_id"],
                transaction_date=datetime.now(),
                transaction_total_currency=return_booking.currency,
                payment_currency_buying_rate_at_time=buying_rate,
                payment_currency_selling_rate_at_time=selling_rate,
                session_id=session_id,
                created_at=datetime.now(),
                updated_at=datetime.now())

            db.session.add(transaction)

            get_to_invoice = db.session.query(Invoice) \
                .filter(Invoice.deletion_marker == None) \
                .filter(Invoice.booking_id == booking_payload["booking_id"]) \
                .first()

            if get_to_invoice:
                get_to_invoice.deletion_marker = 1
                get_to_invoice.updated_at = datetime.now()

            # if float(booking_payload["booking_amount"]) <= float(booking_payload["payment_amount"]):
            if float(booking_payload["booking_amount"]) <= float(
                    converted_transaction_amount):
                return_booking.payment_status = 1
                return_booking.updated_at = datetime.now()
                return_booking.status = get_booking_status_id("Confirmed")
            ## To prevent update of booking session_id
            # return_booking.session_id = session_id
            elif float(booking_payload["booking_amount"]) > float(
                    converted_transaction_amount):
                # Prevents negligible amounts like 0.0233 from indicating that a booking has not been paid for
                if round(balance) <= 0:
                    return_booking.payment_status = 1
                else:
                    return_booking.payment_status = 2
                return_booking.updated_at = datetime.now()
                return_booking.status = get_booking_status_id("Deposit")
        ## To prevent update of booking session_id
        # return_booking.session_id = session_id

        elif len(get_past_payments) > 0:
            total_payments = []
            for each_payment in get_past_payments:
                if each_payment.transaction_payment_currency:
                    converted_transaction_total = currencyHandler(
                        return_booking.currency,
                        each_payment.transaction_payment_currency,
                        each_payment.transaction_total)
                else:
                    converted_transaction_total = currencyHandler(
                        return_booking.currency, return_booking.currency,
                        each_payment.transaction_total)

                total_payments.append(float(converted_transaction_total))

            past_total = sum(total_payments)

            converted_transaction_amount = currencyHandler(
                return_booking.currency, booking_payload["currency_id"],
                booking_payload["payment_amount"])

            pending = float(booking_payload["booking_amount"]) - float(past_total)
            balance = pending - float(converted_transaction_amount)

            transaction = Transaction(
                transaction_booking_public_id=transaction_id,
                booking_id=booking_payload["booking_id"],
                transaction_original_cost=booking_payload["booking_amount"],
                transaction_total=booking_payload["payment_amount"],
                transaction_balance=balance,
                transaction_payment_method=booking_payload["payment_method"],
                transaction_payment_gateway=payment_gateway,
                transaction_payment_currency=booking_payload["currency_id"],
                transaction_date=datetime.now(),
                transaction_total_currency=return_booking.currency,
                payment_currency_buying_rate_at_time=buying_rate,
                payment_currency_selling_rate_at_time=selling_rate,
                session_id=session_id,
                created_at=datetime.now(),
                updated_at=datetime.now())

            db.session.add(transaction)

            get_to_invoice = db.session.query(Invoice) \
                .filter(Invoice.deletion_marker == None) \
                .filter(Invoice.booking_id == booking_payload["booking_id"]) \
                .first()

            if get_to_invoice:
                get_to_invoice.deletion_marker = 1
                get_to_invoice.updated_at = datetime.now()

            if pending <= float(converted_transaction_amount):
                return_booking.payment_status = 1
                return_booking.updated_at = datetime.now()
                return_booking.status = get_booking_status_id("Confirmed")
            ## To prevent update of booking session_id
            # return_booking.session_id = session_id
            elif pending > float(converted_transaction_amount):
                # return_booking.payment_status = 2
                # Prevents negligible amounts like 0.0233 from indicating that a booking has not been paid for
                if round(balance) <= 0:
                    return_booking.payment_status = 1
                else:
                    return_booking.payment_status = 2
                return_booking.updated_at = datetime.now()
                return_booking.status = get_booking_status_id("Deposit")
        ## To prevent update of booking session_id
        # return_booking.session_id = session_id

        get_all_inventory = db.session.query(Inventory) \
            .filter(Inventory.deletion_marker == None) \
            .filter(Inventory.booking_id == booking_payload["booking_id"]) \
            .all()

        if get_all_inventory:
            for single_inventory in get_all_inventory:
                single_inventory.inventory_payment_method = booking_payload[
                    "payment_method"]
                single_inventory.updated_at = datetime.now()

        else:
            pass

        get_all_facilities = db.session.query(Facility) \
            .filter(Facility.deletion_marker == None) \
            .filter(Facility.booking_id == booking_payload["booking_id"]) \
            .all()

        if get_all_facilities:
            for single_facility in get_all_facilities:
                single_facility.facility_payment_method = booking_payload[
                    "payment_method"]
                single_facility.updated_at = datetime.now()

        else:
            pass

        gatepass = db.session.query(Gatepass) \
            .filter(Gatepass.deletion_marker == None) \
            .filter(Gatepass.booking_id == booking_payload["booking_id"]) \
            .first()

        gatepass_id = gatepass.gatepass_public_id

        get_all_guests = db.session.query(GatepassGuest) \
            .filter(GatepassGuest.deletion_marker == None) \
            .filter(GatepassGuest.gatepass_id == gatepass_id) \
            .all()

        if get_all_guests:
            for single_guest in get_all_guests:
                single_guest.gatepass_guest_payment_method = booking_payload[
                    "payment_method"]
                single_guest.updated_at = datetime.now()

        else:
            pass

        get_all_vehicles = db.session.query(GatepassVehicle) \
            .filter(GatepassVehicle.deletion_marker == None) \
            .filter(GatepassVehicle.gatepass_id == gatepass_id) \
            .all()

        if get_all_vehicles:
            for single_vehicle in get_all_vehicles:
                single_vehicle.gatepass_vehicle_payment_method = booking_payload[
                    "payment_method"]
                single_vehicle.updated_at = datetime.now()

        else:
            pass

        try:
            db.session.commit()
            close(db)
            message = []
            message.append("The booking has been paid for.")
            return jsonify({"message": message}), 200

        except Exception as e:
            db.session.rollback()
            close(db)

            message = []
            message.append("There was an error paying for the booking.")

            return jsonify({"message": message, "error": str(e)}), 422
