import {UserAPI} from 'app/common/UserAPI';
import axios from 'axios';
import {assert} from 'chai';
import * as fse from 'fs-extra';
import {TestServer} from 'test/gen-server/apiUtils';
import {configForUser} from 'test/gen-server/testUtils';
import {createTmpDir} from 'test/server/docTools';
import {openClient} from 'test/server/gristClient';
import * as testUtils from 'test/server/testUtils';

const chimpy = configForUser('Chimpy');
const kiwi = configForUser('Kiwi');

describe('DocApi2', function() {
  this.timeout(40000);
  let server: TestServer;
  let homeUrl: string;
  let owner: UserAPI;
  let wsId: number;
  testUtils.setTmpLogLevel('error');
  let oldEnv: testUtils.EnvironmentSnapshot;

  before(async function() {
    oldEnv = new testUtils.EnvironmentSnapshot();
    const tmpDir = await createTmpDir();
    process.env.GRIST_DATA_DIR = tmpDir;
    process.env.STRIPE_ENDPOINT_SECRET = 'TEST_WITHOUT_ENDPOINT_SECRET';
    // Use the TEST_REDIS_URL as the global redis url, if supplied.
    if (process.env.TEST_REDIS_URL && !process.env.REDIS_URL) {
      process.env.REDIS_URL = process.env.TEST_REDIS_URL;
    }

    server = new TestServer(this);
    homeUrl = await server.start(['home', 'docs']);
    const api = await server.createHomeApi('chimpy', 'docs', true);
    await api.newOrg({name: 'testy', domain: 'testy'});
    owner = await server.createHomeApi('chimpy', 'testy', true);
    wsId = await owner.newWorkspace({name: 'ws'}, 'current');
  });

  after(async function() {
    const api = await server.createHomeApi('chimpy', 'docs');
    await api.deleteOrg('testy');
    await server.stop();
    oldEnv.restore();
  });

  describe('DELETE /docs/{did}', async () => {
    it('permanently deletes a document and all of its forks', async function() {
      // Create a new document and fork it twice.
      const docId = await owner.newDoc({name: 'doc'}, wsId);
      const session = await owner.getSessionActive();
      const client = await openClient(server.server, session.user.email, session.org?.domain || 'docs');
      await client.openDocOnConnect(docId);
      const forkDocResponse1 = await client.send('fork', 0);
      const forkDocResponse2 = await client.send('fork', 0);

      // Check that files were created for the trunk and forks.
      const docPath = server.server.getStorageManager().getPath(docId);
      const forkPath1 = server.server.getStorageManager().getPath(forkDocResponse1.data.docId);
      const forkPath2 = server.server.getStorageManager().getPath(forkDocResponse2.data.docId);
      assert.equal(await fse.pathExists(docPath), true);
      assert.equal(await fse.pathExists(forkPath1), true);
      assert.equal(await fse.pathExists(forkPath2), true);

      // Delete the trunk via API.
      const deleteDocResponse = await axios.delete(`${homeUrl}/api/docs/${docId}`, chimpy);
      assert.equal(deleteDocResponse.status, 200);

      // Check that files for the trunk and forks were deleted.
      assert.equal(await fse.pathExists(docPath), false);
      assert.equal(await fse.pathExists(forkPath1), false);
      assert.equal(await fse.pathExists(forkPath2), false);
    });
  });

  describe('/docs/{did}/timing', async () => {
    let docId: string;
    before(async function() {
      docId = await owner.newDoc({name: 'doc2'}, wsId);
    });

    after(async function() {
      await owner.deleteDoc(docId);
    });

    // There are two endpoints here /timing/start and /timing/stop.
    // Here we just test that it is operational, available only for owners
    // and that it returns sane results. Exact tests are done in python.

    // Smoke test.
    it('POST /docs/{did}/timing smoke tests', async function() {
      // We are disabled.
      let resp = await axios.get(`${homeUrl}/api/docs/${docId}/timing`, chimpy);
      assert.equal(resp.status, 200);
      assert.deepEqual(resp.data, {status: 'disabled'});

      // Start it.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/start`, {}, chimpy);
      assert.equal(resp.status, 200);

      // Stop it.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/stop`, {}, chimpy);
      assert.equal(resp.status, 200);
      assert.deepEqual(resp.data, []);
    });

    it('POST /docs/{did}/timing/start', async function() {
      // Start timing as non owner, should fail.
      let resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/start`, {}, kiwi);
      assert.equal(resp.status, 403);

      // Query status as non owner, should fail.
      resp = await axios.get(`${homeUrl}/api/docs/${docId}/timing`, kiwi);
      assert.equal(resp.status, 403);

      // Check as owner.
      resp = await axios.get(`${homeUrl}/api/docs/${docId}/timing`, chimpy);
      assert.equal(resp.status, 200);
      assert.deepEqual(resp.data, {status: 'disabled'});

      // Start timing as owner.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/start`, {}, chimpy);
      assert.equal(resp.status, 200);

      // Check we are started.
      resp = await axios.get(`${homeUrl}/api/docs/${docId}/timing`, chimpy);
      assert.equal(resp.status, 200);
      assert.deepEqual(resp.data, {status: 'active', timing: []});

      // Starting timing again works as expected, returns 400 as this is already.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/start`, {}, chimpy);
      assert.equal(resp.status, 400);

      // As non owner
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/stop`, {}, kiwi);
      assert.equal(resp.status, 403);
    });

    it('POST /docs/{did}/timing/stop', async function() {
      // Timings are turned on, so we can stop them.
      // First as non owner, we should fail.
      let resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/stop`, {}, kiwi);
      assert.equal(resp.status, 403);

      // Next as owner.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/stop`, {}, chimpy);
      assert.equal(resp.status, 200);

      // Now do it once again, we should got 400, as we are not timing.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/stop`, {}, chimpy);
      assert.equal(resp.status, 400);
    });

    it('GET /docs/{did}/timing', async function() {
      // Now we can check the results. Start timing and check that we got [] in response.
      let resp =  await axios.post(`${homeUrl}/api/docs/${docId}/timing/start`, {}, chimpy);
      assert.equal(resp.status, 200);

      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/stop`, {}, chimpy);
      assert.equal(resp.status, 200);
      assert.deepEqual(resp.data, []);

      // Now create a table with a formula column and make sure we see it in the results.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/apply`, [
        ['AddTable', 'Timings', [
          {id: 'A', formula: '$id' }
        ]],
      ], chimpy);
      assert.equal(resp.status, 200);

      // Now start it again,
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/start`, {}, chimpy);
      assert.equal(resp.status, 200);

      // Make sure we see that it is active and we have some intermediate results
      resp = await axios.get(`${homeUrl}/api/docs/${docId}/timing`, chimpy);
      assert.equal(resp.status, 200);
      assert.deepEqual(resp.data, {status: 'active', timing: []});

      // And trigger some formula calculations.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/apply`, [
        ['BulkAddRecord', 'Timings', [null, null], {}],
      ], chimpy);
      assert.equal(resp.status, 200, JSON.stringify(resp.data));

      // Make sure we can't stop it as non owner.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/stop`, {}, kiwi);
      assert.equal(resp.status, 403);

      // Now stop it as owner and make sure the result is sane.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/stop`, {}, chimpy);
      assert.equal(resp.status, 200, JSON.stringify(resp.data));
      const data = resp.data as Array<{
        tableId: string;
        colId: string;
        sum: number;
        count: number;
        average: number;
        max: number;
        markers?: Array<{
          name: string;
          sum: number;
          count: number;
          average: number;
          max: number;
        }>
      }>;

      assert.isAbove(data.length, 0);
      assert.equal(data[0].tableId, 'Timings');
      assert.isTrue(typeof data[0].sum === 'number');
      assert.isTrue(typeof data[0].count === 'number');
      assert.isTrue(typeof data[0].average === 'number');
      assert.isTrue(typeof data[0].max === 'number');
    });

    it('POST /docs/{did}/timing/start remembers state after reload', async function() {
      // Make sure we are off.
      let resp = await axios.get(`${homeUrl}/api/docs/${docId}/timing`, chimpy);
      assert.equal(resp.status, 200);
      assert.deepEqual(resp.data, {status: 'disabled'});

      // Now start it.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/timing/start`, {}, chimpy);
      assert.equal(resp.status, 200);

      // Now reload document.
      resp = await axios.post(`${homeUrl}/api/docs/${docId}/force-reload`, {}, chimpy);
      assert.equal(resp.status, 200);

      // And check that we are still on.
      resp = await axios.get(`${homeUrl}/api/docs/${docId}/timing`, chimpy);
      assert.equal(resp.status, 200, JSON.stringify(resp.data));
      assert.equal(resp.data.status, 'active');
      assert.isNotEmpty(resp.data.timing);
    });
  });
});