import { field, logger } from "@coder/logger"
import { test as base } from "@playwright/test"
import { CodeServer, CodeServerPage } from "./models/CodeServer"

/**
 * Wraps `test.describe` to create and manage an instance of code-server. If you
 * don't use this you will need to create your own code-server instance and pass
 * it to `test.use`.
 *
 * If `includeCredentials` is `true` page requests will be authenticated.
 */
export const describe = (
  name: string,
  includeCredentials: boolean,
  codeServerArgs: string[],
  codeServerEnv: NodeJS.ProcessEnv,
  fn: (codeServer: CodeServer) => void,
) => {
  test.describe(name, () => {
    // This will spawn on demand so nothing is necessary on before.
    const codeServer = new CodeServer(name, codeServerArgs, codeServerEnv)

    // Kill code-server after the suite has ended. This may happen even without
    // doing it explicitly but it seems prudent to be sure.
    test.afterAll(async () => {
      await codeServer.close()
    })

    const storageState = JSON.parse(process.env.STORAGE || "{}")

    // Sanity check to ensure the cookie is set.
    const cookies = storageState?.cookies
    if (includeCredentials && (!cookies || cookies.length !== 1 || !!cookies[0].key)) {
      logger.error("no cookies", field("storage", JSON.stringify(cookies)))
      throw new Error("no credentials to include")
    }

    test.use({
      // Makes `codeServer` and `authenticated` available to the extend call
      // below.
      codeServer,
      authenticated: includeCredentials,
      // This provides a cookie that authenticates with code-server.
      storageState: includeCredentials ? storageState : {},
      // NOTE@jsjoeio some tests use --cert which uses a self-signed certificate
      // without this option, those tests will fail.
      ignoreHTTPSErrors: true,
    })

    fn(codeServer)
  })
}

interface TestFixtures {
  authenticated: boolean
  codeServer: CodeServer
  codeServerPage: CodeServerPage
}

/**
 * Create a test that spawns code-server if necessary and ensures the page is
 * ready.
 */
export const test = base.extend<TestFixtures>({
  authenticated: false,
  codeServer: undefined, // No default; should be provided through `test.use`.
  codeServerPage: async ({ authenticated, codeServer, page }, use) => {
    // It's possible code-server might prevent navigation because of unsaved
    // changes (seems to happen based on timing even if no changes have been
    // made too). In these cases just accept.
    page.on("dialog", (d) => d.accept())

    const codeServerPage = new CodeServerPage(codeServer, page, authenticated)
    await codeServerPage.navigate()
    await use(codeServerPage)
  },
})

/** Shorthand for test.expect. */
export const expect = test.expect