import { jest, expect, beforeAll, afterAll } from '@jest/globals';
import { Environment } from '../types/Environment';
import { PatchedDriver } from '../types/PatchedDriver';
import { CubejsServerCoreExposed } from '../types/CubejsServerCoreExposed';
import {
  getFixtures,
  getCreateQueries,
  getRefreshQueries,
  getCore,
  getDriver,
  patchDriver,
  hookPreaggs,
  runEnvironment,
} from '../helpers';

export function testSequence(type: string): void {
  describe(`Sequence with the @cubejs-backend/${type}-driver`, () => {
    jest.setTimeout(60 * 5 * 1000);

    const fixtures = getFixtures(type);
    // Pinot is seeded by runEnvironment (controller ingestion) with fixed
    // `<table>_pinot` names; the Internal pre-agg cases are skipped via
    // fixtures.skip since Pinot cannot materialize source-side rollups.
    const suffix = type === 'pinot' ? 'pinot' : 'core';
    let core: CubejsServerCoreExposed;
    let source: PatchedDriver;
    let storage: PatchedDriver;
    let query: string[];
    let env: Environment;

    function execute(name: string, test: () => Promise<void>) {
      if (fixtures.skip && fixtures.skip.indexOf(name) >= 0) {
        it.skip(name, test);
      } else {
        it(name, test);
      }
    }

    beforeAll(async () => {
      env = await runEnvironment(type, suffix);
      process.env.CUBEJS_REFRESH_WORKER = 'true';
      process.env.CUBEJS_CUBESTORE_HOST = '127.0.0.1';
      process.env.CUBEJS_CUBESTORE_PORT = process.env.CUBEJS_CUBESTORE_PORT ? process.env.CUBEJS_CUBESTORE_PORT : `${env.store.port}`;
      process.env.CUBEJS_CUBESTORE_USER = 'root';
      process.env.CUBEJS_CUBESTORE_PASS = 'root';
      process.env.CUBEJS_CACHE_AND_QUEUE_DRIVER = 'memory'; // memory, cubestore
      if (env.data) {
        process.env.CUBEJS_DB_HOST = type === 'pinot' ? 'http://127.0.0.1' : '127.0.0.1';
        process.env.CUBEJS_DB_PORT = `${env.data.port}`;
      }
      const drivers = await getDriver(type);
      source = drivers.source;
      storage = drivers.storage;
      // Pinot has no SQL DDL — it is seeded by runEnvironment (controller ingestion).
      if (type !== 'pinot') {
        query = getCreateQueries(type, suffix);
        await Promise.all(query.map(async (q) => {
          await source.query(q);
        }));
        // CrateDB is eventually consistent: make the freshly loaded rows visible
        // before pre-aggregations are built from them.
        if (type === 'crate') {
          await Promise.all(getRefreshQueries(type, suffix).map((q) => source.query(q)));
        }
      }
      patchDriver(source);
      patchDriver(storage);
      core = getCore(type, 'cubestore', source, storage);
    });

    afterAll(async () => {
      // Pinot has no dropTable; the cluster is torn down with the environment.
      if (type !== 'pinot') {
        const tables = Object
          .keys(fixtures.tables)
          .map((key: string) => `${fixtures.tables[key]}_${suffix}`);
        await Promise.all(
          tables.map(async (t) => {
            await source.dropTable(t);
          })
        );
      }
      await source.release();
      await storage.release();
      await core.shutdown();
      await env.stop();
    });

    beforeEach(() => {
      source.calls = [];
      storage.calls = [];
    });

    execute('for the Customers.RollingExternal', async () => {
      await hookPreaggs(core, 'Customers.RAExternal');
      expect([source.calls, storage.calls]).toMatchSnapshot();
    });

    execute('for the Customers.RollingInternal', async () => {
      await hookPreaggs(core, 'Customers.RAInternal');
      expect([source.calls, storage.calls]).toMatchSnapshot();
    });

    execute('for the ECommerce.SimpleAnalysisExternal', async () => {
      await hookPreaggs(core, 'ECommerce.SAExternal');
      expect([source.calls, storage.calls]).toMatchSnapshot();
    });

    execute('for the ECommerce.SimpleAnalysisInternal', async () => {
      await hookPreaggs(core, 'ECommerce.SAInternal');
      expect([source.calls, storage.calls]).toMatchSnapshot();
    });

    execute('for the ECommerce.TimeAnalysisExternal', async () => {
      await hookPreaggs(core, 'ECommerce.TAExternal');
      expect([source.calls, storage.calls]).toMatchSnapshot();
    });

    execute('for the ECommerce.TimeAnalysisInternal', async () => {
      await hookPreaggs(core, 'ECommerce.TAInternal');
      expect([source.calls, storage.calls]).toMatchSnapshot();
    });
  });
}
