import { metrics } from "@opentelemetry/api"
import { cache } from "react"

import * as api from "@/lib/api"
import {
  protectedProcedure,
  router,
  safeProtectedProcedure,
} from "@/server/trpc"

import { countries } from "@/components/TempDesignSystem/Form/Country/countries"
import * as maskValue from "@/utils/maskValue"
import { getMembership, getMembershipCards } from "@/utils/user"

import { friendTransactionsInput, staysInput } from "./input"
import {
  creditCardsSchema,
  getFriendTransactionsSchema,
  getMembershipCardsSchema,
  getStaysSchema,
  getUserSchema,
} from "./output"
import { updateStaysBookingUrl } from "./utils"

import type { Session } from "next-auth"

import type {
  LoginType,
  TrackingSDKUserData,
} from "@/types/components/tracking"
import { Transactions } from "@/types/enums/transactions"
import { User } from "@/types/user"
import type { MembershipLevel } from "@/constants/membershipLevels"

// OpenTelemetry metrics: User
const meter = metrics.getMeter("trpc.user")
const getVerifiedUserCounter = meter.createCounter("trpc.user.get")
const getVerifiedUserSuccessCounter = meter.createCounter(
  "trpc.user.get-success"
)
const getVerifiedUserFailCounter = meter.createCounter("trpc.user.get-fail")
const getProfileCounter = meter.createCounter("trpc.user.profile")
const getProfileSuccessCounter = meter.createCounter(
  "trpc.user.profile-success"
)
const getProfileFailCounter = meter.createCounter("trpc.user.profile-fail")

// OpenTelemetry metrics: Stays
const getPreviousStaysCounter = meter.createCounter("trpc.user.stays.previous")
const getPreviousStaysSuccessCounter = meter.createCounter(
  "trpc.user.stays.previous-success"
)
const getPreviousStaysFailCounter = meter.createCounter(
  "trpc.user.stays.previous-fail"
)
const getUpcomingStaysCounter = meter.createCounter("trpc.user.stays.upcoming")
const getUpcomingStaysSuccessCounter = meter.createCounter(
  "trpc.user.stays.upcoming-success"
)
const getUpcomingStaysFailCounter = meter.createCounter(
  "trpc.user.stays.upcoming-fail"
)

// OpenTelemetry metrics: Transactions
const getFriendTransactionsCounter = meter.createCounter(
  "trpc.user.transactions.friendTransactions"
)
const getFriendTransactionsSuccessCounter = meter.createCounter(
  "trpc.user.transactions.friendTransactions-success"
)
const getFriendTransactionsFailCounter = meter.createCounter(
  "trpc.user.transactions.friendTransactions-fail"
)

// OpenTelemetry metrics: Credit Cards
const getCreditCardsCounter = meter.createCounter("trpc.user.creditCards")
const getCreditCardsSuccessCounter = meter.createCounter(
  "trpc.user.creditCards-success"
)
const getCreditCardsFailCounter = meter.createCounter(
  "trpc.user.creditCards-fail"
)

export const getVerifiedUser = cache(
  async ({ session }: { session: Session }) => {
    const now = Date.now()
    if (session.token.expires_at && session.token.expires_at < now) {
      return { error: true, cause: "token_expired" } as const
    }
    getVerifiedUserCounter.add(1)
    console.info("api.user.profile getVerifiedUser start", JSON.stringify({}))
    const apiResponse = await api.get(api.endpoints.v1.Profile.profile, {
      headers: {
        Authorization: `Bearer ${session.token.access_token}`,
      },
    })

    if (!apiResponse.ok) {
      const text = await apiResponse.text()
      getVerifiedUserFailCounter.add(1, {
        error_type: "http_error",
        error: JSON.stringify({
          status: apiResponse.status,
          statusText: apiResponse.statusText,
          text,
        }),
      })
      console.error(
        "api.user.profile getVerifiedUser error",
        JSON.stringify({
          error: {
            status: apiResponse.status,
            statusText: apiResponse.statusText,
            text,
          },
        })
      )
      if (apiResponse.status === 401) {
        return { error: true, cause: "unauthorized" } as const
      } else if (apiResponse.status === 403) {
        return { error: true, cause: "forbidden" } as const
      } else if (apiResponse.status === 404) {
        return { error: true, cause: "notfound" } as const
      }
      return {
        error: true,
        cause: "unknown",
        status: apiResponse.status,
      } as const
    }

    const apiJson = await apiResponse.json()
    if (!apiJson.data?.attributes) {
      getVerifiedUserFailCounter.add(1, {
        error_type: "data_error",
      })
      console.error(
        "api.user.profile getVerifiedUser data error",
        JSON.stringify({
          apiResponse: apiJson,
        })
      )
      return null
    }

    const verifiedData = getUserSchema.safeParse(apiJson)
    if (!verifiedData.success) {
      getVerifiedUserFailCounter.add(1, {
        error_type: "validation_error",
        error: JSON.stringify(verifiedData.error),
      })
      console.error(
        "api.user.profile validation error",
        JSON.stringify({
          errors: verifiedData.error,
        })
      )
      return null
    }
    getVerifiedUserSuccessCounter.add(1)
    console.info("api.user.profile getVerifiedUser success", JSON.stringify({}))
    return verifiedData
  }
)

