import * as functions from "firebase-functions";
import { firestore } from "firebase-admin";
import {
  orderCol,
  OrderModel,
  OrderStatus,
  usersCol,
  userWalletCol,
  userWalletHistoryCol,
} from "./collections";
import {
  orderedTimeslotNotification,
  paymentSuccessNotification,
} from "./notification-function";
import { HttpsError } from "firebase-functions/v1/https";
import { doctorCol, BookByWho, timeSlotCol } from "./collections";
import { CURRENCY } from "./constants";
import { CustomError } from "./utils/custom_error";
import { UserWalletHistory } from "./model/user_wallet_model";

export enum PaymentStatus {
  PaymentSuccess = "payment_success",
  PaymentFail = "payment_failed",
}

export enum PaymentMethod {
  Stripe = "stripe",
  Paystack = "paystack",
  Razorpay = "razorpay",
  PayMob = "paymob",
  Midtrans = "midtrans",
}

enum TimeSlotStatus {
  booked = "booked",
  refund = "refund",
  cancel = "cancel",
}
type DoctorTimeslot = {
  doctorName: string;
  doctorPicture: string;
};

export interface ConfirmPayment {
  orderId: string;
  timeSlotId: string;
  paymentMethod: PaymentMethod;
  paymentStatus: PaymentStatus;
  amount: number;
  currency: string;
  fee: string;
  paymentType: string;
  bookByWho: BookByWho;
  doctor: DoctorTimeslot;
  doctorId: string;
}

/**
 * Confirm payment, confirm the payment made by user, will set the status to booked and add the Appointment to Doctor Tab and client tab,
 * usually this get called when webhook successfully payment get called
 * @param {ConfirmPayment} confirmPayment you need to create new ConfirmPayment object base on previous ordered timeslot
 */
export async function confirmPayment(confirmPayment: ConfirmPayment) {
  try {
    await orderCol.doc(confirmPayment.orderId).update({
      charged: true,
      amount: confirmPayment.amount,
      status: confirmPayment.paymentStatus,
      currency: confirmPayment.currency,
      fee: confirmPayment.fee,
      paymentMethod: confirmPayment.paymentMethod,
      paymentType: confirmPayment.paymentType,
    });
    await timeSlotCol.doc(confirmPayment.timeSlotId).update({
      charged: true,
      available: false,
      bookByWho: confirmPayment.bookByWho,
      status: TimeSlotStatus.booked,
      doctor: confirmPayment.doctor,
      purchaseTime: firestore.Timestamp.fromDate(new Date()),
    });

    await orderedTimeslotNotification(confirmPayment.doctorId);
  } catch (error) {
    throw error;
  }
}
/**
 * create an order with status not pay, later once client successfully made the payment, you can change the order status to pay
 * @param timeSlotId timeslot id that client wanted to buy
 * @param userId user id who buy this timeslot
 * @param orderId you can set order id, you get this from your payment provider, like id transaction or reference id, that later you can use to find this particular order id, usually from webhook
 */
export async function createOrder(
  timeSlotId: string,
  userId: string,
  orderId: string
) {
  try {
    const newOrder = {
      charged: false,
      status: OrderStatus.notPay,
      timeSlotId: timeSlotId,
      userId: userId,
      createdAt: firestore.Timestamp.fromDate(new Date()),
    } as OrderModel;

    await orderCol.doc(orderId).create(newOrder);
    console.log("add new order");
  } catch (error) {
    throw error;
  }
}

/**
 * purchase free timeslot, this function get calle by client when the timeslot price is 0
 * if actual price is not 0 will return a https error
 * @param userId string of user id that wanto purchase this timeslot
 * @param timeSlotId timeSlotId that user wanto purchase
 * @return will return true if success success
 */
export const purchaseFreeTimeSlot = functions.https.onCall(
  async (request, response) => {
    try {
      let { userId, timeSlotId } = request;
      let purchasedTimeSlot = await timeSlotCol.doc(timeSlotId).get();
      let purchasedTimeslotData = purchasedTimeSlot.data();
      console.log(JSON.stringify(purchasedTimeslotData));
      if (!purchasedTimeslotData) {
        throw new HttpsError(
          "not-found",
          "TimeSlot not found, please check the timeslot again"
        );
      }
      let amount = purchasedTimeslotData.price;
      if (amount > 0) {
        throw new HttpsError(
          "permission-denied",
          "the timeslot price is not 0 or free, please choose another timeslot"
        );
      }

      //Get user info who book this timeslot
      let bookByWho = await usersCol.doc(userId).get();
      let bookByWhoData = bookByWho.data();
      if (!bookByWhoData) {
        throw new HttpsError("not-found", "User book by who not found");
      }
      //Get doctor detail data
      let doctor = await doctorCol.doc(purchasedTimeslotData.doctorId).get();
      let doctorData = doctor.data();

      if (!doctorData) {
        throw new HttpsError("not-found", "Doctor Data not found");
      }

      await purchasedTimeSlot.ref.update({
        charged: true,
        available: false,
        bookByWho: {
          userId: userId,
          displayName: bookByWhoData.displayName,
          photoUrl: bookByWhoData.photoUrl ? bookByWhoData.photoUrl : "",
        },
        status: TimeSlotStatus.booked,
        doctor: {
          doctorName: doctorData.doctorName,
          doctorPicture: doctorData.doctorPicture,
        },
        purchaseTime: firestore.Timestamp.fromDate(new Date()),
      });
      const orderData = await orderCol.add({
        createdAt: firestore.Timestamp.fromDate(new Date()),
        timeSlotId: timeSlotId,
        userId: request.userId,
        charged: true,
        stripePaymentId: "",
        amount: amount,
        status: OrderStatus.pay,
        linkReceipt: "",
        currency: CURRENCY,
      });

      await orderedTimeslotNotification(doctor.id);
      await paymentSuccessNotification(request.userId, orderData.id);
      return true;
    } catch (e) {
      throw e;
    }
  }
);

