import * as assert from 'assert';
import * as path from 'path';

import {
  CodeAction,
  commands,
  DiagnosticSeverity,
  languages,
  Range,
  Selection,
  TextDocument,
  TextDocumentContentProvider,
  Uri,
  window,
} from 'vscode';
import { commandId } from '../../command';
import { sleep } from '../../helpers/sleep';
const getPathDirname = (src: string, parentNumber = 1) => {
  let res = src;
  for (let i = 0; i < parentNumber; i++) res = path.dirname(res);

  return res;
};
const getUri = (fileName: string) => Uri.file(getPathDirname(__dirname, 3) + `/src/test/suite/data/${fileName}.ts`);

function executeCodeActionProvider(uri: Uri, range: Range) {
  return commands.executeCommand<CodeAction[]>('vscode.executeCodeActionProvider', uri, range);
}

interface TypeGenerationOptions {
  expectedType?: string;
}

const performTypeGeneration = async (element: string, document: TextDocument, { expectedType }: TypeGenerationOptions = {}) => {
  let fileText = document.getText();
  const f1Position = fileText.indexOf(element);
  const wordRange = document.getWordRangeAtPosition(document.positionAt(f1Position));
  if (!wordRange) {
    assert.fail(`${element} not found in file`);
  }
  const actions = await executeCodeActionProvider(document.uri, wordRange);
  const generateAction = actions?.find((action) => action.command!.command === commandId);
  if (!generateAction) assert.fail('Generate action not found');

  const command = generateAction.command!;
  await commands.executeCommand.apply(null, [command.command, ...command.arguments!]);
  if (expectedType) {
    fileText = document.getText();
    const res = fileText.includes(`${element}: ${expectedType}`);
    assert.strictEqual(res, true);
  } else {
    const diagnostics = languages
      .getDiagnostics(document.uri)
      .filter((x) => [DiagnosticSeverity.Error, DiagnosticSeverity.Warning].includes(x.severity));
    assert.strictEqual(diagnostics.length, 0, JSON.stringify(diagnostics));
  }
};

suiteSetup(function (done) {
  this.timeout(5000);
  sleep(2000).then(() => done());
});

suite('Extension Test Suite', async function () {
  this.timeout(5000);

  test('Simple function', async () => {
    try {
      const textEditor = await window.showTextDocument(getUri('f1'));
      await performTypeGeneration('f1()', textEditor.document, { expectedType: 'void' });
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Multiline arguments function', async () => {
    try {
      const name = 'f2';
      const textEditor = await window.showTextDocument(getUri(name));
      await performTypeGeneration(name, textEditor.document);
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Simple property', async () => {
    try {
      const textEditor = await window.showTextDocument(getUri('p1'));
      await performTypeGeneration('_p1_', textEditor.document, { expectedType: 'number' });
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Getter property', async () => {
    try {
      const textEditor = await window.showTextDocument(getUri('p2'));
      await performTypeGeneration('p2()', textEditor.document, { expectedType: 'number' });
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Function property', async () => {
    try {
      const name = 'p3';
      const textEditor = await window.showTextDocument(getUri(name));
      await performTypeGeneration(name, textEditor.document);
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Complex getter property', async () => {
    try {
      const name = 'p4';
      const textEditor = await window.showTextDocument(getUri(name));
      await performTypeGeneration(name, textEditor.document);
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Arrow function', async () => {
    try {
      const name = 'a1';
      const textEditor = await window.showTextDocument(getUri(name));
      await performTypeGeneration(name, textEditor.document);
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Long arrow function', async () => {
    try {
      const name = 'a2';
      const textEditor = await window.showTextDocument(getUri(name));
      await performTypeGeneration(name, textEditor.document);
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Arrow function with complex argument', async () => {
    try {
      const name = 'a3';
      const textEditor = await window.showTextDocument(getUri(name));
      await performTypeGeneration(name, textEditor.document);
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Arrow function argument', async () => {
    try {
      const name = 'a4';
      const textEditor = await window.showTextDocument(getUri(name));
      await performTypeGeneration(name, textEditor.document);
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Complex array', async () => {
    try {
      const name = 'arr1';
      const textEditor = await window.showTextDocument(getUri(name));
      await performTypeGeneration(name, textEditor.document);
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Simple cursor', async () => {
    try {
      const name = 'cursor1';
      const textEditor = await window.showTextDocument(getUri(name));
      const initialCursorPosition = new Selection(textEditor.selection.anchor, textEditor.selection.active);
      await performTypeGeneration(name, textEditor.document);
      assert.deepStrictEqual(initialCursorPosition, textEditor.selection);
    } catch (e) {
      assert.fail(e);
    }
  });

  test('Complex cursor', async () => {
    try {
      const name = 'cursor3';
      const textEditor = await window.showTextDocument(getUri(name));
      const initialCursorPosition = new Selection(textEditor.selection.anchor, textEditor.selection.active);
      await performTypeGeneration(name, textEditor.document);
      assert.deepStrictEqual(initialCursorPosition, textEditor.selection);
    } catch (e) {
      assert.fail(e);
    }
  });
});