/**
 * @jest-environment jsdom
 */
import invariant from "invariant";
import cryptoFactory from "@ledgerhq/coin-cosmos/chain/chain";
import { getCurrentCosmosPreloadData } from "@ledgerhq/coin-cosmos/preloadedData";
import preloadedMockData from "@ledgerhq/coin-cosmos/preloadedData.mock";
import { LiveConfig } from "@ledgerhq/live-config/LiveConfig";
import { setEnv } from "@ledgerhq/live-env";
import { CurrencyBridge } from "@ledgerhq/types-live";
import { act, renderHook } from "@testing-library/react";
import "../../__tests__/test-helpers/dom-polyfill";
import { getAccountCurrency } from "../../account";
import { getAccountBridge, getCurrencyBridge } from "../../bridge";
import { makeBridgeCacheSystem } from "../../bridge/cache";
import { liveConfig } from "../../config/sharedConfig";
import { getCryptoCurrencyById } from "../../currencies";
import { genAccount, genAddingOperationsInAccount } from "../../mock/account";
import * as hooks from "./react";
import type {
  CosmosAccount,
  CosmosDelegation,
  CosmosMappedDelegation,
  CosmosResources,
  CosmosValidatorItem,
  Transaction,
} from "./types";

const localCache = {};
const cache = makeBridgeCacheSystem({
  saveData(c, d) {
    localCache[c.id] = d;
    return Promise.resolve();
  },

  getData(c) {
    return Promise.resolve(localCache[c.id]);
  },
});

