/**
 * Copyright IBM Corp. 2024, 2025
 */
import { AssertConstants } from '../../src/constants/assertConstants.js';
import { performAssertion } from '../../src/handlers/assertion.handler.js';

describe('Assertion Handler', () => {
  describe('performAssertion', () => {
    describe(`${AssertConstants.equals_action}`, () => {
      it('should not throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.equals_action, 200, 200),
        ).not.toThrow();
      });

      it('should throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.equals_action, 200, 201),
        ).toThrow();
      });
    });

    describe(`${AssertConstants.not_equals_action}`, () => {
      it('should not throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.not_equals_action, 200, 201),
        ).not.toThrow();
      });

      it('should throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.not_equals_action, 200, 200),
        ).toThrow();
      });
    });

    describe(`${AssertConstants.greaterThan_action}`, () => {
      it('should not throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.greaterThan_action, 200, 100),
        ).not.toThrow();
      });

      it('should throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.greaterThan_action, 200, 201),
        ).toThrow();
      });
    });

    describe(`${AssertConstants.lessThan_action}`, () => {
      it('should not throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.lessThan_action, 100, 200),
        ).not.toThrow();
      });

      it('should throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.lessThan_action, 400, 201),
        ).toThrow();
      });
    });

    describe(`${AssertConstants.include_action}`, () => {
      describe('array', () => {
        it('should not throw error', async () => {
          expect(() =>
            performAssertion(AssertConstants.include_action, [1, 2], 1),
          ).not.toThrow();
        });

        it('should throw error', async () => {
          expect(() =>
            performAssertion(AssertConstants.include_action, [1, 2], 4),
          ).toThrow();
        });
      });

      describe('string', () => {
        it('should not throw error', async () => {
          expect(() =>
            performAssertion(AssertConstants.include_action, 'foo', 'f'),
          ).not.toThrow();
        });

        it('should throw error', async () => {
          expect(() =>
            performAssertion(AssertConstants.include_action, 'foo', 'd'),
          ).toThrow();
        });
      });

      describe('object', () => {
        it('should not throw error', async () => {
          expect(() =>
            performAssertion(
              AssertConstants.include_action,
              { foo: 'bar', a: 'b' },
              { a: 'b' },
            ),
          ).not.toThrow();
        });

        it('should throw error', async () => {
          expect(() =>
            performAssertion(
              AssertConstants.include_action,
              { foo: 'bar', a: 'b' },
              { e: 'd' },
            ),
          ).toThrow();
        });
      });
    });

    describe(`${AssertConstants.validateSchema_action}`, () => {
      it('should not throw error for valid schema', async () => {
        expect(() =>
          performAssertion(
            AssertConstants.validateSchema_action,
            { foo: 'bar' },
            {
              type: 'object',
              properties: {
                foo: { type: 'string' },
              },
              required: ['foo'],
              additionalProperties: false,
            },
          ),
        ).not.toThrow();
      });

      it('should not throw error for valid schema string', async () => {
        expect(() =>
          performAssertion(
            AssertConstants.validateSchema_action,
            { foo: 'bar' },
            '{"type":"object","properties":{"foo":{"type":"string"}},"required":["foo"],"additionalProperties":false}',
          ),
        ).not.toThrow();
      });

      it('should throw error for invalid schema', async () => {
        expect(() =>
          performAssertion(
            AssertConstants.validateSchema_action,
            { food: 'bar' },
            {
              type: 'object',
              properties: {
                foo: { type: 'string' },
              },
              required: ['foo'],
              additionalProperties: false,
            },
          ),
        ).toThrow("must have required property 'foo'");
      });
    });

    describe(`${AssertConstants.inValidateSchema_action}`, () => {
      it('should throw error for valid schema', async () => {
        expect(() =>
          performAssertion(
            AssertConstants.inValidateSchema_action,
            { foo: 'bar' },
            {
              type: 'object',
              properties: {
                foo: { type: 'string' },
              },
              required: ['foo'],
              additionalProperties: false,
            },
          ),
        ).toThrow();
      });

      it('should throw error for valid schema string', async () => {
        expect(() =>
          performAssertion(
            AssertConstants.inValidateSchema_action,
            { foo: 'bar' },
            '{"type":"object","properties":{"foo":{"type":"string"}},"required":["foo"],"additionalProperties":false}',
          ),
        ).toThrow();
      });

      it('should throw error for invalid schema', async () => {
        expect(() =>
          performAssertion(
            AssertConstants.inValidateSchema_action,
            { food: 'bar' },
            {
              type: 'object',
              properties: {
                foo: { type: 'string' },
              },
              required: ['foo'],
              additionalProperties: false,
            },
          ),
        ).not.toThrow();
      });
    });

    describe(`${AssertConstants.have_property_action}`, () => {
      it('should not throw error', () => {
        expect(() =>
          performAssertion(
            AssertConstants.have_property_action,
            { food: 'bar' },
            'food',
          ),
        ).not.toThrow();
      });

      it('should not throw error', () => {
        expect(() =>
          performAssertion(
            AssertConstants.have_property_action,
            { food: 'bar' },
            '',
          ),
        ).toThrow();
      });
    });

    describe(`${AssertConstants.not_have_property_action}`, () => {
      it('should not throw error', () => {
        expect(() =>
          performAssertion(
            AssertConstants.not_have_property_action,
            { food: 'bar' },
            'bar',
          ),
        ).not.toThrow();
      });

      it('should not throw error', () => {
        expect(() =>
          performAssertion(
            AssertConstants.not_have_property_action,
            { food: 'bar' },
            'food',
          ),
        ).toThrow();
      });
    });

    describe(`${AssertConstants.lengthOf_action}`, () => {
      it('should not throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.lengthOf_action, [1, 2], 2),
        ).not.toThrow();
      });

      it('should throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.lengthOf_action, [1, 2], 3),
        ).toThrow();
      });
    });

    describe(`${AssertConstants.matches_action}`, () => {
      it('should not throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.matches_action, 'foobar', '^foo'),
        ).not.toThrow();
      });

      it('should throw error', async () => {
        expect(() =>
          performAssertion(AssertConstants.matches_action, 'gator', '^foo'),
        ).toThrow();
      });
    });

    describe(`${AssertConstants.type_action}`, () => {
      describe('for array', () => {
        it('should not throw error', async () => {
          expect(() =>
            performAssertion(AssertConstants.type_action, [1, 2, 3], 'array'),
          ).not.toThrow();
        });

        it('should throw error', async () => {
          expect(() =>
            performAssertion(AssertConstants.type_action, 'gator', 'array'),
          ).toThrow();
        });
      });

      describe('for other', () => {
        it('should not throw error', async () => {
          expect(() =>
            performAssertion(AssertConstants.type_action, 'foobar', 'string'),
          ).not.toThrow();
        });

        it('should throw error', async () => {
          expect(() =>
            performAssertion(AssertConstants.type_action, 'gator', 'number'),
          ).toThrow();
        });
      });
    });

    describe('dynamic Chai chain', () => {
      it("should pass 'to.be.equal' assertion", () => {
        expect(() => performAssertion('to.be.equal', 42, 42)).not.toThrow();
      });

      it("should fail 'to.be.equal' assertion", () => {
        expect(() => performAssertion('to.be.equal', 1, 2)).toThrow(
          'expected 1 to equal 2',
        );
      });

      it("should pass 'to.have.lengthOf' assertion", () => {
        expect(() =>
          performAssertion('to.have.lengthOf', [1, 2, 3], 3),
        ).not.toThrow();
      });

      it('should throw on invalid intermediate chain', () => {
        expect(() => performAssertion('to.boo.equal', 5, 5)).toThrow(
          "Assertion failed for action 'to.boo.equal': Invalid assertion part 'boo' in chain 'to.boo.equal'",
        );
      });

      it('should throw on invalid final method', () => {
        expect(() => performAssertion('to.be.notAMethod', 5, 5)).toThrow(
          "Assertion failed for action 'to.be.notAMethod': Invalid Chai property: notAMethod",
        );
      });
    });

    describe('Special string input handling', () => {
      describe('Empty object representation', () => {
        it('should pass when comparing empty object with "{}"', () => {
          expect(() =>
            performAssertion(AssertConstants.equals_action, {}, '{}'),
          ).not.toThrow();
        });

        it('should fail when comparing non-empty object with "{}"', () => {
          expect(() =>
            performAssertion(
              AssertConstants.equals_action,
              { key: 'value' },
              '{}',
            ),
          ).toThrow();
        });

        it('should pass when comparing non-empty object with not equals "{}"', () => {
          expect(() =>
            performAssertion(
              AssertConstants.not_equals_action,
              { key: 'value' },
              '{}',
            ),
          ).not.toThrow();
        });

        it('should fail when comparing empty object with not equals "{}"', () => {
          expect(() =>
            performAssertion(AssertConstants.not_equals_action, {}, '{}'),
          ).toThrow();
        });
      });

      describe('Empty array representation', () => {
        it('should pass when comparing empty array with "[]"', () => {
          expect(() =>
            performAssertion(AssertConstants.equals_action, [], '[]'),
          ).not.toThrow();
        });

        it('should fail when comparing non-empty array with "[]"', () => {
          expect(() =>
            performAssertion(AssertConstants.equals_action, [1, 2, 3], '[]'),
          ).toThrow();
        });

        it('should pass when comparing non-empty array with not equals "[]"', () => {
          expect(() =>
            performAssertion(
              AssertConstants.not_equals_action,
              [1, 2, 3],
              '[]',
            ),
          ).not.toThrow();
        });

        it('should fail when comparing empty array with not equals "[]"', () => {
          expect(() =>
            performAssertion(AssertConstants.not_equals_action, [], '[]'),
          ).toThrow();
        });
      });

      describe('Undefined representation', () => {
        it('should pass when comparing undefined with "undefined"', () => {
          expect(() =>
            performAssertion(
              AssertConstants.equals_action,
              undefined,
              'undefined',
            ),
          ).not.toThrow();
        });

        it('should fail when comparing null with "undefined"', () => {
          expect(() =>
            performAssertion(AssertConstants.equals_action, null, 'undefined'),
          ).toThrow();
        });

        it('should pass when comparing value with not equals "undefined"', () => {
          expect(() =>
            performAssertion(
              AssertConstants.not_equals_action,
              'some value',
              'undefined',
            ),
          ).not.toThrow();
        });

        it('should fail when comparing undefined with not equals "undefined"', () => {
          expect(() =>
            performAssertion(
              AssertConstants.not_equals_action,
              undefined,
              'undefined',
            ),
          ).toThrow();
        });
      });

      describe('Null representation', () => {
        it('should pass when comparing null with "null"', () => {
          expect(() =>
            performAssertion(AssertConstants.equals_action, null, 'null'),
          ).not.toThrow();
        });

        it('should fail when comparing undefined with "null"', () => {
          expect(() =>
            performAssertion(AssertConstants.equals_action, undefined, 'null'),
          ).toThrow();
        });

        it('should pass when comparing value with not equals "null"', () => {
          expect(() =>
            performAssertion(
              AssertConstants.not_equals_action,
              'some value',
              'null',
            ),
          ).not.toThrow();
        });

        it('should fail when comparing null with not equals "null"', () => {
          expect(() =>
            performAssertion(AssertConstants.not_equals_action, null, 'null'),
          ).toThrow();
        });
      });
    });
  });
});
