import { logger } from "@coder/logger"
import express from "express"
import { promises as fs } from "fs"
import path from "path"
import { WebsocketRequest } from "../../../typings/pluginapi"
import { HttpCode } from "../../common/http"
import { rootPath } from "../constants"
import { replaceTemplates } from "../http"
import { escapeHtml, getMediaMime } from "../util"

interface ErrorWithStatusCode {
  statusCode: number
}

interface ErrorWithCode {
  code: string
}

/** Error is network related. */
export const errorHasStatusCode = (error: any): error is ErrorWithStatusCode => {
  return error && "statusCode" in error
}

/** Error originates from file system. */
export const errorHasCode = (error: any): error is ErrorWithCode => {
  return error && "code" in error
}

const notFoundCodes = [404, "ENOENT", "EISDIR"]

export const errorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
  let statusCode = 500

  if (errorHasStatusCode(err)) {
    statusCode = err.statusCode
  } else if (errorHasCode(err) && notFoundCodes.includes(err.code)) {
    statusCode = HttpCode.NotFound
  }

  res.status(statusCode)

  // 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")) {
    const resourcePath = path.resolve(rootPath, "src/browser/pages/error.html")
    res.set("Content-Type", getMediaMime(resourcePath))
    const content = await fs.readFile(resourcePath, "utf8")
    res.send(
      replaceTemplates(req, content)
        .replace(/{{ERROR_TITLE}}/g, statusCode.toString())
        .replace(/{{ERROR_HEADER}}/g, statusCode.toString())
        .replace(/{{ERROR_BODY}}/g, escapeHtml(err.message)),
    )
  } else {
    res.json({
      error: err.message,
      ...(err.details || {}),
    })
  }
}

export const wsErrorHandler: express.ErrorRequestHandler = async (err, req, res, next) => {
  logger.error(`${err.message} ${err.stack}`)
  ;(req as WebsocketRequest).ws.end()
}
