import crypto from "crypto"
import jwt from "jsonwebtoken"

import {
  type WarmupFunctionsKey,
  warmupKeys,
} from "@/services/warmup/warmupKeys"
import { safeTry } from "@/utils/safeTry"
import { timeout } from "@/utils/timeout"

import type { Config, Context } from "@netlify/functions"

async function WarmupHandler(request: Request, context: Context) {
  const [_, error] = await safeTry(validateRequest(request, context))

  if (error) {
    if (error instanceof Error) {
      switch (error.message as ErrorCode) {
        case ErrorCodes.WARMUP_DISABLED:
          console.warn("[WARMUP] Warmup is disabled")
          return
        case ErrorCodes.REQUEST_NOT_FOR_CURRENT_CONTEXT:
          // This is expected, this webhook will be called for all deployments
          // and we only want to warmup the ones that match our URL
          return
      }
    }

    console.error("[WARMUP] Warmup failed", error)
    return
  }

  console.log("[WARMUP] Request is valid, starting warmup")
  await performWarmup(context)
  console.log("[WARMUP] Warmup completed")
}

async function validateRequest(
  request: Request,
  context: Context
): Promise<true> {
  if (request.method !== "POST") {
    throw new Error(ErrorCodes.METHOD_NOT_ALLOWED)
  }

  const warmupEnabled =
    true ||
    process.env.WARMUP_ENABLED === "true" ||
    Netlify.env.get("WARMUP_ENABLED") === "true"

  if (!warmupEnabled) {
    throw new Error(ErrorCodes.WARMUP_DISABLED)
  }

  if (!request.body) {
    throw new Error(ErrorCodes.MISSING_BODY)
  }

  const body = await request.text()
  console.log("[WARMUP] Warmup body", body)
  const deployment = JSON.parse(body) as DeploymentInfo
  if (!deployment) {
    throw new Error(ErrorCodes.UNABLE_TO_PARSE_DEPLOYMENT_INFO)
  }

  const isForCurrentContext = isRequestForCurrentContext({
    currentUrl: context.url.origin,
    deployedUrl: deployment.deploy_ssl_url,
  })

  if (!isForCurrentContext) {
    throw new Error(ErrorCodes.REQUEST_NOT_FOR_CURRENT_CONTEXT)
  }

  console.log("[WARMUP] Warmup request", deployment)
  let signature: string
  try {
    const headerValue = request.headers.get("x-webhook-signature")
    if (!headerValue) {
      throw new Error(ErrorCodes.MISSING_SIGNATURE_HEADER)
    }
    signature = headerValue
  } catch (e) {
    console.warn("[WARMUP] Failed to parse signature", e)
    throw new Error(ErrorCodes.FAILED_TO_PARSE_SIGNATURE)
  }

  if (!signature) {
    throw new Error(ErrorCodes.MISSING_SIGNATURE)
  }

  const isValid = await validateSignature(signature, body)

  if (!isValid) {
    throw new Error(ErrorCodes.INVALID_SIGNATURE)
  }

  return true
}

export async function performWarmup(context: Context) {
  for (const key of warmupKeys) {
    console.log("[WARMUP] Warming up cache", key)
    await callWarmup(key, context)
    // allow api to catch up
    await timeout(1000)
  }
}

async function callWarmup(key: WarmupFunctionsKey, context: Context) {
  const baseUrl = context.url.origin

  const url = new URL("/api/web/warmup", baseUrl)
  url.searchParams.set("key", key)

  const warmupToken =
    process.env.WARMUP_TOKEN || Netlify.env.get("WARMUP_TOKEN")

  try {
    const response = await fetch(url, {
      headers: {
        cache: "no-store",
        Authorization: `Bearer ${warmupToken}`,
      },
    })

    if (!response.ok) {
      console.error(
        `[WARMUP] Warmup failed '${url.href}' with error: ${response.status}: ${response.statusText}`
      )
    }
  } catch (error) {
    console.error(
      `[WARMUP] Warmup failed '${url.href}' with error: ${error instanceof Error ? error.message : error}`
    )
  }
}

