// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

const EventEmitter = require('node:events');
const path = require('path');
const { runShellCmd } = require('./utils');

const launchBrowserAndRunTests = async (logPrefix, port) => {
  const testResults = [];
  const puppeteer = require('puppeteer-core');
  let browser;
  try {
    browser = await puppeteer.launch({
      executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
      browser: 'chrome',
      headless: true,
      args: ['--enable-features=SharedArrayBuffer', '--no-sandbox', '--disable-setuid-sandbox'],
    });

    for await (const flags of [
      [false, false],
      [false, true],
      [true, false],
      [true, true],
    ]) {
      const [multiThread, proxy] = flags;
      console.log(`[${logPrefix}] Running test with multi-thread: ${multiThread}, proxy: ${proxy}...`);

      const page = await browser.newPage();
      await page.goto(`http://localhost:${port}`);
      // wait for the page to load
      await page.waitForSelector('#ortstate', { visible: true });
      // if multi-thread is enabled, check the checkbox
      if (multiThread) {
        await page.locator('#cb-mt').click();
      }
      // if proxy is enabled, check the checkbox
      if (proxy) {
        await page.locator('#cb-px').click();
      }
      // click the load model button
      await page.locator('#btn-load').click();
      // wait for the model to load or fail
      await page.waitForFunction("['2','3'].includes(document.getElementById('ortstate').innerText)");
      // verify the model is loaded
      const modelLoadState = await page.$eval('#ortstate', (el) => el.innerText);
      if (modelLoadState !== '2') {
        const ortLog = await page.$eval('#ortlog', (el) => el.innerText);
        testResults.push({ multiThread, proxy, success: false, message: `Failed to load model: ${ortLog}` });
        continue;
      }

      // click the run test button
      await page.locator('#btn-run').click();
      // wait for the inference run to complete or fail
      await page.waitForFunction("['5','6'].includes(document.getElementById('ortstate').innerText)");
      // verify the inference run result
      const runState = await page.$eval('#ortstate', (el) => el.innerText);
      if (runState !== '5') {
        const ortLog = await page.$eval('#ortlog', (el) => el.innerText);
        testResults.push({ multiThread, proxy, success: false, message: `Failed to run model: ${ortLog}` });
        continue;
      }

      testResults.push({ multiThread, proxy, success: true });
    }

    return testResults;
  } finally {
    console.log(`[${logPrefix}] Closing the browser...`);
    // close the browser
    if (browser) {
      await browser.close();
    }
  }
};

async function runTest(testCaseName, logPrefix, ready, cmd, port) {
  const wd = path.join(__dirname, 'testcases', testCaseName);
  logPrefix = [testCaseName, ...logPrefix].join(':');

  console.log(`[${logPrefix}] Testing...`);
  const npmRunDevEvent = new EventEmitter();
  const npmRunDev = runShellCmd(cmd, {
    wd,
    event: npmRunDevEvent,
    ready,
    ignoreExitCode: true,
  });

  let testResults;
  npmRunDevEvent.on('serverReady', async () => {
    try {
      testResults = await launchBrowserAndRunTests(logPrefix, port);
    } finally {
      console.log(`[${logPrefix}] Killing the server...`);
      // kill the server as the last step
      npmRunDevEvent.emit('kill');
    }
  });

  await npmRunDev;

  console.log(`[${logPrefix}] test result:`, testResults);
  if (testResults.some((r) => !r.success)) {
    throw new Error(`[${logPrefix}] test failed.`);
  }
}

async function runDevTest(testCaseName, ready, port, logPrefix = '', cmd = 'npm run dev') {
  return runTest(testCaseName, logPrefix ? ['dev', logPrefix] : ['dev'], ready, cmd, port);
}

async function runProdTest(testCaseName, ready, port) {
  const wd = path.join(__dirname, 'testcases', testCaseName);

  console.log(`[${testCaseName}:prod] Building...`);
  await runShellCmd('npm run build', { wd });
  await runTest(testCaseName, ['prod'], ready, 'npm run start', port);
}

module.exports = {
  runDevTest,
  runProdTest,
};
