import { Request, Router } from "express"
import { HttpCode, HttpError } from "../../common/http"
import { getHost, ensureProxyEnabled, authenticated, ensureAuthenticated, ensureOrigin, redirect, self } from "../http"
import { proxy } from "../proxy"
import { Router as WsRouter } from "../wsRouter"

export const router = Router()

const proxyDomainToRegex = (matchString: string): RegExp => {
  const escapedMatchString = matchString.replace(/[.*+?^$()|[\]\\]/g, "\\$&")

  // Replace {{port}} with a regex group to capture the port
  // Replace {{host}} with .+ to allow any host match (so rely on DNS record here)
  let regexString = escapedMatchString.replace("{{port}}", "(\\d+)")
  regexString = regexString.replace("{{host}}", ".+")

  regexString = regexString.replace(/[{}]/g, "\\$&") //replace any '{}' that might be left

  return new RegExp("^" + regexString + "$")
}

let proxyRegexes: RegExp[] = []
const proxyDomainsToRegex = (proxyDomains: string[]): RegExp[] => {
  if (proxyDomains.length !== proxyRegexes.length) {
    proxyRegexes = proxyDomains.map(proxyDomainToRegex)
  }
  return proxyRegexes
}

/**
 * Return the port if the request should be proxied.
 *
 * The proxy-domain should be of format anyprefix-{{port}}-anysuffix.{{host}}, where {{host}} is optional
 * e.g. code-8080.domain.tld would match for code-{{port}}.domain.tld and code-{{port}}.{{host}}.
 *
 */
const maybeProxy = (req: Request): string | undefined => {
  const reqDomain = getHost(req)
  if (reqDomain === undefined) {
    return undefined
  }

  const regexs = proxyDomainsToRegex(req.args["proxy-domain"])

  for (const regex of regexs) {
    const match = reqDomain.match(regex)

    if (match) {
      return match[1] // match[1] contains the port
    }
  }

  return undefined
}

router.all(/.*/, async (req, res, next) => {
  const port = maybeProxy(req)
  if (!port) {
    return next()
  }

  ensureProxyEnabled(req)

  if (req.method === "OPTIONS" && req.args["skip-auth-preflight"]) {
    // Allow preflight requests with `skip-auth-preflight` flag
    return next()
  }

  // Must be authenticated to use the proxy.
  const isAuthenticated = await authenticated(req)
  if (!isAuthenticated) {
    // Let the assets through since they're used on the login page.
    if (req.path.startsWith("/_static/") && req.method === "GET") {
      return next()
    }

    // Assume anything that explicitly accepts text/html is a user browsing a
    // page (as opposed to an xhr request). Don't use `req.accepts()` since
    // *every* request that I've seen (in Firefox and Chromium at least)
    // includes `*/*` making it always truthy. Even for css/javascript.
    if (req.headers.accept && req.headers.accept.includes("text/html")) {
      // Let the login through.
      if (/\/login\/?/.test(req.path)) {
        return next()
      }
      // Redirect all other pages to the login.
      const to = self(req)
      return redirect(req, res, "login", {
        to: to !== "/" ? to : undefined,
      })
    }

    // Everything else gets an unauthorized message.
    throw new HttpError("Unauthorized", HttpCode.Unauthorized)
  }

  proxy.web(req, res, {
    ignorePath: true,
    target: `http://0.0.0.0:${port}${req.originalUrl}`,
  })
})

export const wsRouter = WsRouter()

wsRouter.ws(/.*/, async (req, _, next) => {
  const port = maybeProxy(req)
  if (!port) {
    return next()
  }

  ensureProxyEnabled(req)
  ensureOrigin(req)
  await ensureAuthenticated(req)
  proxy.ws(req, req.ws, req.head, {
    ignorePath: true,
    target: `http://0.0.0.0:${port}${req.originalUrl}`,
  })
})
