// Copyright 2022 @paritytech/contracts-ui authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import BN from 'bn.js';
import { compactAddLength, u8aToU8a, isNumber, BN_TEN } from '@polkadot/util';
import { randomAsU8a } from '@polkadot/util-crypto';
import { MAX_CALL_WEIGHT } from '../../constants';
import {
  Bytes,
  ApiPromise,
  AbiParam,
  Registry,
  OrFalsy,
  Weight,
  ChainType,
  SubmittableResult,
  Hash,
  CodeBundleDocument,
} from 'types';
import { isValidCodeHash } from 'ui/util';

const EMPTY_SALT = new Uint8Array();

export function maximumBlockWeight(api: OrFalsy<ApiPromise>): Weight {
  return api?.consts.system.blockWeights
    ? api.consts.system.blockWeights.maxBlock
    : (api?.consts.system.maximumBlockWeight as Weight) || MAX_CALL_WEIGHT;
}

export function encodeSalt(salt: Uint8Array | string | null = randomAsU8a()): Uint8Array {
  return salt instanceof Bytes
    ? salt
    : salt && salt.length
    ? compactAddLength(u8aToU8a(salt))
    : EMPTY_SALT;
}

export const NOOP = (): void => undefined;

export function fromBalance(value: BN | number | string | null): string {
  if (!value) {
    return '';
  }

  return value.toString();
}

export function toBalance(api: ApiPromise, value: string | number): BN {
  const asString = isNumber(value) ? value.toString() : value;
  const siPower = new BN(api.registry.chainDecimals[0]);

  const isDecimalValue = /^(\d+)\.(\d+)$/.exec(asString);

  if (isDecimalValue) {
    const div = new BN(asString.replace(/\.\d*$/, ''));
    const modString = asString.replace(/^\d+\./, '').substr(0, api.registry.chainDecimals[0]);
    const mod = new BN(modString);

    return div
      .mul(BN_TEN.pow(siPower))
      .add(mod.mul(BN_TEN.pow(new BN(siPower.subn(modString.length)))));
  } else {
    return new BN(asString.replace(/[^\d]/g, '')).mul(BN_TEN.pow(siPower));
  }
}

export function toSats(api: ApiPromise, balance: BN | number): BN {
  let bn: BN;

  if (isNumber(balance)) {
    bn = new BN(balance);
  } else {
    bn = balance;
  }

  return bn.mul(BN_TEN.pow(new BN(api.registry.chainDecimals[0])));
}

export function fromSats(api: ApiPromise, sats: BN): string {
  const pow = BN_TEN.pow(new BN(api.registry.chainDecimals[0]));
  const [div, mod] = [sats.div(pow), sats.mod(pow)];

  return `${div.toString()}${!mod.eqn(0) ? `.${mod.toString()}` : ''}`;
}

export function convertToNumber(value: string) {
  return value.includes('.') ? parseFloat(value) : parseInt(value);
}

export function transformUserInput(
  registry: Registry,
  messageArgs: AbiParam[],
  values?: Record<string, unknown>
) {
  return messageArgs.map(({ name, type: { type } }) => {
    const value = values ? values[name] : null;

    if (type === 'Balance') {
      return registry.createType('Balance', value);
    }

    return value || null;
  });
}

export function isValidWsUrl(s: unknown) {
  if (typeof s === 'string') {
    let url;
    try {
      url = new URL(s);
    } catch (_) {
      return false;
    }

    return url.protocol === 'ws:' || url.protocol === 'wss:';
  }
  return false;
}

export function isResultReady(result: SubmittableResult, systemChainType: ChainType): boolean {
  return systemChainType.isDevelopment ? result.isInBlock : result.isFinalized;
}

export function getBlockHash(
  status: SubmittableResult['status'],
  systemChainType: ChainType
): Hash {
  return systemChainType.isDevelopment ? status.asInBlock : status.asFinalized;
}

export async function getContractInfo(api: ApiPromise, address: string) {
  return (await api.query.contracts.contractInfoOf(address)).unwrapOr(null);
}

export async function checkOnChainCode(api: ApiPromise, codeHash: string): Promise<boolean> {
  return isValidCodeHash(codeHash)
    ? (await api.query.contracts.codeStorage(codeHash)).isSome
    : false;
}

export async function filterOnChainCode(api: ApiPromise, items: CodeBundleDocument[]) {
  const codes: CodeBundleDocument[] = [];
  for (const item of items) {
    const isOnChain = await checkOnChainCode(api, item.codeHash);
    isOnChain && codes.push(item);
  }
  return codes;
}