export default WarmupHandler
export const config: Config = {
  method: "POST",
}

async function validateSignature(token: string, buffer: string) {
  try {
    const secret =
      process.env.WARMUP_SIGNATURE_SECRET ||
      Netlify.env.get("WARMUP_SIGNATURE_SECRET")

    if (!secret) {
      throw new Error("WARMUP_SIGNATURE_SECRET is not set")
    }

    const options: jwt.VerifyOptions = {
      issuer: "netlify",
      algorithms: ["HS256"],
    }
    const decoded = jwt.verify(token, secret, options)
    const hashedBody = crypto.createHash("sha256").update(buffer).digest("hex")

    if (!hasSha256(decoded)) {
      console.error(
        "[WARMUP] Decoded jwt does not contain sha256, unable to verify signature"
      )
      return false
    }

    return decoded.sha256 === hashedBody
  } catch (error) {
    console.error("[WARMUP] Failed to validate signature", error)
    return false
  }
}

function hasSha256(decoded: unknown): decoded is { sha256: string } {
  return (
    typeof decoded === "object" &&
    decoded !== null &&
    "sha256" in decoded &&
    typeof (decoded as { sha256: unknown }).sha256 === "string"
  )
}

function isRequestForCurrentContext({
  currentUrl,
  deployedUrl,
}: {
  currentUrl: string
  deployedUrl: string
}) {
  const isForProduction =
    currentUrl === "https://web-scandic-hotels.netlify.app" &&
    deployedUrl === "https://release--web-scandic-hotels.netlify.app"

  return isForProduction || currentUrl === deployedUrl
}

type DeploymentInfo = {
  id: string
  site_id: string
  build_id: string
  state: string
  name: string
  url: string
  ssl_url: string
  admin_url: string
  deploy_url: string
  deploy_ssl_url: string
  created_at: string
  updated_at: string
  user_id: string
  error_message: string | null
  required: any[]
  required_functions: any[]
  commit_ref: string
  review_id: string | null
  branch: string
  commit_url: string
  skipped: any
  locked: any
  title: string
  commit_message: string | null
  review_url: string | null
  published_at: string | null
  context: string
  deploy_time: number
  available_functions: unknown[]
  screenshot_url: string | null
  committer: string
  skipped_log: any
  manual_deploy: boolean
  plugin_state: string
  lighthouse_plugin_scores: any
  links: {
    permalink: string
    alias: string
    branch: string | null
  }
  framework: string
  entry_path: string | null
  views_count: number | null
  function_schedules: {
    cron: string
    name: string
  }[]
  public_repo: boolean
  pending_review_reason: string | null
  lighthouse: any
  edge_functions_present: boolean
  expires_at: string | null
  blobs_region: string
}

const ErrorCodes = {
  DEPLOYMENT_NOT_FOUND: "DEPLOYMENT NOT FOUND",
  FAILED_TO_PARSE_SIGNATURE: "FAILED TO PARSE SIGNATURE",
  INVALID_DEPLOYMENT_STATE: "INVALID DEPLOYMENT STATE",
  INVALID_SIGNATURE: "INVALID SIGNATURE",
  METHOD_NOT_ALLOWED: "METHOD NOT ALLOWED",
  MISSING_BODY: "MISSING BODY",
  MISSING_SIGNATURE_HEADER: "MISSING SIGNATURE HEADER",
  MISSING_SIGNATURE: "MISSING SIGNATURE",
  UNABLE_TO_PARSE_DEPLOYMENT_INFO: "UNABLE TO PARSE DEPLOYMENT INFO",
  REQUEST_NOT_FOR_CURRENT_CONTEXT: "REQUEST NOT FOR CURRENT CONTEXT",
  WARMUP_DISABLED: "WARMUP IS DISABLED",
} as const
type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes]
