import {
  Account,
  AccountLike,
  AnyMessage,
  getCurrencyForAccount,
  Operation,
  SignedOperation,
} from "@ledgerhq/types-live";
import { accountToPlatformAccount, getPlatformTransactionSignFlowInfos } from "./converters";
import { RawPlatformTransaction, RawPlatformSignedTransaction } from "./rawTypes";
import {
  deserializePlatformTransaction,
  deserializePlatformSignedTransaction,
} from "./serializers";
import type { TrackingAPI } from "./tracking";
import { LiveAppManifest, TranslatableString } from "./types";
import { isTokenAccount, getMainAccount, isAccount } from "../account/index";
import { getAccountBridge } from "../bridge/index";
import { Transaction } from "../generated/types";
import { prepareMessageToSign } from "../hw/signMessage/index";
import { Exchange } from "../exchange/types";
import { WalletState } from "@ledgerhq/live-wallet/store";

export function translateContent(content: string | TranslatableString, locale = "en"): string {
  if (!content || typeof content === "string") return content;
  return content[locale] || content.en;
}

export type WebPlatformContext = {
  manifest: LiveAppManifest;
  accounts: AccountLike[];
  tracking: TrackingAPI;
};

function getParentAccount(account: AccountLike, fromAccounts: AccountLike[]): Account | undefined {
  return isTokenAccount(account)
    ? (fromAccounts.find(a => a.id === account.parentId) as Account)
    : undefined;
}

export function receiveOnAccountLogic(
  walletState: WalletState,
  { manifest, accounts, tracking }: WebPlatformContext,
  accountId: string,
  uiNavigation: (
    account: AccountLike,
    parentAccount: Account | undefined,
    accountAddress: string,
  ) => Promise<string>,
): Promise<string> {
  tracking.platformReceiveRequested(manifest);

  const account = accounts.find(account => account.id === accountId);

  if (!account) {
    tracking.platformReceiveFail(manifest);
    return Promise.reject(new Error("Account required"));
  }

  const parentAccount = getParentAccount(account, accounts);
  const accountAddress = accountToPlatformAccount(walletState, account, parentAccount).address;

  return uiNavigation(account, parentAccount, accountAddress);
}

export function signTransactionLogic(
  { manifest, accounts, tracking }: WebPlatformContext,
  accountId: string,
  transaction: RawPlatformTransaction,
  uiNavigation: (
    account: AccountLike,
    parentAccount: Account | undefined,
    signFlowInfos: {
      canEditFees: boolean;
      hasFeesProvided: boolean;
      liveTx: Partial<Transaction>;
    },
  ) => Promise<RawPlatformSignedTransaction>,
): Promise<RawPlatformSignedTransaction> {
  tracking.platformSignTransactionRequested(manifest);

  if (!transaction) {
    tracking.platformSignTransactionFail(manifest);
    return Promise.reject(new Error("Transaction required"));
  }

  const platformTransaction = deserializePlatformTransaction(transaction);
  const account = accounts.find(account => account.id === accountId);

  if (!account) {
    tracking.platformSignTransactionFail(manifest);
    return Promise.reject(new Error("Account required"));
  }

  const parentAccount = getParentAccount(account, accounts);

  const accountFamily = isTokenAccount(account)
    ? parentAccount?.currency.family
    : account.currency.family;

  const { canEditFees, liveTx, hasFeesProvided } =
    getPlatformTransactionSignFlowInfos(platformTransaction);

  if (accountFamily !== liveTx.family) {
    return Promise.reject(
      new Error(
        `Account and transaction must be from the same family. Account family: ${accountFamily}, Transaction family: ${liveTx.family}`,
      ),
    );
  }

  return uiNavigation(account, parentAccount, {
    canEditFees,
    liveTx,
    hasFeesProvided,
  });
}

export function broadcastTransactionLogic(
  { manifest, accounts, tracking }: WebPlatformContext,
  accountId: string,
  signedTransaction: RawPlatformSignedTransaction,
  uiNavigation: (
    account: AccountLike,
    parentAccount: Account | undefined,
    signedOperation: SignedOperation,
  ) => Promise<string>,
): Promise<string> {
  if (!signedTransaction) {
    tracking.platformBroadcastFail(manifest);
    return Promise.reject(new Error("Transaction required"));
  }

  const account = accounts.find(account => account.id === accountId);
  if (!account) {
    tracking.platformBroadcastFail(manifest);
    return Promise.reject(new Error("Account required"));
  }

  const parentAccount = getParentAccount(account, accounts);

  const signedOperation = deserializePlatformSignedTransaction(signedTransaction, accountId);

  return uiNavigation(account, parentAccount, signedOperation);
}

