import * as api from "@/lib/api"
import { createRefIdPlugin } from "@/server/plugins/refIdToConfirmationNumber"
import { getMembershipNumber } from "@/server/routers/user/utils"
import { createCounter } from "@/server/telemetry"
import { router, safeProtectedServiceProcedure } from "@/server/trpc"

import { encrypt } from "@/utils/encryption"
import { isValidSession } from "@/utils/session"

import {
  addPackageInput,
  cancelBookingsInput,
  createBookingInput,
  guaranteeBookingInput,
  removePackageInput,
  updateBookingInput,
} from "./input"
import { bookingConfirmationSchema, createBookingSchema } from "./output"
import { cancelBooking } from "./utils"

const refIdPlugin = createRefIdPlugin()

export const bookingMutationRouter = router({
  create: safeProtectedServiceProcedure
    .input(createBookingInput)
    .use(async ({ ctx, next }) => {
      const token = isValidSession(ctx.session)
        ? ctx.session.token.access_token
        : ctx.serviceToken

      return next({
        ctx: {
          token,
        },
      })
    })
    .mutation(async function ({ ctx, input }) {
      const { language, ...inputWithoutLang } = input
      const { rooms, ...loggableInput } = inputWithoutLang

      const createBookingCounter = createCounter("trpc.booking", "create")
      const metricsCreateBooking = createBookingCounter.init({
        membershipNumber: await getMembershipNumber(ctx.session),
        language,
        ...loggableInput,
        rooms: inputWithoutLang.rooms.map(({ guest, ...room }) => {
          const { becomeMember, membershipNumber } = guest
          return { ...room, guest: { becomeMember, membershipNumber } }
        }),
      })

      metricsCreateBooking.start()

      const headers = {
        Authorization: `Bearer ${ctx.token}`,
      }

      const apiResponse = await api.post(
        api.endpoints.v1.Booking.bookings,
        {
          headers,
          body: inputWithoutLang,
        },
        { language }
      )

      if (!apiResponse.ok) {
        await metricsCreateBooking.httpError(apiResponse)

        const apiJson = await apiResponse.json()
        if ("errors" in apiJson && apiJson.errors.length) {
          const error = apiJson.errors[0]
          return { error: true, cause: error.code } as const
        }

        return null
      }

      const apiJson = await apiResponse.json()

      const verifiedData = createBookingSchema.safeParse(apiJson)
      if (!verifiedData.success) {
        metricsCreateBooking.validationError(verifiedData.error)
        return null
      }

      metricsCreateBooking.success()

      const expire = Math.floor(Date.now() / 1000) + 60 // 1 minute expiry

      return {
        booking: verifiedData.data,
        sig: encrypt(expire.toString()),
      }
    }),
  priceChange: safeProtectedServiceProcedure
    .concat(refIdPlugin.toConfirmationNumber)
    .use(async ({ ctx, next }) => {
      const token = isValidSession(ctx.session)
        ? ctx.session.token.access_token
        : ctx.serviceToken

      return next({
        ctx: {
          token,
        },
      })
    })
    .mutation(async function ({ ctx }) {
      const { confirmationNumber, token } = ctx

      const priceChangeCounter = createCounter("trpc.booking", "price-change")
      const metricsPriceChange = priceChangeCounter.init({ confirmationNumber })

      metricsPriceChange.start()

      const headers = {
        Authorization: `Bearer ${token}`,
      }

      const apiResponse = await api.put(
        api.endpoints.v1.Booking.priceChange(confirmationNumber),
        {
          headers,
        }
      )

      if (!apiResponse.ok) {
        await metricsPriceChange.httpError(apiResponse)
        return null
      }

      const apiJson = await apiResponse.json()
      const verifiedData = createBookingSchema.safeParse(apiJson)
      if (!verifiedData.success) {
        metricsPriceChange.validationError(verifiedData.error)
        return null
      }

      metricsPriceChange.success()

      return verifiedData.data
    }),
  cancel: safeProtectedServiceProcedure
    .input(cancelBookingsInput)
    .concat(refIdPlugin.toConfirmationNumbers)
    .use(async ({ ctx, next }) => {
      const token = isValidSession(ctx.session)
        ? ctx.session.token.access_token
        : ctx.serviceToken

      return next({
        ctx: {
          token,
        },
      })
    })
    .mutation(async function ({ ctx, input }) {
      const { confirmationNumbers, token } = ctx
      const { language } = input

      const responses = await Promise.allSettled(
        confirmationNumbers.map((confirmationNumber) =>
          cancelBooking(confirmationNumber, language, token)
        )
      )

      const cancelledRoomsSuccessfully: (string | null)[] = []
      for (const [idx, response] of responses.entries()) {
        if (response.status === "fulfilled") {
          if (response.value) {
            cancelledRoomsSuccessfully.push(confirmationNumbers[idx])
            continue
          }
        } else {
          console.info(
            `Cancelling booking failed for confirmationNumber: ${confirmationNumbers[idx]}`
          )
          console.error(response.reason)
        }

        cancelledRoomsSuccessfully.push(null)
      }

      return cancelledRoomsSuccessfully
    }),
  packages: safeProtectedServiceProcedure
    .input(addPackageInput)
    .concat(refIdPlugin.toConfirmationNumber)
    .use(async ({ ctx, next }) => {
      const token = isValidSession(ctx.session)
        ? ctx.session.token.access_token
        : ctx.serviceToken

      return next({
        ctx: {
          token,
        },
      })
    })
    .mutation(async function ({ ctx, input }) {
      const { confirmationNumber, token } = ctx
      const { language, refId, ...body } = input

      const addPackageCounter = createCounter("trpc.booking", "package.add")
      const metricsAddPackage = addPackageCounter.init({
        confirmationNumber,
        language,
      })

      metricsAddPackage.start()

      const headers = {
        Authorization: `Bearer ${token}`,
      }

      const apiResponse = await api.post(
        api.endpoints.v1.Booking.packages(confirmationNumber),
        {
          headers,
          body: body,
        },
        { language }
      )

      if (!apiResponse.ok) {
        await metricsAddPackage.httpError(apiResponse)
        return null
      }

      const apiJson = await apiResponse.json()
      const verifiedData = createBookingSchema.safeParse(apiJson)
      if (!verifiedData.success) {
        metricsAddPackage.validationError(verifiedData.error)
        return null
      }

      metricsAddPackage.success()

      return verifiedData.data
    }),
  guarantee: safeProtectedServiceProcedure
    .input(guaranteeBookingInput)
    .concat(refIdPlugin.toConfirmationNumber)
    .use(async ({ ctx, next }) => {
      const token = isValidSession(ctx.session)
        ? ctx.session.token.access_token
        : ctx.serviceToken

      return next({
        ctx: {
          token,
        },
      })
    })
    .mutation(async function ({ ctx, input }) {
      const { confirmationNumber, token } = ctx
      const { language, refId, ...body } = input

      const guaranteeBookingCounter = createCounter("trpc.booking", "guarantee")
      const metricsGuaranteeBooking = guaranteeBookingCounter.init({
        confirmationNumber,
        language,
      })

      metricsGuaranteeBooking.start()

      const headers = {
        Authorization: `Bearer ${token}`,
      }

      const apiResponse = await api.put(
        api.endpoints.v1.Booking.guarantee(confirmationNumber),
        {
          headers,
          body: body,
        },
        { language }
      )

      if (!apiResponse.ok) {
        await metricsGuaranteeBooking.httpError(apiResponse)
        return null
      }

      const apiJson = await apiResponse.json()
      const verifiedData = createBookingSchema.safeParse(apiJson)
      if (!verifiedData.success) {
        metricsGuaranteeBooking.validationError(verifiedData.error)
        return null
      }

      metricsGuaranteeBooking.success()

      return verifiedData.data
    }),
  update: safeProtectedServiceProcedure
    .input(updateBookingInput)
    .concat(refIdPlugin.toConfirmationNumber)
    .use(async ({ ctx, next }) => {
      const token = isValidSession(ctx.session)
        ? ctx.session.token.access_token
        : ctx.serviceToken

      return next({
        ctx: {
          token,
        },
      })
    })
    .mutation(async function ({ ctx, input }) {
      const { confirmationNumber, token } = ctx
      const { language, refId, ...body } = input

      const updateBookingCounter = createCounter("trpc.booking", "update")
      const metricsUpdateBooking = updateBookingCounter.init({
        confirmationNumber,
        language,
      })

      metricsUpdateBooking.start()

      const apiResponse = await api.put(
        api.endpoints.v1.Booking.booking(confirmationNumber),
        {
          body,
          headers: {
            Authorization: `Bearer ${token}`,
          },
        },
        { language }
      )

      if (!apiResponse.ok) {
        await metricsUpdateBooking.httpError(apiResponse)
        return null
      }

      const apiJson = await apiResponse.json()

      const verifiedData = bookingConfirmationSchema.safeParse(apiJson)
      if (!verifiedData.success) {
        metricsUpdateBooking.validationError(verifiedData.error)
        return null
      }

      metricsUpdateBooking.success()

      return verifiedData.data
    }),
  removePackage: safeProtectedServiceProcedure
    .input(removePackageInput)
    .concat(refIdPlugin.toConfirmationNumber)
    .use(async ({ ctx, next }) => {
      const token = isValidSession(ctx.session)
        ? ctx.session.token.access_token
        : ctx.serviceToken

      return next({
        ctx: {
          token,
        },
      })
    })
    .mutation(async function ({ ctx, input }) {
      const { confirmationNumber, token } = ctx
      const { codes, language } = input

      const removePackageCounter = createCounter(
        "trpc.booking",
        "package.remove"
      )
      const metricsRemovePackage = removePackageCounter.init({
        confirmationNumber,
        codes,
        language,
      })

      metricsRemovePackage.start()

      const headers = {
        Authorization: `Bearer ${token}`,
      }

      const apiResponse = await api.remove(
        api.endpoints.v1.Booking.packages(confirmationNumber),
        {
          headers,
        },
        [["language", language], ...codes.map((code) => ["codes", code])]
      )

      if (!apiResponse.ok) {
        await metricsRemovePackage.httpError(apiResponse)
        return false
      }

      metricsRemovePackage.success()

      return true
    }),
})
