import { beforeEach, describe, it, expect } from 'vitest';
import { createExecutionStep } from '../../test/factories/execution-step.js';
import { createDropdownArgument, createDynamicArgument, createStringArgument } from '../../test/factories/app.js';
import computeParameters from './compute-parameters.js';

const computeVariable = (stepId, path) => `{{step.${stepId}.${path}}}`;

describe('Compute parameters helper', () => {
  let executionStepOne,
    executionStepTwo,
    executionStepThree,
    executionSteps;

  beforeEach(async () => {
    executionStepOne = await createExecutionStep({
      dataOut: {
        step1Key1: 'plain text value for step1Key1',
        // eslint-disable-next-line no-loss-of-precision
        step1Key2: 1267963836502380617,
        step1Key3: '1267963836502380617',
        step1Key4: {
          step1Key4ChildKey1: 'plain text value for step1Key3ChildKey1',
          // eslint-disable-next-line no-loss-of-precision
          step1Key4ChildKey2: 1267963836502380617,
          step1Key4ChildKey3: '3650238061712679638',
          step1Key4ChildKey4: ["value1", "value2"],
        }
      }
    });

    executionStepTwo = await createExecutionStep({
      dataOut: {
        step2Key1: 'plain text value for step2Key1',
        // eslint-disable-next-line no-loss-of-precision
        step2Key2: 6502380617126796383,
        step2Key3: '6502380617126796383',
        step2Key4: {
          step2Key4ChildKey1: 'plain text value for step2Key3ChildKey1',
          // eslint-disable-next-line no-loss-of-precision
          step2Key4ChildKey2: 6502380617126796383,
          step2Key4ChildKey3: '6502380617312679638',
          step2Key4ChildKey4: ["value1", "value2"],
        }
      }
    });

    executionStepThree = await createExecutionStep({
      dataOut: {
        step3Key1: 'plain text value for step3Key1',
        // eslint-disable-next-line no-loss-of-precision
        step3Key2: 123123,
        step3Key3: '123123',
        step3Key4: {
          step3Key4ChildKey1: 'plain text value for step3Key3ChildKey1',
          // eslint-disable-next-line no-loss-of-precision
          step3Key4ChildKey2: 123123,
          step3Key4ChildKey3: '123123',
          step3Key4ChildKey4: ["value1", "value2"],
        }
      }
    });

    executionSteps = [executionStepOne, executionStepTwo, executionStepThree];
  });

  it('should resolve with parameters having no corresponding steps', () => {
    const parameters = {
      key1: `${computeVariable('non-existent-step-id', 'non-existent-key')}`,
      key2: `"${computeVariable('non-existent-step-id', 'non-existent-key')}" is the value for non-existent-key`,
    };

    const computedParameters = computeParameters(parameters, [], executionSteps);
    const expectedParameters = {
      key1: '',
      key2: '"" is the value for non-existent-key',
    }

    expect(computedParameters).toStrictEqual(expectedParameters);
  });

  describe('with no parameters', () => {
    it('should resolve empty object', () => {
      const parameters = {};

      const computedParameters = computeParameters(parameters, [], executionSteps);

      expect(computedParameters).toStrictEqual(parameters);
    });
  });

  describe('with string parameters', () => {
    let stepArguments;
    beforeEach(() => {
      stepArguments = [
        createStringArgument({
          key: 'key1',
        }),
      ];
    });

    it('should resolve as-is without variables', () => {
      const parameters = {
        key1: 'plain text',
        key2: 'plain text',
      };

      const computedParameters = computeParameters(parameters, stepArguments, executionSteps);

      expect(computedParameters).toStrictEqual(parameters);
    });

    it('should preserve leading and trailing spaces', () => {
      const parameters = {
        key1: '  plain text  ',
        key2: 'plain text  ',
        key3: '  plain text',
      };

      const computedParameters = computeParameters(parameters, stepArguments, executionSteps);

      expect(computedParameters).toStrictEqual(parameters);
    });

    it('should compute variables correctly', () => {
      const parameters = {
        key1: `static text  ${computeVariable(executionStepOne.stepId, 'step1Key1')}`,
      };

      const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
      const expectedParameters = {
        key1: `static text  plain text value for step1Key1`,
      };

      expect(computedParameters).toStrictEqual(expectedParameters);
    });

    describe('with variables containing JSON', () => {
      describe('without explicit valueType defined', () => {
        let stepArguments;
        beforeEach(() => {
          stepArguments = [
            createStringArgument({
              key: 'key1',
            }),
          ];
        });

        it('should resolve text + JSON value as-is', () => {
          const parameters = {
            key1: 'prepended text {"key": "value"} ',
          };

          const computedParameters = computeParameters(parameters, executionSteps);

          expect(computedParameters).toStrictEqual(parameters);
        });

        it('should resolve stringified JSON parsed', () => {
          const parameters = {
            key1: '{"key1": "plain text", "key2": "119007199254740999"}',
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            key1: '{"key1": "plain text", "key2": "119007199254740999"}',
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });

        it('should handle arrays at root level', () => {
          const parameters = {
            key1: '["value1", "value2"]',
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            key1: '["value1", "value2"]',
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });

        it('should handle arrays in nested level', () => {
          const parameters = {
            key1: '{"items": ["value1", "value2"]}',
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            key1: '{"items": ["value1", "value2"]}',
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });

        it('should compute mix variables correctly', () => {
          const parameters = {
            key1: `another static text ${computeVariable(executionStepThree.stepId, 'step3Key4.step3Key4ChildKey4')}`,
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            key1: `another static text ["value1","value2"]`,
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });

        it('should compute variables correctly', () => {
          const parameters = {
            key1: `${computeVariable(executionStepThree.stepId, 'step3Key4.step3Key4ChildKey4')}`,
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            key1: '["value1","value2"]',
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });

        it('should not parse non-primitives in nested arrays', () => {
          const stepArguments = [
            createDynamicArgument({
              key: 'inputs',
            })
          ];

          const parameters = {
            inputs: [
              {
                key: 'person',
                value: '{ "name": "John Doe", "age": 32 }',
              }
            ],
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            inputs: [
              {
                key: 'person',
                value: '{ "name": "John Doe", "age": 32 }',
              }
            ],
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });
      });

      describe(`with valueType as 'parse'`, () => {
        let stepArguments;
        beforeEach(() => {
          stepArguments = [
            createStringArgument({
              key: 'key1',
              valueType: 'parse',
            }),
          ];
        });

        it('should resolve text + JSON value as-is', () => {
          const parameters = {
            key1: 'prepended text {"key": "value"} ',
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);

          expect(computedParameters).toStrictEqual(parameters);
        });

        it('should resolve stringified JSON parsed', () => {
          const parameters = {
            key1: '{"key1": "plain text", "key2": "119007199254740999"}',
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            key1: {
              key1: 'plain text',
              key2: '119007199254740999',
            },
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });

        it('should handle arrays at root level', () => {
          const parameters = {
            key1: '["value1", "value2"]',
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            key1: ['value1', 'value2'],
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });

        it('should handle arrays in nested level', () => {
          const parameters = {
            key1: '{"items": ["value1", "value2"]}',
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            key1: {
              items: ['value1', 'value2'],
            }
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });

        it('should compute and parse mix variables correctly', () => {
          const parameters = {
            key1: `another static text ${computeVariable(executionStepThree.stepId, 'step3Key4.step3Key4ChildKey4')}`,
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            key1: `another static text ["value1","value2"]`,
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });

        it('should compute and parse variables correctly', () => {
          const parameters = {
            key1: `${computeVariable(executionStepThree.stepId, 'step3Key4.step3Key4ChildKey4')}`,
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            key1: ["value1", "value2"],
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });

        it('should compute and parse variables in nested arrays correctly', () => {
          const stepArguments = [
            createDynamicArgument({
              key: 'inputs',
              fields: [
                {
                  label: 'Key',
                  key: 'key',
                  required: true,
                  variables: true,
                },
                {
                  label: 'Value',
                  key: 'value',
                  required: true,
                  variables: true,
                  valueType: 'parse',
                }
              ],
            })
          ];

          const parameters = {
            inputs: [
              {
                key: 'person',
                value: '{ "name": "John Doe", "age": 32 }',
              }
            ],
          };

          const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
          const expectedParameters = {
            inputs: [
              {
                key: 'person',
                value: {
                  name: 'John Doe',
                  age: 32,
                }
              }
            ],
          };

          expect(computedParameters).toStrictEqual(expectedParameters);
        });
      });
    });
  });

  describe('with number parameters', () => {
    let stepArguments;
    beforeEach(() => {
      stepArguments = [
        createStringArgument({
          key: 'key1',
        }),
        createStringArgument({
          key: 'key2',
        }),
      ];
    });

    it('should resolve number larger than MAX_SAFE_INTEGER correctly', () => {
      const parameters = {
        // eslint-disable-next-line no-loss-of-precision
        key1: 119007199254740999,
      };

      const computedParameters = computeParameters(parameters, stepArguments, executionSteps);

      expect(computedParameters).toStrictEqual(parameters);
      expect(computedParameters.key1 > Number.MAX_SAFE_INTEGER).toBe(true);
    });

    it('should resolve stringified number as-is', () => {
      const parameters = {
        key1: '119007199254740999',
      };

      const computedParameters = computeParameters(parameters, stepArguments, executionSteps);

      expect(computedParameters).toStrictEqual(parameters);
    });

    it('should compute variables with int values correctly', () => {
      const parameters = {
        key1: `another static text ${computeVariable(executionStepThree.stepId, 'step3Key2')}`,
        key2: `${computeVariable(executionStepThree.stepId, 'step3Key3')}`
      };

      const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
      const expectedParameters = {
        key1: `another static text 123123`,
        key2: `123123`
      };

      expect(computedParameters).toStrictEqual(expectedParameters);
    });

    it.todo('should compute variables with bigint values correctly', () => {
      const parameters = {
        key1: `another static text ${computeVariable(executionStepTwo.stepId, 'step2Key2')}`,
        key2: `${computeVariable(executionStepTwo.stepId, 'step2Key3')}`
      };

      const computedParameters = computeParameters(parameters, stepArguments, executionSteps);
      const expectedParameters = {
        // The expected `key2` is computed wrongly.
        key1: `another static text 6502380617126796383`,
        key2: `6502380617126796383`
      };

      expect(computedParameters).toStrictEqual(expectedParameters);
    });
  });

  describe('with boolean parameters', () => {
    let stepArguments;
    beforeEach(() => {
      stepArguments = [
        createDropdownArgument({
          key: 'key1',
        }),
        createDropdownArgument({
          key: 'key2',
        }),
      ];
    });

    it('should resolve boolean as-is', () => {
      const parameters = {
        key1: true,
        key2: false,
      };

      const computedParameters = computeParameters(parameters, stepArguments, executionSteps);

      expect(computedParameters).toStrictEqual(parameters);
      expect(computedParameters.key1).toBe(true);
      expect(computedParameters.key2).toBe(false);
    });
  });
});
