import * as functions from "firebase-functions";
import Stripe from "stripe";
import {
  doctorCol,
  orderCol,
  OrderStatus,
  timeSlotCol,
  topUpOrderCol,
  usersCol,
} from "./collections";
import { CURRENCY } from "./constants";
import { firestore } from "firebase-admin";
import {
  PaymentMethod,
  PaymentStatus,
  topUpUserWallet,
} from "./payment-functions";
import {
  orderedTimeslotNotification,
  paymentSuccessNotification,
} from "./notification-function";
import { refundCol } from "./collections";
import { refundTimeslot } from "./timeslot-functions";
import { TopUpOrderStatus } from "./model/topup_order_model";
import { CustomError } from "./utils/custom_error";
import {
  addHistoryWalletUser,
  createWalletDataForUser,
} from "./user-wallet-functions";
import { UserWalletHistory } from "./model/user_wallet_model";

enum PaymentType {
  TopUp = "topup",
  TimeSlot = "timeslot",
}

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2020-08-27",
});

/**
 *
 * function when client topup their wallet, by default we use Stripe payment gateway
 * you can change this to your payment gateway by following the stripe structure,
 * adjust it with your own payment gateway
 */
export const createOrderTopUpStripe = functions.https.onCall(
  async (request, response) => {
    try {
      const { amount, uid } = request;

      if (amount <= 0) {
        throw new functions.https.HttpsError(
          "invalid-argument",
          "the amount must be greater than 0"
        );
      }

      /// Create order data in database
      const order = await topUpOrderCol.add({
        amount: amount,
        createdAt: new Date(),
        userId: uid,
        currency: CURRENCY,
        status: TopUpOrderStatus.created,
        paymentMethod: PaymentMethod.Stripe,
      });

      const paymentIntent = await stripe.paymentIntents.create({
        amount: amount * 100,
        currency: CURRENCY,
        payment_method_types: ["card"],
        metadata: {
          type: PaymentType.TopUp,
          uid: uid,
          orderId: order.id,
        },
      });
      if (paymentIntent) {
        await topUpOrderCol.doc(order.id).update({
          stripePaymentId: paymentIntent.id,
        });
        return { clientSecret: paymentIntent.client_secret };
      } else {
        await topUpOrderCol.doc(order.id).update({
          status: TopUpOrderStatus.failed,
        });
        throw new functions.https.HttpsError(
          "internal",
          "failed to create Stripe payment intent"
        );
      }
    } catch (error) {
      throw error;
    }
  }
);
/**
 *
 * function when client purchase timeslot, by default we use Stripe payment gateway
 * you can change this to your payment gateway by following the stripe structure of purchaing the timeslot
 */
export const purchaseTimeslot = functions.https.onCall(
  async (request, response) => {
    try {
      const choosedTimeSlot = (
        await timeSlotCol.doc(request.timeSlotId).get()
      ).data();
      if (!choosedTimeSlot) {
        throw "choosed timeslot is not found";
      }
      const paymentIntent = await stripe.paymentIntents.create({
        amount: choosedTimeSlot.price * 100,
        currency: CURRENCY,
        payment_method_types: ["card"],
        metadata: {
          type: PaymentType.TimeSlot,
          uid: request.userId,
        },
      });

      await orderCol.add({
        createdAt: firestore.Timestamp.fromDate(new Date()),
        timeSlotId: request.timeSlotId,
        userId: request.userId,
        charged: false,
        stripePaymentId: paymentIntent.id,
        status: OrderStatus.notPay,
        paymentMethod: PaymentMethod.Stripe,
      });
      return paymentIntent.client_secret;
    } catch (error) {
      throw error;
    }
  }
);

/**
 * this function is for stripe webhook, when client successfully purchase the timeslot, stripe will call this webhook
 * make sure you have setup the stripe webhook by following this tutorial {@link https://halodoctor.netlify.app/docs/payment-gateway/stripe-webook}
 */
