import { tokenize } from 'esprima';
import { describe, it, expect } from 'vitest';

import { EvaluationPair, extractJsEvaluationPairsWithTokenizer, LanguagePluginID } from '../types/index.js';
import { refactorNameInCode, refactorNameInString, refactorNameInValue, refactorNameInStep } from './refactor.js';

describe('Refactoring', () => {
  describe('Page Scope', () => {
    describe('refactorNameInCode', () => {
      it('should refactor the old name to the new name in the code', async () => {
        const value = 'return oldName;';
        const oldName = 'oldName';
        const newName = 'newName';
        const dataTree = {
          oldName: {}
        };

        const result = await refactorNameInCode({
          value,
          oldName,
          newName,
          dataTree,
          extractPairs: extractJsEvaluationPairs
        });

        expect(result).toEqual('return newName;');
      });
    });

    describe('refactorNameInString', () => {
      it('should refactor the old name to the new name in the string', async () => {
        const value = 'Hello, {{oldName}}!';
        const oldName = 'oldName';
        const newName = 'newName';
        const dataTree = {
          oldName: {}
        };

        const result = await refactorNameInString({
          value,
          oldName,
          newName,
          dataTree,
          extractPairs: extractJsEvaluationPairs
        });

        expect(result).toEqual('Hello, {{newName}}!');
      });
    });

    describe('refactorNameInValue', () => {
      it('in object', async () => {
        const oldValue = {
          name: '{{oldName}}',
          age: 30
        };
        const oldName = 'oldName';
        const newName = 'newName';
        const dataTree = {
          oldName: {}
        };

        const result = await refactorNameInValue({
          oldValue,
          oldName,
          newName,
          dataTree,
          extractPairs: extractJsEvaluationPairs
        });

        expect(result).toEqual({
          name: '{{newName}}',
          age: 30
        });
      });
    });

    it('in array', async () => {
      const oldValue = ['{{oldName}}', 30];
      const oldName = 'oldName';
      const newName = 'newName';
      const dataTree = {
        oldName: {}
      };

      const result = await refactorNameInValue({
        oldValue,
        oldName,
        newName,
        dataTree,
        extractPairs: extractJsEvaluationPairs
      });

      expect(result).toEqual(['{{newName}}', 30]);
    });
    it('in string', async () => {
      const oldValue = '{{oldName}}';
      const oldName = 'oldName';
      const newName = 'newName';
      const dataTree = {
        oldName: {}
      };

      const result = await refactorNameInValue({
        oldValue,
        oldName,
        newName,
        dataTree,
        extractPairs: extractJsEvaluationPairs
      });

      expect(result).toEqual('{{newName}}');
    });
  });

  describe('App Scope', () => {
    describe('refactorNameInCode', () => {
      it('should refactor the old name to the new name in the code', async () => {
        const value = 'return App.oldName;';
        const oldName = 'oldName';
        const newName = 'newName';
        const dataTree = {
          oldName: {}
        };

        const result = await refactorNameInCode({
          value,
          oldName,
          newName,
          dataTree,
          namespace: 'App',
          extractPairs: extractJsEvaluationPairs
        });

        expect(result).toEqual('return App.newName;');
      });
    });

    describe('refactorNameInString', () => {
      it('should refactor the old name to the new name in the string', async () => {
        const value = 'Hello, {{App.oldName}}!';
        const oldName = 'oldName';
        const newName = 'newName';
        const dataTree = {
          oldName: {}
        };

        const result = await refactorNameInString({
          value,
          oldName,
          newName,
          dataTree,
          namespace: 'App',
          extractPairs: extractJsEvaluationPairs
        });

        expect(result).toEqual('Hello, {{App.newName}}!');
      });
    });

    describe('refactorNameInValue', () => {
      it('in object', async () => {
        const oldValue = {
          name: '{{App.oldName}}',
          age: 30
        };
        const oldName = 'oldName';
        const newName = 'newName';
        const dataTree = {
          oldName: {}
        };

        const result = await refactorNameInValue({
          oldValue,
          oldName,
          newName,
          dataTree,
          namespace: 'App',
          extractPairs: extractJsEvaluationPairs
        });

        expect(result).toEqual({
          name: '{{App.newName}}',
          age: 30
        });
      });
    });

    it('in array', async () => {
      const oldValue = ['{{App.oldName}}', 30];
      const oldName = 'oldName';
      const newName = 'newName';
      const dataTree = {
        oldName: {}
      };

      const result = await refactorNameInValue({
        oldValue,
        oldName,
        newName,
        dataTree,
        namespace: 'App',
        extractPairs: extractJsEvaluationPairs
      });

      expect(result).toEqual(['{{App.newName}}', 30]);
    });
    it('in string', async () => {
      const oldValue = '{{App.oldName}}';
      const oldName = 'oldName';
      const newName = 'newName';
      const dataTree = {
        oldName: {}
      };

      const result = await refactorNameInValue({
        oldValue,
        oldName,
        newName,
        dataTree,
        namespace: 'App',
        extractPairs: extractJsEvaluationPairs
      });

      expect(result).toEqual('{{App.newName}}');
    });
  });

  describe('refactorNameInStep', () => {
    it('should refactor names in JS steps', async () => {
      const oldName = 'oldName';
      const newName = 'newName';
      const dataTree = {
        oldName: {}
      };
      const configuration = {
        body: 'return {{oldName}};'
      };
      await refactorNameInStep({
        configuration,
        oldName,
        newName,
        dataTree,
        namespace: undefined,
        pluginId: LanguagePluginID.JavaScript,
        extractor: extractJsEvaluationPairs
      });
      expect(configuration.body).toEqual('return {{newName}};');
    });

    it('should refactor names in steps with nested configurations, i.e. Databricks, Kinesis, etc', async () => {
      const oldName = 'oldName';
      const newName = 'newName';
      const dataTree = {
        oldName: {}
      };
      const configuration = {
        get: {
          limit: 10,
          pollingCooldownMs: 1000,
          shardIteratorType: 'SHARD_ITERATOR_TYPE_LATEST',
          streamIdentifierType: 'STREAM_IDENTIFIER_STREAM_NAME'
        },
        put: {
          data: '"test": {{oldName.text}}',
          streamName: '{{oldName.text}}',
          streamIdentifierType: 'STREAM_IDENTIFIER_STREAM_NAME'
        },
        operationType: 'OPERATION_TYPE_PUT'
      };
      await refactorNameInStep({
        configuration,
        oldName,
        newName,
        dataTree,
        namespace: undefined,
        pluginId: '12345',
        extractor: extractJsEvaluationPairs
      });
      expect(configuration).toEqual({
        get: {
          limit: 10,
          pollingCooldownMs: 1000,
          shardIteratorType: 'SHARD_ITERATOR_TYPE_LATEST',
          streamIdentifierType: 'STREAM_IDENTIFIER_STREAM_NAME'
        },
        put: {
          data: '"test": {{newName.text}}',
          streamName: '{{newName.text}}',
          streamIdentifierType: 'STREAM_IDENTIFIER_STREAM_NAME'
        },
        operationType: 'OPERATION_TYPE_PUT'
      });
    });
  });
});

async function extractJsEvaluationPairs(
  jsSnippet: string,
  entitiesToExtract: Set<string>,
  dataTree: Record<Exclude<string, 'API' | 'WIDGETS'>, unknown>,
  namespacedEntitiesToExtract?: Record<string, Set<string>>
): Promise<EvaluationPair[]> {
  return extractJsEvaluationPairsWithTokenizer(jsSnippet, entitiesToExtract, dataTree, tokenize, namespacedEntitiesToExtract);
}
