// TODO makeMockBridge need to be exploded into families (bridge/mock) with utility code shared.
import { genOperation } from "@ledgerhq/ledger-wallet-framework/mocks/account";
import { Account, AccountBridge, CurrencyBridge, Operation } from "@ledgerhq/types-live";
import { BigNumber } from "bignumber.js";
import Prando from "prando";
import { Observable, of } from "rxjs";
import { getEnv } from "@ledgerhq/live-env";
import perFamilyMock from "../generated/mock";
import { genAccount } from "../mock/account";
import { getOperationAmountNumber } from "../operation";
import { delay } from "../promise";
import { Result } from "@ledgerhq/ledger-wallet-framework/derivation";
const MOCK_DATA_SEED = getEnv("MOCK") || "MOCK";
const broadcasted: Record<string, Operation[]> = {};
const syncTimeouts = {};

export const sync: AccountBridge<any>["sync"] = initialAccount =>
  new Observable(o => {
    const accountId = initialAccount.id;

    const sync = () => {
      const ops = broadcasted[accountId] || [];
      broadcasted[accountId] = [];
      o.next(acc => {
        const balance = ops.reduce(
          (sum, op) => sum.plus(getOperationAmountNumber(op)),
          acc.balance,
        );
        const nextAcc = {
          ...acc,
          blockHeight: acc.blockHeight + 1000, // make a sync move a lot by blockHeight to avoid flawky tests issue on op confirm.
          lastSyncDate: new Date(),
          operations: ops.concat(acc.operations.slice(0)),
          pendingOperations: [],
          balance,
          spendableBalance: balance,
        };
        const perFamilyOperation = perFamilyMock[acc.currency.id];
        const postSyncAccount = perFamilyOperation && perFamilyOperation.postSyncAccount;
        if (postSyncAccount) return postSyncAccount(nextAcc);
        return nextAcc;
      });
      o.complete();
    };

    syncTimeouts[accountId] = setTimeout(sync, 500);

    return () => {
      clearTimeout(syncTimeouts[accountId]);
      syncTimeouts[accountId] = null;
    };
  });

export const broadcast: AccountBridge<any>["broadcast"] = ({ signedOperation }) =>
  Promise.resolve(signedOperation.operation);

export const signOperation: AccountBridge<any>["signOperation"] = ({ account, transaction }) =>
  new Observable(o => {
    let cancelled = false;

    async function main() {
      await delay(1000);
      if (cancelled) return;

      for (let i = 0; i <= 1; i += 0.1) {
        o.next({
          type: "device-streaming",
          progress: i,
          index: i,
          total: 10,
        });
        await delay(300);
      }

      o.next({
        type: "device-signature-requested",
      });
      await delay(2000);
      if (cancelled) return;
      o.next({
        type: "device-signature-granted",
      });
      const rng = new Prando("");
      const op = genOperation(account, account, account.operations, rng);
      op.type = "OUT";
      op.value = transaction.amount;
      op.blockHash = null;
      op.blockHeight = null;
      op.senders = [account.freshAddress];
      op.recipients = [transaction.recipient];
      op.blockHeight = account.blockHeight;
      op.date = new Date();
      await delay(1000);
      if (cancelled) return;
      broadcasted[account.id] = (broadcasted[account.id] || []).concat(op);
      o.next({
        type: "signed",
        signedOperation: {
          operation: { ...op },
          signature: "",
        },
      });
    }

    main().then(
      () => o.complete(),
      e => o.error(e),
    );
    return () => {
      cancelled = true;
    };
  });

export { isInvalidRecipient } from "./validateAddress";

const subtractOneYear = date =>
  new Date(new Date(date).setFullYear(new Date(date).getFullYear() - 1));

export const scanAccounts: CurrencyBridge["scanAccounts"] = ({ currency }) =>
  new Observable(o => {
    let unsubscribed = false;

    async function job() {
      // TODO offer a way to mock a failure
      const nbAccountToGen = 3;

      for (let i = 0; i < nbAccountToGen && !unsubscribed; i++) {
        const isLast = i === 2;
        await delay(500);
        const account = genAccount(`${MOCK_DATA_SEED}_${currency.id}_${i}`, {
          operationsSize: isLast ? 0 : 100,
          currency,
          subAccountsCount: isLast ? 0 : undefined,
          bandwidth: !isLast,
        });
        account.index = i;
        account.operations = isLast
          ? []
          : account.operations.map(operation => ({
              ...operation,
              date: subtractOneYear(operation.date),
            }));
        account.used = isLast ? false : account.used;

        if (isLast) {
          account.spendableBalance = account.balance = new BigNumber(0);
        }

        const perFamilyOperation = perFamilyMock[currency.id];
        const postScanAccount = perFamilyOperation && perFamilyOperation.postScanAccount;
        if (postScanAccount)
          postScanAccount(account, {
            isEmpty: isLast,
          });
        if (!unsubscribed)
          o.next({
            type: "discovered",
            account,
          });
      }

      if (!unsubscribed) o.complete();
    }

    job();
    return () => {
      unsubscribed = true;
    };
  });

export const makeAccountBridgeReceive: () => (
  account: Account,
  arg1: {
    verify?: boolean;
    deviceId: string;
    subAccountId?: string;
  },
) => Observable<Result> = () => account =>
  of({
    address: account.freshAddress,
    path: account.freshAddressPath,
    publicKey: "mockPublickKey", // We could probably keep the publicKey in `account.freshPublicKey`
  });

export const signRawOperation: AccountBridge<any>["signRawOperation"] = ({ account }) =>
  new Observable(o => {
    let cancelled = false;

    async function main() {
      await delay(1000);
      if (cancelled) return;

      for (let i = 0; i <= 1; i += 0.1) {
        o.next({
          type: "device-streaming",
          progress: i,
          index: i,
          total: 10,
        });
        await delay(300);
      }

      o.next({
        type: "device-signature-requested",
      });
      await delay(2000);
      if (cancelled) return;
      o.next({
        type: "device-signature-granted",
      });
      const rng = new Prando("");
      const op = genOperation(account, account, account.operations, rng);
      op.type = "OUT";
      op.value = new BigNumber(0);
      op.blockHash = null;
      op.blockHeight = null;
      op.senders = [account.freshAddress];
      op.recipients = [];
      op.blockHeight = account.blockHeight;
      op.date = new Date();
      await delay(1000);
      if (cancelled) return;
      broadcasted[account.id] = (broadcasted[account.id] || []).concat(op);
      o.next({
        type: "signed",
        signedOperation: {
          operation: { ...op },
          signature: "",
        },
      });
    }

    main().then(
      () => o.complete(),
      e => o.error(e),
    );
    return () => {
      cancelled = true;
    };
  });
