import {
  isAssetsZero,
  lovelacesAmt,
  negateAssets,
  noAdaValue,
} from '@3rd-eye-labs/cardano-offchain-common';
import { addAssets, Assets, TxBuilder } from '@lucid-evolution/lucid';
import { JSONStringify } from 'json-with-bigint';
import { match, P } from 'ts-pattern';
import { assert, expect } from 'vitest';

export async function expectScriptFailure(
  /**
   * This doesn't have to be full message, can be just a part of it.
   */
  contains: string,
  tx: Promise<TxBuilder>,
): Promise<void> {
  if (contains.length === 0) {
    throw new Error('Expected error message has to be non empty.');
  }

  const result = await (await tx).completeSafe();

  const errMsg = match(result)
    .with({ _tag: 'Left', left: P.select() }, (smth) => smth.message)
    .otherwise(() => null);

  if (!errMsg) {
    throw new Error(`Expected TX to fail, but it succeeded.`);
  }

  if (!errMsg.includes(contains)) {
    throw new Error(
      `Expected TX to fail with error containing: "${contains}". But got: "${errMsg}"`,
    );
  }
}

export function assertValueInRange<T extends number | bigint>(
  val: T,
  bounds: { min: T; max: T },
): void {
  assert(bounds.max > bounds.min, 'Bounds are incorrectly configured.');

  expect(
    bounds.min <= val && val <= bounds.max,
    `${val} not in range [${bounds.min}, ${bounds.max}]`,
  ).toBeTruthy();
}

export function expectValue(
  actual: Assets,
  msg: string,
): {
  toEqual: (expected: Assets) => void;
  /**
   * The non ADA part of value has to equal and the lovelace has to be less than or equal than
   * the expected `maxLovelace`.
   */
  toEqualNonAdaAndMaxAda: (constraints: {
    expectedNonAda: Assets;
    maxLovelace: bigint;
  }) => void;
} {
  return {
    toEqual(expected) {
      expect(
        isAssetsZero(addAssets(actual, negateAssets(expected))),
        `${msg}: Expected ${JSONStringify(actual)} to equal ${JSONStringify(expected)}`,
      ).toBeTruthy();
    },
    toEqualNonAdaAndMaxAda({ expectedNonAda, maxLovelace }) {
      expect(
        isAssetsZero(
          addAssets(noAdaValue(actual), negateAssets(expectedNonAda)),
        ),
        `${msg}: Expected no ADA part of ${JSONStringify(actual)} to equal ${JSONStringify(expectedNonAda)}`,
      ).toBeTruthy();

      expect(
        lovelacesAmt(actual),
        `${msg}: Lovelace part is more than expected`,
      ).toBeLessThanOrEqual(maxLovelace);
    },
  };
}