export function parsedUser(data: User, isMFA: boolean) {
  const country = countries.find((c) => c.code === data.address.countryCode)

  const user = {
    address: {
      city: data.address.city,
      country: country?.name ?? "",
      countryCode: data.address.countryCode,
      streetAddress: data.address.streetAddress,
      zipCode: data.address.zipCode,
    },
    dateOfBirth: data.dateOfBirth,
    email: data.email,
    firstName: data.firstName,
    language: data.language,
    lastName: data.lastName,
    membership: getMembership(data.memberships),
    memberships: data.memberships,
    name: `${data.firstName} ${data.lastName}`,
    phoneNumber: data.phoneNumber,
    profileId: data.profileId,
  }

  if (!isMFA) {
    if (user.address.city) {
      user.address.city = maskValue.text(user.address.city)
    }
    if (user.address.streetAddress) {
      user.address.streetAddress = maskValue.text(user.address.streetAddress)
    }

    user.address.zipCode = data.address?.zipCode
      ? maskValue.text(data.address.zipCode)
      : ""

    user.dateOfBirth = maskValue.all(user.dateOfBirth)

    user.email = maskValue.email(user.email)

    user.phoneNumber = user.phoneNumber ? maskValue.phone(user.phoneNumber) : ""
  }

  return user
}

async function getCreditCards(session: Session) {
  getCreditCardsCounter.add(1)
  console.info("api.profile.creditCards start", JSON.stringify({}))
  const apiResponse = await api.get(api.endpoints.v1.Profile.creditCards, {
    headers: {
      Authorization: `Bearer ${session.token.access_token}`,
    },
  })

  if (!apiResponse.ok) {
    const text = await apiResponse.text()
    getCreditCardsFailCounter.add(1, {
      error_type: "http_error",
      error: JSON.stringify({
        status: apiResponse.status,
        statusText: apiResponse.statusText,
        text,
      }),
    })
    console.error(
      "api.profile.creditCards error ",
      JSON.stringify({
        error: {
          status: apiResponse.status,
          statusText: apiResponse.statusText,
          text,
        },
      })
    )
    return null
  }

  const apiJson = await apiResponse.json()
  const verifiedData = creditCardsSchema.safeParse(apiJson)
  if (!verifiedData.success) {
    getCreditCardsFailCounter.add(1, {
      error_type: "validation_error",
      error: JSON.stringify(verifiedData.error),
    })
    console.error(
      "api.profile.creditCards validation error ",
      JSON.stringify({ error: verifiedData.error })
    )
    return null
  }
  getCreditCardsSuccessCounter.add(1)
  console.info("api.profile.creditCards success", JSON.stringify({}))
  return verifiedData.data.data
}

