import { type NextMiddleware, NextResponse } from "next/server"

import {
  loyaltyPagesWebviews,
  myPagesWebviews,
  myStayWebviews,
  refreshWebviews,
  webviews,
} from "@/constants/routes/webviews"
import { env } from "@/env/server"
import { badRequest, notFound } from "@/server/errors/next"

import { decryptData } from "@/utils/aes"
import { resolve as resolveEntry } from "@/utils/entry"
import { findLang } from "@/utils/languages"

import { getDefaultRequestHeaders } from "./utils"

import type { MiddlewareMatcher } from "@/types/middleware"
import type { Lang } from "@/constants/languages"

export const middleware: NextMiddleware = async (request) => {
  const { nextUrl } = request
  const lang = findLang(nextUrl.pathname)!

  const loginTypeHeader = request.headers.get("loginType")
  const loginTypeSearchParam = nextUrl.searchParams.get("loginType")

  const adobeMc = nextUrl.searchParams.get("adobe_mc")

  const headers = getDefaultRequestHeaders(request)

  // LoginType is passed from the mobile app as a header and needs to be passed around with each subsequent
  // request within webviews due to tracking. We set the loginType as a header and pass it along to future
  // requests, and to read it from TRPC side (which is where the tracking object is created).
  // The value is appended to all webview links as a search param, just like adobe_mc.
  const loginType = loginTypeHeader || loginTypeSearchParam
  if (loginType) {
    headers.set("loginType", loginType)
  }

  // adobe_mc (Experience Cloud ID) needs to be passed around as a search param, which will be read
  // from the URL by the tracking SDK. Adobe_mc is passed from the mobile app as a searchParam, and
  // then passed to nextjs as a header. In the RSC, the adobe_mc is appended as a searchParam on each webview link.
  if (adobeMc) {
    headers.set("adobe_mc", adobeMc)
  }

  // If user is redirected to /lang/webview/refresh/, the webview token is invalid and we remove the cookie
  if (refreshWebviews.includes(nextUrl.pathname)) {
    return NextResponse.rewrite(
      new URL(
        `/${lang}/webview/refresh?${nextUrl.searchParams.toString()}`,
        nextUrl
      ),
      {
        headers: {
          "Set-Cookie": `webviewToken=0; Max-Age=0; Secure; HttpOnly; Path=/; SameSite=Strict;`,
        },
        request: {
          headers,
        },
      }
    )
  }

  const webviewToken = request.cookies.get("webviewToken")
  if (webviewToken) {
    // since the token exists, this is a subsequent visit
    // we're done, allow it
    return handleWebviewRewrite({
      nextUrl,
      headers,
      decryptedData: null,
      lang,
      setCookie: false,
    })
  }

  try {
    // Authorization header is required for webviews
    // It should be base64 encoded
    const authorization = request.headers.get("X-Authorization")!
    if (!authorization) {
      console.error("Authorization header is missing")
      return badRequest("Authorization header is missing")
    }

    // Initialization vector header is required for webviews
    // It should be base64 encoded
    const initializationVector = request.headers.get("X-AES-IV")!
    if (!initializationVector) {
      console.error("initializationVector header is missing")
      return badRequest("initializationVector header is missing")
    }

    const decryptedData = await decryptData(
      env.WEBVIEW_ENCRYPTION_KEY,
      initializationVector,
      authorization
    )

    headers.append("Cookie", `webviewToken=${decryptedData}`)

    return handleWebviewRewrite({
      nextUrl,
      headers,
      decryptedData,
      lang,
      setCookie: true,
    })
  } catch (e) {
    if (e instanceof Error) {
      console.error("Error in webView middleware")
      console.error(`${e.name}: ${e.message}`)
    }

    return badRequest()
  }
}

async function handleWebviewRewrite({
  nextUrl,
  headers,
  decryptedData,
  lang,
  setCookie,
}: {
  nextUrl: URL
  headers: Headers
  decryptedData: string | null
  lang: Lang
  setCookie: boolean
}) {
  const path = nextUrl.pathname

  if (myStayWebviews.includes(path)) {
    return NextResponse.next({
      request: { headers },
      ...(setCookie && {
        headers: {
          "Set-Cookie": `webviewToken=${decryptedData}; Secure; HttpOnly; Path=/; SameSite=Strict;`,
        },
      }),
    })
  }

  const pathNameWithoutLang = path.replace(`/${lang}/webview`, "")

  const { uid } = await resolveEntry(pathNameWithoutLang, lang)
  if (uid) {
    headers.set("x-uid", uid)
  }

  if (myPagesWebviews.includes(path)) {
    return NextResponse.rewrite(
      new URL(`/${lang}/webview/account-page/${uid}`, nextUrl),
      {
        request: { headers },
        ...(setCookie && {
          headers: {
            "Set-Cookie": `webviewToken=${decryptedData}; Secure; HttpOnly; Path=/; SameSite=Strict;`,
          },
        }),
      }
    )
  }

  if (loyaltyPagesWebviews.includes(path)) {
    return NextResponse.rewrite(
      new URL(`/${lang}/webview/loyalty-page/${uid}`, nextUrl),
      {
        request: { headers },
        ...(setCookie && {
          headers: {
            "Set-Cookie": `webviewToken=${decryptedData}; Secure; HttpOnly; Path=/; SameSite=Strict;`,
          },
        }),
      }
    )
  }

  return notFound()
}

export const matcher: MiddlewareMatcher = (request) => {
  const { nextUrl } = request

  return webviews.includes(nextUrl.pathname)
}
