import {
  constructContinueTx,
  constructContractRef,
  constructRead,
  Context,
  extractContractArgs,
  extractRead,
  PreparedOperation,
} from '@coinweb/contract-kit';
import { getCallParameters, getContractIssuer, getMethodArguments } from 'lib/onchain';
import { TypedClaim } from 'lib/shared';

import { constructMutexExecOpsClaimStore } from '../claims';
import { MutexLockState, MutexExecOpArgs, MutexSaveExecOpResultArgs, MutexPreReadTakeOpsArgs } from '../types';
import { isMatchLockKeys } from '../utils';

import { saveExecOpResultMethodName } from './names';

export const execOps = (context: Context) => {
  const { availableCweb } = getCallParameters(context);
  const issuer = getContractIssuer(context);

  const [ops, processId, execId] = getMethodArguments<MutexExecOpArgs>(context);

  const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map(
    ({ content }) => content as TypedClaim<MutexLockState>
  );

  let availableOps: PreparedOperation[] = [];
  const unavailableIndexes: number[] = [];

  console.log('extractContractArgs(context.tx) >>>', JSON.stringify(extractContractArgs(context.tx)));
  console.log('ops >>>', JSON.stringify(ops));
  console.log('lockQueue >>>', JSON.stringify(lockQueue));

  if (!lockQueue) {
    availableOps = ops;
  } else {
    ops.forEach((op, i) => {
      const claimKey = 'StoreOp' in op ? op.StoreOp.key : op.TakeOp.key;
      const isLockedByOtherProcess = lockQueue.some(
        ({ body }) =>
          body.locked && body.processId !== processId && body.keys.some((key) => isMatchLockKeys(key, claimKey))
      );

      if (isLockedByOtherProcess) {
        unavailableIndexes.push(i);
      } else {
        availableOps.push(op);
      }
    });
  }

  if (availableOps.length === 0) {
    return execId
      ? [
          constructContinueTx(context, [
            constructMutexExecOpsClaimStore(execId, new Array(ops.length).fill({ ok: false })),
          ]),
        ]
      : [];
  }

  if (!execId) {
    return [constructContinueTx(context, availableOps)];
  }

  const preReadTakeOps = availableOps.filter((op) => 'TakeOp' in op).map((op) => constructRead(issuer, op.TakeOp.key));

  if (preReadTakeOps.length > 0) {
    const fee = 700n + BigInt(preReadTakeOps.length) * 100n;

    return [
      constructContinueTx(
        context,
        [],
        [
          {
            callInfo: {
              ref: constructContractRef(issuer, []),
              methodInfo: {
                methodName: saveExecOpResultMethodName,
                methodArgs: [execId, unavailableIndexes, availableOps] satisfies MutexPreReadTakeOpsArgs,
              },
              contractInfo: {
                providedCweb: availableCweb - fee,
                authenticated: null,
              },
              contractArgs: preReadTakeOps,
            },
          },
        ]
      ),
    ];
  }

  const fee = 700n + BigInt(availableOps.length) * 100n;

  return [
    constructContinueTx(
      context,
      [],
      [
        {
          callInfo: {
            ref: constructContractRef(issuer, []),
            methodInfo: {
              methodName: saveExecOpResultMethodName,
              methodArgs: [execId, unavailableIndexes] satisfies MutexSaveExecOpResultArgs,
            },
            contractInfo: {
              providedCweb: availableCweb - fee,
              authenticated: null,
            },
            contractArgs: availableOps,
          },
        },
      ]
    ),
  ];
};