export const userQueryRouter = router({
  get: protectedProcedure
    .use(async function (opts) {
      return opts.next({
        ctx: {
          ...opts.ctx,
          isMFA:
            opts.ctx.session.token.mfa_scope &&
            opts.ctx.session.token.mfa_expires_at > Date.now(),
        },
      })
    })
    .query(async function getUser({ ctx }) {
      const data = await getVerifiedUser({ session: ctx.session })

      if (!data) {
        return null
      }

      if ("error" in data) {
        return data
      }

      return parsedUser(data.data, ctx.isMFA)
    }),
  getSafely: safeProtectedProcedure.query(async function getUser({ ctx }) {
    if (!ctx.session) {
      return null
    }

    const data = await getVerifiedUser({ session: ctx.session })

    if (!data || "error" in data) {
      return null
    }

    return parsedUser(data.data, true)
  }),
  name: safeProtectedProcedure.query(async function ({ ctx }) {
    if (!ctx.session) {
      return null
    }
    const verifiedData = await getVerifiedUser({ session: ctx.session })

    if (!verifiedData || "error" in verifiedData) {
      return null
    }
    return {
      firstName: verifiedData.data.firstName,
      lastName: verifiedData.data.lastName,
    }
  }),
  membershipLevel: protectedProcedure.query(async function ({ ctx }) {
    const verifiedData = await getVerifiedUser({ session: ctx.session })
    if (!verifiedData || "error" in verifiedData) {
      return null
    }

    const membershipLevel = getMembership(verifiedData.data.memberships)
    return membershipLevel
  }),
  safeMembershipLevel: safeProtectedProcedure.query(async function ({ ctx }) {
    if (!ctx.session) {
      return null
    }
    const verifiedData = await getVerifiedUser({ session: ctx.session })

    if (!verifiedData || "error" in verifiedData) {
      return null
    }

    const membershipLevel = getMembership(verifiedData.data.memberships)
    return membershipLevel
  }),
  tracking: safeProtectedProcedure.query(async function ({ ctx }) {
    const notLoggedInUserTrackingData: TrackingSDKUserData = {
      loginStatus: "Non-logged in",
    }

    if (!ctx.session) {
      return notLoggedInUserTrackingData
    }
    const verifiedUserData = await getVerifiedUser({ session: ctx.session })

    if (!verifiedUserData || "error" in verifiedUserData) {
      return notLoggedInUserTrackingData
    }

    const params = new URLSearchParams()
    params.set("limit", "1")
    getPreviousStaysCounter.add(1, { query: JSON.stringify({ params }) })
    console.info(
      "api.booking.stays.past start",
      JSON.stringify({ query: { params } })
    )
    const previousStaysResponse = await api.get(
      api.endpoints.v1.Booking.Stays.past,
      {
        headers: {
          Authorization: `Bearer ${ctx.session.token.access_token}`,
        },
      },
      params
    )

    if (!previousStaysResponse.ok) {
      getPreviousStaysFailCounter.add(1, {
        error_type: "http_error",
        error: JSON.stringify({
          status: previousStaysResponse.status,
          statusText: previousStaysResponse.statusText,
        }),
      })
      console.error(
        "api.booking.stays.past error",
        JSON.stringify({
          error: {
            status: previousStaysResponse.status,
            statusText: previousStaysResponse.statusText,
          },
        })
      )
      return notLoggedInUserTrackingData
    }

    const previousStaysApiJson = await previousStaysResponse.json()
    const verifiedPreviousStaysData =
      getStaysSchema.safeParse(previousStaysApiJson)
    if (!verifiedPreviousStaysData.success) {
      getPreviousStaysFailCounter.add(1, {
        error_type: "validation_error",
        error: JSON.stringify(verifiedPreviousStaysData.error),
      })
      console.error(
        "api.booking.stays.past validation error, ",
        JSON.stringify({ error: verifiedPreviousStaysData.error })
      )
      return notLoggedInUserTrackingData
    }
    getPreviousStaysSuccessCounter.add(1)
    console.info("api.booking.stays.past success", JSON.stringify({}))

    const membership = getMembership(verifiedUserData.data.memberships)

    const loggedInUserTrackingData: TrackingSDKUserData = {
      loginStatus: "logged in",
      loginType: ctx.session.token.loginType as LoginType,
      memberId: membership?.membershipNumber,
      memberLevel: membership?.membershipLevel as MembershipLevel,
      noOfNightsStayed: verifiedPreviousStaysData.data.links?.totalCount ?? 0,
      totalPointsAvailableToSpend: membership?.currentPoints,
      loginAction: "login success",
    }
    return loggedInUserTrackingData
  }),

  stays: router({
    previous: protectedProcedure
      .input(staysInput)
      .query(async ({ ctx, input }) => {
        const { limit, cursor } = input
        const params: Record<string, string> = { limit }
        if (cursor) {
          params.offset = cursor
        }
        getPreviousStaysCounter.add(1, { query: JSON.stringify({ params }) })
        console.info(
          "api.booking.stays.past start",
          JSON.stringify({ query: { params } })
        )

        const apiResponse = await api.get(
          api.endpoints.v1.Booking.Stays.past,
          {
            headers: {
              Authorization: `Bearer ${ctx.session.token.access_token}`,
            },
          },
          params
        )

        if (!apiResponse.ok) {
          const text = await apiResponse.text()
          getPreviousStaysFailCounter.add(1, {
            query: JSON.stringify({ params }),
            error_type: "http_error",
            error: JSON.stringify({
              status: apiResponse.status,
              statusText: apiResponse.statusText,
              text,
            }),
          })
          console.error(
            "api.booking.stays.past error ",
            JSON.stringify({
              query: { params },
              error: {
                status: apiResponse.status,
                statusText: apiResponse.statusText,
                text,
              },
            })
          )
          return null
        }

        const apiJson = await apiResponse.json()

        const verifiedData = getStaysSchema.safeParse(apiJson)
        if (!verifiedData.success) {
          getPreviousStaysFailCounter.add(1, {
            query: JSON.stringify({ params }),
            error_type: "validation_error",
            error: JSON.stringify(verifiedData.error),
          })
          console.error(
            "api.booking.stays.past validation error ",
            JSON.stringify({
              query: { params },
              error: verifiedData.error,
            })
          )

          return null
        }
        getPreviousStaysSuccessCounter.add(1, {
          query: JSON.stringify({ params }),
        })
        console.info(
          "api.booking.stays.past success",
          JSON.stringify({ query: { params } })
        )
        const nextCursor =
          verifiedData.data.links &&
            verifiedData.data.links.offset < verifiedData.data.links.totalCount
            ? verifiedData.data.links.offset
            : undefined

        const updatedData = await updateStaysBookingUrl(
          verifiedData.data.data,
          ctx.session.token.access_token,
          ctx.lang
        )

        return {
          data: updatedData,
          nextCursor,
        }
      }),

    upcoming: protectedProcedure
      .input(staysInput)
      .query(async ({ ctx, input }) => {
        const { limit, cursor } = input

        const params: Record<string, string> = { limit }
        if (cursor) {
          params.offset = cursor
        }
        getUpcomingStaysCounter.add(1, {
          query: JSON.stringify({ params }),
        })
        console.info(
          "api.booking.stays.future start",
          JSON.stringify({ query: { params } })
        )
        const apiResponse = await api.get(
          api.endpoints.v1.Booking.Stays.future,
          {
            headers: {
              Authorization: `Bearer ${ctx.session.token.access_token}`,
            },
          },
          params
        )

        if (!apiResponse.ok) {
          const text = await apiResponse.text()
          getUpcomingStaysFailCounter.add(1, {
            query: JSON.stringify({ params }),
            error_type: "http_error",
            error: JSON.stringify({
              status: apiResponse.status,
              statusText: apiResponse.statusText,
              text,
            }),
          })
          console.error(
            "api.booking.stays.future error ",
            JSON.stringify({
              query: { params },
              error_type: "http_error",
              error: {
                status: apiResponse.status,
                statusText: apiResponse.statusText,
                text,
              },
            })
          )
          return null
        }

        const apiJson = await apiResponse.json()
        const verifiedData = getStaysSchema.safeParse(apiJson)
        if (!verifiedData.success) {
          getUpcomingStaysFailCounter.add(1, {
            query: JSON.stringify({ params }),
            error_type: "validation_error",
            error: JSON.stringify(verifiedData.error),
          })
          console.error(
            "api.booking.stays.future validation error ",
            JSON.stringify({
              query: { params },
              error: verifiedData.error,
            })
          )
          return null
        }
        getUpcomingStaysSuccessCounter.add(1, {
          query: JSON.stringify({ params }),
        })
        console.info("api.booking.stays.future success", {
          query: JSON.stringify({ params }),
        })
        const nextCursor =
          verifiedData.data.links &&
            verifiedData.data.links.offset < verifiedData.data.links.totalCount
            ? verifiedData.data.links.offset
            : undefined

        const updatedData = await updateStaysBookingUrl(
          verifiedData.data.data,
          ctx.session.token.access_token,
          ctx.lang
        )

        return {
          data: updatedData,
          nextCursor,
        }
      }),
  }),
  transaction: router({
    friendTransactions: protectedProcedure
      .input(friendTransactionsInput)
      .query(async ({ ctx, input }) => {
        const { limit, page } = input
        getFriendTransactionsCounter.add(1)
        console.info(
          "api.transaction.friendTransactions start",
          JSON.stringify({})
        )
        const apiResponse = await api.get(
          api.endpoints.v1.Profile.Transaction.friendTransactions,
          {
            cache: undefined, // override defaultOptions
            headers: {
              Authorization: `Bearer ${ctx.session.token.access_token}`,
            },
            next: { revalidate: 30 * 60 * 1000 },
          }
        )

        if (!apiResponse.ok) {
          // switch (apiResponse.status) {
          //   case 400:
          //     throw badRequestError()
          //   case 401:
          //     throw unauthorizedError()
          //   case 403:
          //     throw forbiddenError()
          //   default:
          //     throw internalServerError()
          // }
          const text = await apiResponse.text()
          getFriendTransactionsFailCounter.add(1, {
            error_type: "http_error",
            error: JSON.stringify({
              status: apiResponse.status,
              statusText: apiResponse.statusText,
              text,
            }),
          })
          console.error(
            "api.transaction.friendTransactions error ",
            JSON.stringify({
              error: {
                status: apiResponse.status,
                statusText: apiResponse.statusText,
                text,
              },
            })
          )
          return null
        }

        const apiJson = await apiResponse.json()
        const verifiedData = getFriendTransactionsSchema.safeParse(apiJson)
        if (!verifiedData.success) {
          getFriendTransactionsFailCounter.add(1, {
            error_type: "validation_error",
            error: JSON.stringify(verifiedData.error),
          })
          console.error(
            "api.transaction.friendTransactions validation error ",
            JSON.stringify({ error: verifiedData.error })
          )
          return null
        }

        getFriendTransactionsSuccessCounter.add(1)
        console.info(
          "api.transaction.friendTransactions success",
          JSON.stringify({})
        )
        const updatedData = await updateStaysBookingUrl(
          verifiedData.data.data,
          ctx.session.token.access_token,
          ctx.lang
        )

        const pageData = updatedData
          .filter((t) => t.type !== Transactions.rewardType.expired)
          .sort((a, b) => {
            // 'BALFWD' are transactions from Opera migration that happended in May 2021
            if (a.attributes.confirmationNumber === "BALFWD") return 1
            if (b.attributes.confirmationNumber === "BALFWD") return -1

            const dateA = new Date(
              a.attributes.checkinDate
                ? a.attributes.checkinDate
                : a.attributes.transactionDate
            )
            const dateB = new Date(
              b.attributes.checkinDate
                ? b.attributes.checkinDate
                : b.attributes.transactionDate
            )

            return dateA > dateB ? -1 : 1
          })

        const slicedData = pageData.slice(limit * (page - 1), limit * page)

        return {
          data: {
            transactions: slicedData.map(({ type, attributes }) => {
              return {
                type,
                awardPoints: attributes.awardPoints,
                checkinDate: attributes.checkinDate,
                checkoutDate: attributes.checkoutDate,
                city: attributes.hotelInformation?.city,
                confirmationNumber: attributes.confirmationNumber,
                hotelName: attributes.hotelInformation?.name,
                nights: attributes.nights,
                pointsCalculated: attributes.pointsCalculated,
                hotelId: attributes.hotelOperaId,
                transactionDate: attributes.transactionDate,
                bookingUrl: attributes.bookingUrl,
              }
            }),
          },
          meta: {
            totalPages: Math.ceil(pageData.length / limit),
          },
        }
      }),
  }),

  creditCards: protectedProcedure.query(async function ({ ctx }) {
    return await getCreditCards(ctx.session)
  }),
  safeCreditCards: safeProtectedProcedure.query(async function ({ ctx }) {
    if (!ctx.session) {
      return null
    }

    return await getCreditCards(ctx.session)
  }),

  membershipCards: protectedProcedure.query(async function ({ ctx }) {
    getProfileCounter.add(1)
    console.info("api.profile start", JSON.stringify({}))
    const apiResponse = await api.get(api.endpoints.v1.Profile.profile, {
      cache: "no-store",
      headers: {
        Authorization: `Bearer ${ctx.session.token.access_token}`,
      },
    })

    if (!apiResponse.ok) {
      // switch (apiResponse.status) {
      //   case 400:
      //     throw badRequestError()
      //   case 401:
      //     throw unauthorizedError()
      //   case 403:
      //     throw forbiddenError()
      //   default:
      //     throw internalServerError()
      // }
      const text = await apiResponse.text()
      getProfileFailCounter.add(1, {
        error_type: "http_error",
        error: JSON.stringify({
          status: apiResponse.status,
          statusText: apiResponse.statusText,
          text,
        }),
      })
      console.error(
        "api.profile error",
        JSON.stringify({
          error: {
            status: apiResponse.status,
            statusText: apiResponse.statusText,
            text,
          },
        })
      )
    }

    const apiJson = await apiResponse.json()

    const verifiedData = getMembershipCardsSchema.safeParse(
      apiJson.data.attributes.memberships
    )

    if (!verifiedData.success) {
      getProfileFailCounter.add(1, {
        error_type: "validation_error",
        error: JSON.stringify(verifiedData),
      })
      console.error(
        "api.profile validation error",
        JSON.stringify({ error: verifiedData })
      )
      return null
    }
    getProfileSuccessCounter.add(1)
    console.info("api.profile success", JSON.stringify({}))
    const cards = getMembershipCards(verifiedData.data)

    return cards
  }),
})
