import assert from "assert"
import { readFile, writeFile } from "fs/promises"
import http from "http"
import path from "path"
import { Logger as ProbotLogger, Probot, Server } from "probot"
import { getLog } from "probot/lib/helpers/get-log"
import stoppable from "stoppable"

import { Logger } from "./logger"
import { setup } from "./setup"
import { ensureDir } from "./shell"
import { envNumberVar, envVar } from "./utils"

const main = async () => {
  const startDate = new Date()

  const logFormat = (() => {
    const value = process.env.LOG_FORMAT
    switch (value) {
      case "json": {
        return value
      }
      case undefined: {
        return null
      }
      default: {
        throw new Error(`Invalid $LOG_FORMAT: ${value}`)
      }
    }
  })()
  const minLogLevel = (() => {
    const value = process.env.MIN_LOG_LEVEL
    switch (value) {
      case undefined: {
        return "info"
      }
      case "info":
      case "warn":
      case "error": {
        return value
      }
      default: {
        throw new Error(`Invalid $MIN_LOG_LEVEL: ${value}`)
      }
    }
  })()
  const logger = new Logger({
    name: "command-bot",
    minLogLevel,
    logFormat,
    impl: console,
  })

  const masterToken = envVar("MASTER_TOKEN")

  const shouldPostPullRequestComment = (() => {
    const value = process.env.POST_COMMENT
    switch (value) {
      case "false": {
        return false
      }
      case undefined:
      case "true": {
        return true
      }
      default: {
        throw new Error(`Invalid $POST_COMMENT: ${value}`)
      }
    }
  })()

  const dataPath = envVar("DATA_PATH")
  await ensureDir(dataPath)

  const appDbVersionPath = path.join(dataPath, "task-db-version")
  const shouldClearTaskDatabaseOnStart = process.env.TASK_DB_VERSION
    ? await (async (appDbVersion) => {
        const currentDbVersion = await (async () => {
          try {
            return (await readFile(appDbVersionPath)).toString().trim()
          } catch (error) {
            if (
              /*
              Test for the following error:
                [Error: ENOENT: no such file or directory, open '/foo'] {
                  errno: -2,
                  code: 'ENOENT',
                  syscall: 'unlink',
                  path: '/foo'
                }
              */
              !(error instanceof Error) ||
              (error as { code?: string })?.code !== "ENOENT"
            ) {
              throw error
            }
          }
        })()
        if (currentDbVersion !== appDbVersion) {
          await writeFile(appDbVersionPath, appDbVersion)
          return true
        }
      })(process.env.TASK_DB_VERSION.trim())
    : false

  if (process.env.PING_PORT) {
    // Signal that we have started listening until Probot kicks in
    const pingPort = parseInt(process.env.PING_PORT)
    const pingServer = stoppable(
      http.createServer((_, res) => {
        res.writeHead(200)
        res.end()
      }),
      0,
    )
    pingServer.listen(pingPort)
  }

  const appId = envNumberVar("APP_ID")
  const privateKey = Buffer.from(
    envVar("PRIVATE_KEY_BASE64"),
    "base64",
  ).toString()
  const clientId = envVar("CLIENT_ID")
  const clientSecret = envVar("CLIENT_SECRET")
  const webhookSecret = envVar("WEBHOOK_SECRET")

  let probotLogger: ProbotLogger | undefined = undefined
  switch (logFormat) {
    case "json": {
      probotLogger = getLog({
        level: "error",
        logFormat: "json",
        logLevelInString: true,
        logMessageKey: "msg",
      })
      break
    }
    case null: {
      break
    }
    default: {
      const exhaustivenessCheck: never = logFormat
      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
      throw new Error(`Not exhaustive: ${exhaustivenessCheck}`)
    }
  }
  const bot = Probot.defaults({
    appId,
    privateKey,
    secret: webhookSecret,
    logLevel: "info",
    ...(probotLogger === undefined
      ? {}
      : { log: probotLogger.child({ name: "probot" }) }),
  })
  const server = new Server({
    Probot: bot,
    ...(probotLogger === undefined
      ? {}
      : { log: probotLogger.child({ name: "server" }) }),
    webhookProxy: process.env.WEBHOOK_PROXY_URL,
  })

  const allowedOrganizations = envVar("ALLOWED_ORGANIZATIONS")
    .split(",")
    .filter((value) => {
      return value.length !== 0
    })
    .map((value) => {
      const parsedValue = parseInt(value)
      assert(parsedValue)
      return parsedValue
    })
  assert(allowedOrganizations.length)

  const matrix = (() => {
    if (process.env.MATRIX_HOMESERVER) {
      return {
        homeServer: process.env.MATRIX_HOMESERVER,
        accessToken: envVar("MATRIX_ACCESS_TOKEN"),
      }
    } else {
      return undefined
    }
  })()

  const gitlabAccessToken = envVar("GITLAB_ACCESS_TOKEN")
  const gitlabAccessTokenUsername = envVar("GITLAB_ACCESS_TOKEN_USERNAME")
  const gitlabDomain = envVar("GITLAB_DOMAIN")
  const gitlabPushNamespace = envVar("GITLAB_PUSH_NAMESPACE")
  const gitlabJobImage = envVar("GITLAB_JOB_IMAGE")

  const pipelineScripts = (() => {
    const pipelineScriptsRepository = process.env.PIPELINE_SCRIPTS_REPOSITORY
    if (pipelineScriptsRepository) {
      return {
        repository: pipelineScriptsRepository,
        ref: process.env.PIPELINE_SCRIPTS_REF,
      }
    }
  })()

  await server.load((probot) => {
    void setup(probot, server, {
      appId,
      clientId,
      clientSecret,
      privateKey,
      logger,
      startDate,
      shouldPostPullRequestComment,
      allowedOrganizations,
      dataPath,
      matrix,
      masterToken,
      shouldClearTaskDatabaseOnStart,
      isDeployment: !!process.env.IS_DEPLOYMENT,
      pipelineScripts,
      gitlab: {
        accessToken: gitlabAccessToken,
        accessTokenUsername: gitlabAccessTokenUsername,
        domain: gitlabDomain,
        pushNamespace: gitlabPushNamespace,
        jobImage: gitlabJobImage,
      },
    })
  })

  void server.start()
  logger.info("Probot has started!")
}

void main()