describe("cosmos/react", () => {
  beforeAll(() => {
    LiveConfig.setConfig(liveConfig);
    const cosmos = cryptoFactory("cosmos");
    cosmos.lcd = LiveConfig.getValueByKey("config_currency_cosmos").lcd;
    cosmos.minGasPrice = LiveConfig.getValueByKey("config_currency_cosmos").minGasPrice;
    cosmos.ledgerValidator = LiveConfig.getValueByKey("config_currency_cosmos").ledgerValidator;
  });

  describe("useCosmosFamilyPreloadData", () => {
    it("should return Cosmos preload data and updates", async () => {
      const { prepare } = setup();
      await act(() => prepare());
      const { result } = renderHook(() => hooks.useCosmosFamilyPreloadData("cosmos"));
      const data = getCurrentCosmosPreloadData()["cosmos"];
      expect(result.current).toStrictEqual(data);
      expect(result.current).toStrictEqual(preloadedMockData);
    });
  });

  describe("useCosmosFormattedDelegations", () => {
    it("should return formatted delegations", async () => {
      const { account, prepare } = setup();
      await prepare();
      const { result } = renderHook(() => hooks.useCosmosFamilyMappedDelegations(account));
      const delegations = account.cosmosResources?.delegations;
      invariant(delegations, "cosmos: delegations is required");
      expect(account.cosmosResources?.delegations?.some(d => d.amount[0] === 0)).toBe(false);
      expect(Array.isArray(result.current)).toBe(true);
      expect(result.current.length).toBe((delegations as CosmosDelegation[]).length);
      const { code } = getAccountCurrency(account).units[0];
      expect(result.current[0].formattedAmount.split(" ")[1]).toBe(code);
      expect(result.current[0].formattedPendingRewards.split(" ")[1]).toBe(code);
      expect(typeof result.current[0].rank).toBe("number");
      expect((result.current[0].validator as CosmosValidatorItem).validatorAddress).toBe(
        (delegations as CosmosDelegation[])[0].validatorAddress,
      );
    });

    describe("mode: claimReward", () => {
      it("should only return delegations which have some pending rewards", async () => {
        const { account, prepare } = setup();
        await prepare();
        const { result } = renderHook(() =>
          hooks.useCosmosFamilyMappedDelegations(account, "claimReward"),
        );
        expect(result.current.length).toBe(3);
      });
    });
  });

  describe("useCosmosFamilyDelegationsQuerySelector", () => {
    it("should return delegations filtered by query as options", async () => {
      const { account, transaction, prepare } = setup();
      await prepare();
      invariant(account.cosmosResources, "cosmos: account and cosmos resources required");
      if (!account.cosmosResources)
        throw new Error("cosmos: account and cosmos resources required");

      const delegations = account.cosmosResources.delegations || [];
      const newTx = {
        ...transaction,
        mode: "delegate",
        validators: delegations.map(({ validatorAddress, amount }) => ({
          address: validatorAddress,
          amount,
        })),
      };
      const { result } = renderHook(() =>
        hooks.useCosmosFamilyDelegationsQuerySelector(account, newTx as Transaction),
      );
      expect(result.current.options.length).toBe(delegations.length);
      act(() => {
        result.current.setQuery("FRESHATOMS");
      });
      expect(result.current.options.length).toBe(0);
    });
    it("should return the first delegation as value", async () => {
      const { account, transaction, prepare } = setup();
      await prepare();
      invariant(account.cosmosResources, "cosmos: account and cosmos resources required");
      const delegations = (account.cosmosResources as CosmosResources).delegations || [];
      const newTx = {
        ...transaction,
        mode: "delegate",
        validators: delegations.map(({ validatorAddress, amount }) => ({
          address: validatorAddress,
          amount,
        })),
      };
      const { result } = renderHook(() =>
        hooks.useCosmosFamilyDelegationsQuerySelector(account, newTx as Transaction),
      );
      expect(
        ((result.current.value as CosmosMappedDelegation).validator as CosmosValidatorItem)
          .validatorAddress,
      ).toBe(delegations[0].validatorAddress);
    });
    it("should find delegation by sourceValidator field and return as value for redelegate", async () => {
      const { account, transaction, prepare } = setup();
      await prepare();
      invariant(account.cosmosResources, "cosmos: account and cosmos resources required");
      const delegations = (account.cosmosResources as CosmosResources).delegations || [];
      const sourceValidator = delegations[delegations.length - 1].validatorAddress;
      const newTx = {
        ...transaction,
        mode: "redelegate",
        validators: delegations.map(({ validatorAddress, amount }) => ({
          address: validatorAddress,
          amount,
        })),
        sourceValidator,
      };
      const { result } = renderHook(() =>
        hooks.useCosmosFamilyDelegationsQuerySelector(account, newTx as Transaction),
      );
      expect(
        ((result.current.value as CosmosMappedDelegation).validator as CosmosValidatorItem)
          .validatorAddress,
      ).toBe(sourceValidator);
    });
  });

  describe("useSortedValidators", () => {
    it("should reutrn sorted validators", async () => {
      const { account, prepare } = setup();
      await prepare();
      const { result: preloadDataResult } = renderHook(() =>
        hooks.useCosmosFamilyPreloadData("cosmos"),
      );
      const { validators } = preloadDataResult.current;
      const delegations = (account.cosmosResources?.delegations || []).map(
        ({ validatorAddress, amount }) => ({
          address: validatorAddress,
          amount,
        }),
      );
      const { result } = renderHook(() => hooks.useSortedValidators("", validators, delegations));
      expect(result.current.length).toBe(validators.length);
      const { result: searchResult } = renderHook(() =>
        hooks.useSortedValidators("Nodeasy.com", validators, delegations),
      );
      expect(searchResult.current.length).toBe(1);
    });
  });
  describe("reorderValidators", () => {
    it("should return a list of Validators with Ledger first", () => {
      const { result } = renderHook(() =>
        hooks.useLedgerFirstShuffledValidatorsCosmosFamily("cosmos"),
      );
      const LEDGER_VALIDATOR_ADDRESS = cryptoFactory("cosmos").ledgerValidator;
      expect(result.current[0].validatorAddress).toBe(LEDGER_VALIDATOR_ADDRESS);
    });
  });
});

function setup(): {
  account: CosmosAccount;
  currencyBridge: CurrencyBridge;
  transaction: Transaction;
  prepare: () => Promise<any>;
} {
  setEnv("MOCK", "1");
  setEnv("EXPERIMENTAL_CURRENCIES", "cosmos");
  const seed = "cosmos-2";
  const currency = getCryptoCurrencyById("cosmos");
  const a = genAccount(seed, {
    currency,
  });
  const account = genAddingOperationsInAccount(a, 3, seed) as CosmosAccount;
  const currencyBridge = getCurrencyBridge(currency);
  const bridge = getAccountBridge(account);
  const transaction = bridge.createTransaction(account);
  return {
    account,
    currencyBridge,
    transaction,
    prepare: async () => cache.prepareCurrency(currency),
  };
}
