/**
 * This file is part of the NocoBase (R) project.
 * Copyright (c) 2020-2024 NocoBase Co., Ltd.
 * Authors: NocoBase Team.
 *
 * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
 * For more information, please refer to: https://www.nocobase.com/agreement.
 */

import { mockDatabase } from '@nocobase/database';
import { uid } from '@nocobase/utils';
import axios from 'axios';
import execa from 'execa';
import { resolve } from 'path';
import { getPortPromise } from 'portfinder';

const delay = (timeout) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(true);
    }, timeout);
  });
};

const checkServer = async (port?: number, duration = 1000, max = 60 * 10) => {
  return new Promise((resolve, reject) => {
    let count = 0;
    const baseURL = `http://127.0.0.1:${port}`;
    const url = `${baseURL}/api/__health_check`;
    console.log('url', url);
    const timer = setInterval(async () => {
      if (count++ > max) {
        clearInterval(timer);
        return reject(new Error('Server start timeout.'));
      }

      axios
        .get(url)
        .then((response) => {
          if (response.status === 200) {
            clearInterval(timer);
            resolve(true);
          }
        })
        .catch((error) => {
          const data = error?.response?.data?.error;
          console.error('Request error:', error?.response?.data?.error);
          if (data?.code === 'APP_NOT_INSTALLED_ERROR') {
            resolve(data?.code);
          }
        });
    }, duration);
  });
};

const run = (command, args, options) => {
  return execa(command, args, {
    ...process.env,
    ...options,
  });
};

const createDatabase = async () => {
  if (process.env.DB_DIALECT === 'sqlite') {
    return 'nocobase';
  }
  const db = mockDatabase();
  const name = `d_${uid()}`;
  await db.sequelize.query(`CREATE DATABASE ${name}`);
  await db.close();
  return name;
};

