/**
 * Copyright IBM Corp. 2024, 2025
 */

import { expect } from 'chai';
import { AssertConstants } from '../constants/assertConstants.js';
import {
  validSchemaAssertion,
  invalidSchemaAssertion,
} from '../helpers/schema-validator.helper.js';

/**
 * Converts special string inputs to their actual JavaScript values
 * @param value The input value to convert
 * @returns The converted value or the original if no conversion applies
 */
function convertSpecialStringInput(value: any): any {
  if (typeof value === 'string') {
    if (value === '{}') return {};
    if (value === '[]') return [];
    if (value === 'undefined') return undefined;
    if (value === 'null') return null;
  }
  return value;
}

const assertActionHandlers: Record<
  string,
  (actual: any, expected: any) => void
> = {
  [AssertConstants.equals_action]: (a: any, e: any) => {
    const convertedValue = convertSpecialStringInput(e);

    // Use deep equality for objects and arrays
    if (typeof convertedValue === 'object' && convertedValue !== null) {
      return expect(a).to.deep.equal(convertedValue);
    }

    // Use regular equality for primitives
    return expect(a).to.equal(convertedValue);
  },
  [AssertConstants.not_equals_action]: (a: any, e: any) => {
    const convertedValue = convertSpecialStringInput(e);

    // Use deep equality for objects and arrays
    if (typeof convertedValue === 'object' && convertedValue !== null) {
      return expect(a).not.to.deep.equal(convertedValue);
    }

    // Use regular equality for primitives
    return expect(a).not.to.equal(convertedValue);
  },
  [AssertConstants.include_action]: (a: any, e: any) => expect(a).to.include(e),
  [AssertConstants.greaterThan_action]: (a: any, e: any) =>
    expect(a).to.be.above(e),
  [AssertConstants.lessThan_action]: (a: any, e: any) =>
    expect(a).to.be.below(e),
  [AssertConstants.have_property_action]: (a: any, e: any) =>
    expect(a).to.have.property(e),
  [AssertConstants.not_have_property_action]: (a: any, e: any) =>
    expect(a).not.to.have.property(e),
  [AssertConstants.lengthOf_action]: (a: any, e: any) =>
    expect(a).to.have.lengthOf(e),
  [AssertConstants.validateSchema_action]: validSchemaAssertion,
  [AssertConstants.inValidateSchema_action]: invalidSchemaAssertion,
  [AssertConstants.matches_action]: (a: any, e: any) =>
    expect(a).to.match(new RegExp(e)),
  [AssertConstants.type_action]: (a: any, e: string) =>
    e === 'array' ? expect(a).to.be.an(e) : expect(a).to.be.a(e),
};

export function performAssertion(
  action: string,
  actual: any,
  expected: any,
): void {
  const handler = assertActionHandlers[action];

  if (handler) {
    handler(actual, expected);
    return;
  }

  const parts = action.split('.');
  let assertion: any = expect(actual);

  try {
    for (let i = 0; i < parts.length - 1; i++) {
      const part = parts[i];
      if (!(part in assertion)) {
        throw new Error(
          `Invalid assertion part '${part}' in chain '${action}'`,
        );
      }
      assertion = assertion[part];
    }
    const lastMethod = parts[parts.length - 1];

    assertion[lastMethod](expected);
  } catch (error: any) {
    throw new Error(
      `Assertion failed for action '${action}': ${error.message}`,
    );
  }
}