export const purchaseTimeSlotWithWallet = functions.https.onCall(
  async (request, context) => {
    try {
      const { userId, timeSlotId } = request;
      console.log("request", JSON.stringify(request));
      const userWallet = await userWalletCol.doc(userId).get();
      const userWalletHisotryRef = userWalletHistoryCol(userId);
      const purchasedTimeSlot = await timeSlotCol.doc(timeSlotId).get();
      let purchasedTimeslotData = purchasedTimeSlot.data();
      console.log(JSON.stringify(purchasedTimeslotData));
      //check if the wallet amount is sufficient to purchase this timeslot
      if (userWallet?.data()?.balance && purchasedTimeSlot?.data()?.price) {
        if (userWallet.data()!.balance < purchasedTimeSlot.data()!.price) {
          throw new functions.https.HttpsError(
            "failed-precondition",
            "User wallet balance is not sufficient to purchase this timeslot"
          );
        }
      }

      /// Purchase timeslot
      if (!purchasedTimeslotData) {
        throw new HttpsError(
          "not-found",
          "TimeSlot not found, please check the timeslot again"
        );
      }

      //Get user info who book this timeslot
      let bookByWho = await usersCol.doc(userId).get();
      let bookByWhoData = bookByWho.data();
      if (!bookByWhoData) {
        throw new HttpsError("not-found", "User book by who not found");
      }
      //Get doctor detail data
      let doctor = await doctorCol.doc(purchasedTimeslotData.doctorId).get();
      let doctorData = doctor.data();
      let amount = purchasedTimeslotData.price;
      if (!doctorData) {
        throw new HttpsError("not-found", "Doctor Data not found");
      }
      await purchasedTimeSlot.ref.update({
        charged: true,
        available: false,
        bookByWho: {
          userId: userId,
          displayName: bookByWhoData.displayName,
          photoUrl: bookByWhoData.photoUrl ? bookByWhoData.photoUrl : "",
        },
        status: TimeSlotStatus.booked,
        doctor: {
          doctorName: doctorData.doctorName,
          doctorPicture: doctorData.doctorPicture,
        },
        purchaseTime: firestore.Timestamp.fromDate(new Date()),
      });
      await orderCol.add({
        createdAt: firestore.Timestamp.fromDate(new Date()),
        timeSlotId: timeSlotId,
        userId: request.userId,
        charged: true,
        stripePaymentId: "",
        amount: amount,
        status: OrderStatus.pay,
        linkReceipt: "",
        currency: CURRENCY,
      });
      /// Reduce use wallet
      await userWalletCol.doc(userId).update({
        balance: firestore.FieldValue.increment(-amount),
        updatedAt: new Date(),
      });

      /// add user wallet history with type payment
      const walletHistory: UserWalletHistory = {
        initialAmount: 0,
        finalAmount: 0,
        transactionAmount: purchasedTimeSlot?.data()?.price ?? 0,
        createdAt: new Date(),
        userId: userId,
        status: "Success",
        type: "Payment",
      };
      await userWalletHisotryRef.add(walletHistory);
      return { success: true };
    } catch (error) {
      throw error;
    }
  }
);

/**
 * Asynchronously top up the wallet of a user with a given amount.
 *
 * @param {string} userId - The ID of the user whose wallet is being topped up.
 * @param {number} amount - The amount to top up the user's wallet with.
 * @return {Promise<void>} A promise that resolves when the wallet has been successfully topped up.
 * @throws {Error} If there is an error toping up the user's wallet.
 */

export const topUpUserWallet = async (userId: string, amount: number) => {
  try {
    const userWalletRef = userWalletCol.doc(userId);
    const userWallet = await userWalletRef.get();
    if (userWallet.exists) {
      await userWalletRef.update({
        balance: firestore.FieldValue.increment(amount),
      });
    } else {
      // throw new functions.https.HttpsError("unauthenticated", "user is not authenticated");
      throw new CustomError("not-found", "User wallet not found");
    }
  } catch (error) {
    throw error;
  }
};