export type CompleteExchangeRequest = {
  provider: string;
  fromAccountId: string;
  toAccountId: string;
  transaction: RawPlatformTransaction;
  binaryPayload: string;
  signature: string;
  feesStrategy: string;
  exchangeType: number;
};
export type CompleteExchangeUiRequest = {
  provider: string;
  exchange: Exchange;
  transaction: Transaction;
  binaryPayload: string;
  signature: string;
  feesStrategy: string;
  exchangeType: number;
  swapId?: string;
  amountExpectedTo?: number;
};
export function completeExchangeLogic(
  { manifest, accounts, tracking }: WebPlatformContext,
  {
    provider,
    fromAccountId,
    toAccountId,
    transaction,
    binaryPayload,
    signature,
    feesStrategy,
    exchangeType,
  }: CompleteExchangeRequest,
  uiNavigation: (request: CompleteExchangeUiRequest) => Promise<Operation>,
): Promise<Operation> {
  tracking.platformCompleteExchangeRequested(manifest);

  // Nb get a hold of the actual accounts, and parent accounts
  const fromAccount = accounts.find(a => a.id === fromAccountId);

  const toAccount = accounts.find(a => a.id === toAccountId);

  if (!fromAccount) {
    return Promise.reject();
  }

  if (exchangeType === 0x00 && !toAccount) {
    // if we do a swap, a destination account must be provided
    return Promise.reject();
  }

  const fromParentAccount = getParentAccount(fromAccount, accounts);
  const toParentAccount = toAccount ? getParentAccount(toAccount, accounts) : undefined;
  const exchange = {
    fromAccount,
    fromParentAccount,
    toAccount,
    toParentAccount,
    fromCurrency: getCurrencyForAccount(fromAccount),
    toCurrency: toAccount ? getCurrencyForAccount(toAccount) : undefined,
  };

  const accountBridge = getAccountBridge(fromAccount, fromParentAccount);
  const mainFromAccount = getMainAccount(fromAccount, fromParentAccount);
  const mainFromAccountFamily = mainFromAccount.currency.family;

  const platformTransaction = deserializePlatformTransaction(transaction);
  const { liveTx: liveTransaction } = getPlatformTransactionSignFlowInfos(platformTransaction);

  if (liveTransaction.family !== mainFromAccountFamily) {
    return Promise.reject(
      new Error(
        `Account and transaction must be from the same family. Account family: ${mainFromAccountFamily}, Transaction family: ${liveTransaction.family}`,
      ),
    );
  }

  /**
   * 'subAccountId' is used for ETH and it's ERC-20 tokens.
   * This field is ignored for BTC
   */
  const subAccountId = fromParentAccount ? fromAccount.id : undefined;

  const bridgeTx = accountBridge.createTransaction(mainFromAccount);
  /**
   * We append the `recipient` to the tx created from `createTransaction`
   * to avoid having userGasLimit reset to null for ETH txs
   * cf. libs/ledger-live-common/src/families/ethereum/updateTransaction.ts
   */
  const tx = accountBridge.updateTransaction(
    {
      ...bridgeTx,
      recipient: liveTransaction.recipient,
    },
    {
      ...liveTransaction,
      feesStrategy,
      subAccountId,
    },
  );

  return uiNavigation({
    provider,
    exchange,
    transaction: tx,
    binaryPayload,
    signature,
    feesStrategy,
    exchangeType,
  });
}

export function signMessageLogic(
  { manifest, accounts, tracking }: WebPlatformContext,
  accountId: string,
  message: string,
  uiNavigation: (account: AccountLike, message: AnyMessage) => Promise<string>,
): Promise<string> {
  tracking.platformSignMessageRequested(manifest);

  const account = accounts.find(account => account.id === accountId);
  if (account === undefined) {
    tracking.platformSignMessageFail(manifest);
    return Promise.reject(new Error(`account with id "${accountId}" not found`));
  }

  let formattedMessage: AnyMessage;
  try {
    if (isAccount(account)) {
      formattedMessage = prepareMessageToSign(account, message);
    } else {
      throw new Error("account provided should be the main one");
    }
  } catch (error) {
    tracking.platformSignMessageFail(manifest);
    return Promise.reject(error);
  }

  return uiNavigation(account, formattedMessage);
}
