import {
  Context,
  ContractIssuer,
  extractContractArgs,
  extractUser,
  getMethodArguments,
  GTake,
  User,
} from '@coinweb/contract-kit';
import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';
import { getCallParameters, getContractIssuer } from 'lib/onchain';

import { ExecutorMethodArgs, ResolvedOp } from '../../types';
import { pushAwaitedTask } from '../runtime';

import { extractOps } from './extractOps';

let rawContext: Context | null = null;

const initialContext = {
  isChild: false,
  thisId: '',
  parentId: '' as string | undefined,
  ops: [] as ResolvedOp[],
  funds: {
    availableCweb: 0n,
    storedCweb: 0n,
    takeOps: [] as GTake<CwebTake>[],
  },
  methodName: '',
  initialArgs: [] as unknown[],
  user: null as User | null,
  issuer: null as ContractIssuer | null,
  parentTxId: '',
  needSaveResult: false,
};

export const getRawContext = () => {
  if (!rawContext) {
    throw new Error('Context not set');
  }

  return rawContext;
};

export const getMethodArgs = () => {
  return getMethodArguments(getRawContext()) as [methodName: string, ...ExecutorMethodArgs];
};

export const handleContext = (ctx: Context) => {
  rawContext = ctx;

  const contractArgs = extractContractArgs(getRawContext().tx);
  const [
    methodName,
    initialArgs,
    resolvedOps,
    caller,
    thisId,
    parentId,
    saveResult,
    takenFundsIds = [],
    execOpsIndexes = [],
  ] = getMethodArgs();

  initialContext.isChild = !!parentId;
  initialContext.thisId =
    thisId ??
    (BigInt(`0x${parentId ?? '0'}`) * 3n + BigInt(`0x${getRawContext().call.txid}`)).toString(16).slice(0, 32);
  initialContext.parentId = parentId;
  initialContext.methodName = methodName;
  initialContext.initialArgs = initialArgs ?? [];
  initialContext.needSaveResult = saveResult ?? false;

  const { authInfo } = getCallParameters(getRawContext());
  initialContext.user = (authInfo && extractUser(authInfo)) ?? caller ?? null;

  initialContext.issuer = getContractIssuer(getRawContext());
  initialContext.parentTxId = getRawContext().call.txid;

  console.log('Parse ops in context');
  const { extractedOps, executionOpsTakeOp, storedCweb, fundsTakeOps } = extractOps({
    contractArgs,
    takenFundsIds,
    execOpsIndexes,
  });

  const previousOps = resolvedOps ?? [];
  const allResolvedOps = [...previousOps, ...extractedOps];
  console.log('new ops >>>', JSON.stringify(extractedOps));
  initialContext.ops = allResolvedOps;

  const { availableCweb } = getCallParameters(getRawContext());

  console.log('Available Cweb: ', availableCweb);
  console.log('Stored Cweb: ', storedCweb);

  initialContext.funds = {
    availableCweb: availableCweb,
    storedCweb: storedCweb,
    takeOps: fundsTakeOps,
  };

  if (executionOpsTakeOp) {
    pushAwaitedTask(executionOpsTakeOp);
  }

  return !!executionOpsTakeOp; //This is a flag to check if the execution should restart / TODO: refactor;
};

export const context = {
  get isChild() {
    return initialContext.isChild;
  },
  get thisId() {
    return initialContext.thisId;
  },
  get parentId() {
    return initialContext.parentId;
  },
  get methodName() {
    return getMethodArguments(getRawContext())[0] as string;
  },
  get initialArgs() {
    return initialContext.initialArgs;
  },
  get user() {
    if (!initialContext.user) {
      throw new Error('User not found');
    }

    return initialContext.user;
  },
  get issuer() {
    if (!initialContext.issuer) {
      throw new Error('Issuer not found');
    }

    return initialContext.issuer;
  },
  get parentTxId() {
    return initialContext.parentTxId;
  },
  get ops() {
    return initialContext.ops;
  },
  get needSaveResult() {
    return initialContext.needSaveResult;
  },
  get funds() {
    return initialContext.funds;
  },
};