export const stripeWebhook = functions.https.onRequest(
  async (request, response) => {
    let event;
    try {
      if (process.env.STRIPE_WEBHOOK_SECRET == undefined) {
        throw new Error(
          "Stripe webhook secret is undefined, please check .env file"
        );
      }
      const stripeWebhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
      event = stripe.webhooks.constructEvent(
        request.rawBody,
        request.headers["stripe-signature"]!,
        stripeWebhookSecret
      );
    } catch (error) {
      console.error("Webhook signature verification failed");
      response.sendStatus(400);
      return;
    }

    switch (event.type) {
      case "payment_intent.succeeded":
        try {
          const amount = request.body.data.object.amount_received / 100;
          const currency = request.body.data.object.currency;
          const userId = request.body.data.object.metadata.uid;

          const type: PaymentType = request.body.data.object.metadata.type;
          if (type == PaymentType.TopUp) {
            const confirmTopUpParam: ConfirmTopUpOrderParams = {
              stripePaymentId: request.body.data.object.id,
              currency: currency,
              orderId: request.body.data.object.metadata.orderId,
            };
            await confirmTopUpOrder(confirmTopUpParam);
            const walletHistory: UserWalletHistory = {
              type: "TopUp",
              initialAmount: 0,
              finalAmount: 0,
              transactionAmount: amount,
              createdAt: new Date(),
              userId: userId,
              status: "Success",
              description: "Top Up Success",
            };
            await addHistoryWalletUser(userId, walletHistory);
            response.send();
            return;
          }
          let order = await orderCol
            .where("stripePaymentId", "==", request.body.data.object.id)
            .get();

          let orderRef = order.docs[0];

          orderRef.ref.update({
            charged: true,
            amount: amount,
            status: PaymentStatus.PaymentSuccess,
            currency: currency,
          });

          //Get user info who book this timeslot
          let bookByWho = (
            await usersCol.doc(orderRef.data().userId).get()
          ).data();

          const timeSlot = await timeSlotCol
            .doc(orderRef.data().timeSlotId)
            .get();
          const timeSlotData = timeSlot.data();
          if (!timeSlotData) {
            throw "selected timeslot is null or undefined";
          }
          const doctor = await doctorCol.doc(timeSlotData.doctorId).get();
          timeSlot.ref.update({
            charged: true,
            available: false,
            bookByWho: {
              userId: orderRef.data().userId,
              displayName: bookByWho?.displayName,
              photoUrl: bookByWho?.photoUrl ? bookByWho?.photoUrl : "",
            },
            status: "booked",
            doctor: {
              doctorName: doctor.data()?.doctorName,
              doctorPicture: doctor.data()?.doctorPicture,
            },
            purchaseTime: firestore.Timestamp.fromDate(new Date()),
          });
          await orderedTimeslotNotification(doctor.id);
          await paymentSuccessNotification(orderRef.data().userId, orderRef.id);
          console.log("payment success");
          break;
        } catch (error) {
          console.log("error " + error);
        }
      case "payment_intent.canceled":
        console.log("failed payment ");
        break;
      // ... handle other event types
      default:
        console.log(`Unhandled event type ${event.type}`);
    }
    // Return a 200 response to acknowledge receipt of the event
    response.send();
  }
);
/** 
by default we use stripe refund system if you use other payment gateway you need to change this function to
you can just follow the structure of the function
*/

export const refundStripe = functions.https.onCall(
  async (request, response) => {
    try {
      const { timeSlotId } = request;
      const orderRef = await orderCol
        .where("timeSlotId", "==", request.timeSlotId)
        .get();
      const order = orderRef.docs[0];
      const refund = await stripe.refunds.create({
        payment_intent: order.data().stripePaymentId,
      });
      if (refund.status === "succeeded") {
        let refundData = await refundCol.add({
          createdAt: firestore.Timestamp.fromDate(new Date()),
          timeSlotId: request.timeSlotId,
          paymentId: order.data().stripePaymentId,
          status: refund.status,
          amount: refund.amount,
          refundId: refund.id,
          currency: refund.currency,
        });
        await refundTimeslot(timeSlotId, refundData.id);
        console.log("refund success : " + JSON.stringify(refund));
      } else {
        throw "refund failed";
      }
    } catch (err) {
      throw err;
    }
  }
);

interface ConfirmTopUpOrderParams {
  stripePaymentId: string;
  currency: string;
  orderId: string;
}
export const confirmTopUpOrder = async (param: ConfirmTopUpOrderParams) => {
  try {
    let orderRef = topUpOrderCol.doc(param.orderId);
    let order = await orderRef.get();

    ///Update the Top Up Order
    if (order.exists) {
      await orderRef.update({
        status: TopUpOrderStatus.paid,
        updatedAt: new Date(),
      });
      ///Add balance to user account
      if (order.data()?.userId) {
        await topUpUserWallet(order.data()?.userId!, order.data()?.amount!);
      }
    }
    console.log("Topu Successful, Order id : " + param.orderId);
  } catch (error) {
    if (error instanceof CustomError && error.code === "not-found") {
      let orderRef = topUpOrderCol.doc(param.orderId);
      let order = await orderRef.get();

      ///Update the Top Up Order
      if (order.exists) {
        ///Add balance to user account
        if (order.data()?.userId) {
          await createWalletDataForUser(order.data()!.userId);
          await topUpUserWallet(order.data()?.userId!, order.data()?.amount!);
        }
      }
    }
    throw error;
  }
};