describe.skip('cli', () => {
  test('install', async () => {
    const database = await createDatabase();
    const port = await getPortPromise({
      port: 13000,
    });
    console.log(process.env.DB_DIALECT, port);
    const dbFile = `storage/tests/db/nocobase-${uid()}.sqlite`;
    const env = {
      ...process.env,
      APP_PORT: `${port}`,
      DB_STORAGE: dbFile,
      DB_DATABASE: database,
      SOCKET_PATH: `storage/tests/gateway-e2e-${uid()}.sock`,
      PM2_HOME: resolve(process.cwd(), `storage/tests/.pm2-${uid()}`),
    };
    const subprocess1 = await execa('yarn', ['nocobase', 'install'], {
      env,
    });
    expect(subprocess1.stdout.includes('app installed successfully')).toBeTruthy();
    const subprocess2 = await execa('yarn', ['nocobase', 'install'], {
      env,
    });
    expect(subprocess2.stdout.includes('app is installed')).toBeTruthy();
    const subprocess3 = await execa('yarn', ['nocobase', 'install', '-f'], {
      env,
    });
    expect(subprocess3.stdout.includes('app reinstalled successfully')).toBeTruthy();
  });

  test('start + install', async () => {
    console.log(process.env.DB_DIALECT);
    const database = await createDatabase();
    const port = await getPortPromise({
      port: 13000,
    });
    const dbFile = `storage/tests/db/nocobase-${uid()}.sqlite`;
    const env = {
      ...process.env,
      APP_PORT: `${port}`,
      DB_STORAGE: dbFile,
      DB_DATABASE: database,
      SOCKET_PATH: `storage/tests/gateway-e2e-${uid()}.sock`,
      PM2_HOME: resolve(process.cwd(), `storage/tests/.pm2-${uid()}`),
    };
    const subprocess1 = execa('yarn', ['nocobase', 'dev', '--server'], {
      env,
    });
    const code = await checkServer(port);
    console.log(code);
    expect(code).toBe('APP_NOT_INSTALLED_ERROR');
    execa('yarn', ['nocobase', 'install'], {
      env,
    });
    await delay(5000);
    const data2 = await checkServer(port);
    expect(data2).toBe(true);
    subprocess1.cancel();
  });

  test('install + start', async () => {
    console.log(process.env.DB_DIALECT);
    const database = await createDatabase();
    const port = await getPortPromise({
      port: 13000,
    });
    const dbFile = `storage/tests/db/nocobase-${uid()}.sqlite`;
    const env = {
      ...process.env,
      APP_PORT: `${port}`,
      DB_STORAGE: dbFile,
      DB_DATABASE: database,
      SOCKET_PATH: `storage/tests/gateway-e2e-${uid()}.sock`,
      PM2_HOME: resolve(process.cwd(), `storage/tests/.pm2-${uid()}`),
    };
    await execa('yarn', ['nocobase', 'install'], {
      env,
    });
    const subprocess1 = execa('yarn', ['nocobase', 'dev', '--server'], {
      env,
    });
    const code = await checkServer(port);
    expect(code).toBe(true);
    subprocess1.cancel();
  });

  test('quickstart', async () => {
    console.log(process.env.DB_DIALECT);
    const database = await createDatabase();
    const port = await getPortPromise({
      port: 13000,
    });
    const dbFile = `storage/tests/db/nocobase-${uid()}.sqlite`;
    const env = {
      ...process.env,
      APP_PORT: `${port}`,
      DB_STORAGE: dbFile,
      DB_DATABASE: database,
      SOCKET_PATH: `storage/tests/gateway-e2e-${uid()}.sock`,
      PM2_HOME: resolve(process.cwd(), `storage/tests/.pm2-${uid()}`),
    };
    console.log('DB_STORAGE:', dbFile);
    const subprocess1 = execa('yarn', ['nocobase', 'dev', '--server', '--quickstart'], {
      env,
    });
    const code = await checkServer(port);
    expect(code).toBe(true);
    subprocess1.cancel();
    const subprocess2 = execa('yarn', ['nocobase', 'dev', '--server', '--quickstart'], {
      env,
    });
    const code2 = await checkServer(port);
    expect(code2).toBe(true);
    subprocess2.cancel();
  });

  test('install + upgrade', async () => {
    console.log(process.env.DB_DIALECT);
    const database = await createDatabase();
    const port = await getPortPromise({
      port: 13000,
    });
    const dbFile = `storage/tests/db/nocobase-${uid()}.sqlite`;
    const env = {
      ...process.env,
      APP_PORT: `${port}`,
      DB_STORAGE: dbFile,
      DB_DATABASE: database,
      SOCKET_PATH: `storage/tests/gateway-e2e-${uid()}.sock`,
      PM2_HOME: resolve(process.cwd(), `storage/tests/.pm2-${uid()}`),
    };
    console.log('DB_STORAGE:', dbFile);
    await execa('yarn', ['nocobase', 'install'], {
      env,
    });
    const subprocess2 = await execa('yarn', ['nocobase', 'upgrade'], {
      env,
    });
    expect(subprocess2.stdout.includes('NocoBase has been upgraded')).toBe(true);
  });

  test('quickstart + upgrade', async () => {
    console.log(process.env.DB_DIALECT);
    const database = await createDatabase();
    const port = await getPortPromise({
      port: 13000,
    });
    const dbFile = `storage/tests/db/nocobase-${uid()}.sqlite`;
    const env = {
      ...process.env,
      APP_PORT: `${port}`,
      DB_STORAGE: dbFile,
      DB_DATABASE: database,
      SOCKET_PATH: `storage/tests/gateway-e2e-${uid()}.sock`,
      PM2_HOME: resolve(process.cwd(), `storage/tests/.pm2-${uid()}`),
    };
    console.log('DB_STORAGE:', dbFile);
    const subprocess1 = execa('yarn', ['nocobase', 'dev', '--server', '--quickstart'], {
      env,
    });
    const code = await checkServer(port);
    expect(code).toBe(true);
    await execa('yarn', ['nocobase', 'upgrade'], {
      env,
    });
    await delay(5000);
    const code2 = await checkServer(port);
    expect(code2).toBe(true);
    subprocess1.cancel();
  });
});
