import { metrics } from "@opentelemetry/api"

import * as api from "@/lib/api"
import { dt } from "@/lib/dt"
import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc"
import { router, serviceProcedure } from "@/server/trpc"

import { getHotelData } from "../hotels/query"
import { bookingConfirmationInput, getBookingStatusInput } from "./input"
import { bookingConfirmationSchema, createBookingSchema } from "./output"

const meter = metrics.getMeter("trpc.booking")
const getBookingConfirmationCounter = meter.createCounter(
  "trpc.booking.confirmation"
)
const getBookingConfirmationSuccessCounter = meter.createCounter(
  "trpc.booking.confirmation-success"
)
const getBookingConfirmationFailCounter = meter.createCounter(
  "trpc.booking.confirmation-fail"
)

const getBookingStatusCounter = meter.createCounter("trpc.booking.status")
const getBookingStatusSuccessCounter = meter.createCounter(
  "trpc.booking.status-success"
)
const getBookingStatusFailCounter = meter.createCounter(
  "trpc.booking.status-fail"
)

export const bookingQueryRouter = router({
  confirmation: serviceProcedure
    .input(bookingConfirmationInput)
    .query(async function ({ ctx, input: { confirmationNumber } }) {
      getBookingConfirmationCounter.add(1, { confirmationNumber })

      const apiResponse = await api.get(
        api.endpoints.v1.Booking.booking(confirmationNumber),
        {
          headers: {
            Authorization: `Bearer ${ctx.serviceToken}`,
          },
        }
      )

      if (!apiResponse.ok) {
        const responseMessage = await apiResponse.text()
        getBookingConfirmationFailCounter.add(1, {
          confirmationNumber,
          error_type: "http_error",
          error: responseMessage,
        })
        console.error(
          "api.booking.confirmation error",
          JSON.stringify({
            query: { confirmationNumber },
            error: {
              status: apiResponse.status,
              statusText: apiResponse.statusText,
              text: responseMessage,
            },
          })
        )

        throw serverErrorByStatus(apiResponse.status, apiResponse)
      }

      const apiJson = await apiResponse.json()
      const booking = bookingConfirmationSchema.safeParse(apiJson)
      if (!booking.success) {
        getBookingConfirmationFailCounter.add(1, {
          confirmationNumber,
          error_type: "validation_error",
          error: JSON.stringify(booking.error),
        })
        console.error(
          "api.booking.confirmation validation error",
          JSON.stringify({
            query: { confirmationNumber },
            error: booking.error,
          })
        )
        throw badRequestError()
      }

      const hotelData = await getHotelData(
        { hotelId: booking.data.hotelId, language: ctx.lang },
        ctx.serviceToken
      )

      if (!hotelData) {
        getBookingConfirmationFailCounter.add(1, {
          confirmationNumber,
          hotelId: booking.data.hotelId,
          error_type: "http_error",
          error: "Couldn`t get hotel",
        })
        console.error(
          "api.booking.confirmation error",
          JSON.stringify({
            query: { confirmationNumber, hotelId: booking.data.hotelId },
            error: {
              status: apiResponse.status,
              statusText: apiResponse.statusText,
              text: "Couldn`t get hotel",
            },
          })
        )

        throw serverErrorByStatus(404)
      }

      getBookingConfirmationSuccessCounter.add(1, { confirmationNumber })
      console.info(
        "api.booking.confirmation success",
        JSON.stringify({
          query: { confirmationNumber },
        })
      )

      /**
       * Add hotels check in and out times to booking check in and out date
       * as that is date only (YYYY-MM-DD)
       */
      const checkInTime =
        hotelData.data.attributes.hotelFacts.checkin.checkInTime
      const [checkInHour, checkInMinute] = checkInTime.split(":")
      const checkIn = dt(booking.data.checkInDate)
        .set("hour", Number(checkInHour))
        .set("minute", Number(checkInMinute))
      const checkOutTime =
        hotelData.data.attributes.hotelFacts.checkin.checkOutTime
      const [checkOutHour, checkOutMinute] = checkOutTime.split(":")
      const checkOut = dt(booking.data.checkOutDate)
        .set("hour", Number(checkOutHour))
        .set("minute", Number(checkOutMinute))

      booking.data.checkInDate = checkIn.toDate()
      booking.data.checkOutDate = checkOut.toDate()

      return {
        booking: booking.data,
        hotel: {
          ...hotelData.data.attributes,
          included: hotelData.included,
        },
      }
    }),
  status: serviceProcedure.input(getBookingStatusInput).query(async function ({
    ctx,
    input,
  }) {
    const { confirmationNumber } = input
    getBookingStatusCounter.add(1, { confirmationNumber })

    const apiResponse = await api.get(
      api.endpoints.v1.Booking.status(confirmationNumber),
      {
        headers: {
          Authorization: `Bearer ${ctx.serviceToken}`,
        },
      }
    )

    if (!apiResponse.ok) {
      const responseMessage = await apiResponse.text()
      getBookingStatusFailCounter.add(1, {
        confirmationNumber,
        error_type: "http_error",
        error: responseMessage,
      })
      console.error(
        "api.booking.status error",
        JSON.stringify({
          query: { confirmationNumber },
          error: {
            status: apiResponse.status,
            statusText: apiResponse.statusText,
            text: responseMessage,
          },
        })
      )

      throw serverErrorByStatus(apiResponse.status, apiResponse)
    }

    const apiJson = await apiResponse.json()
    const verifiedData = createBookingSchema.safeParse(apiJson)
    if (!verifiedData.success) {
      getBookingStatusFailCounter.add(1, {
        confirmationNumber,
        error_type: "validation_error",
        error: JSON.stringify(verifiedData.error),
      })
      console.error(
        "api.booking.status validation error",
        JSON.stringify({
          query: { confirmationNumber },
          error: verifiedData.error,
        })
      )
      throw badRequestError()
    }

    getBookingStatusSuccessCounter.add(1, { confirmationNumber })
    console.info(
      "api.booking.status success",
      JSON.stringify({
        query: { confirmationNumber },
      })
    )

    return verifiedData.data
  }),
})
