import { DeviceModelId } from "@ledgerhq/devices";
import { CryptoCurrency, CryptoOrTokenCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
import { Account, AccountLike, AccountRaw, AccountRawLike, Operation } from "@ledgerhq/types-live";
import { BigNumber } from "bignumber.js";
import { Result as UseBridgeTransactionResult } from "../../bridge/useBridgeTransaction";
import { Transaction } from "../../generated/types";

export type { SwapLiveError } from "@ledgerhq/wallet-api-exchange-module";

export type ExchangeSwap = {
  fromParentAccount: Account | null | undefined;
  fromAccount: AccountLike;
  fromCurrency: CryptoOrTokenCurrency;
  toParentAccount: Account | null | undefined;
  toAccount: AccountLike;
  toCurrency: CryptoOrTokenCurrency;
};
export type ExchangeSwapRaw = {
  fromParentAccount: AccountRaw | null | undefined;
  fromAccount: AccountRawLike;
  toParentAccount: AccountRaw | null | undefined;
  toAccount: AccountRawLike;
};

export type ExchangeRateError = {
  name?: string;
  amount?: BigNumber;
  minAmountFromFormatted?: string;
  maxAmountFromFormatted?: string;
};

export type ExchangeRate = {
  rate?: BigNumber;
  // NB Raw rate, for display
  magnitudeAwareRate: BigNumber;
  // NB rate between satoshi units
  payoutNetworkFees?: BigNumber;
  // Only for float
  toAmount: BigNumber;
  // There's a delta somewhere between from times rate and the api.
  rateId?: string;
  provider: string;
  providerType: ExchangeProviderType;
  tradeMethod: "fixed" | "float";
  error?: ExchangeRateError;
  providerURL?: string;
  expirationTime?: number;
};

type ExchangeProviderType = "CEX" | "DEX";

type ExchangeRateCommonRaw = {
  provider: string;
  providerType: ExchangeProviderType;
  from: string;
  to: string;
  amountFrom: string;
  amountRequested?: string;
  amountTo: string;
  payoutNetworkFees: string;
  status: "success";
  errorCode?: number;
  errorMessage?: string;
  providerURL?: string;
};

type ExchangeRateFloatRateRaw = ExchangeRateCommonRaw & {
  tradeMethod: "float";
  rateId?: string;
  minAmountFrom: string;
  maxAmountFrom?: string;
};

type ExchangeRateFixedRateRaw = ExchangeRateCommonRaw & {
  tradeMethod: "fixed";
  rateId: string;
  expirationTime: string;
  rate: string;
};

type ExchangeRateErrorCommon = {
  status: "error";
  tradeMethod: TradeMethod;
  from: string;
  to: string;
  providerType: ExchangeProviderType;
  provider: string;
};

export type ExchangeRateErrorDefault = ExchangeRateErrorCommon & {
  errorCode: number;
  errorMessage: string;
};

type ExchangeRateErrorMinMaxAmount = ExchangeRateErrorCommon & {
  amountRequested: string;
  minAmountFrom: string;
  maxAmountFrom: string;
};

export type ExchangeRateErrors = ExchangeRateErrorDefault | ExchangeRateErrorMinMaxAmount;

export type ExchangeRateResponseRaw =
  | ExchangeRateFloatRateRaw
  | ExchangeRateFixedRateRaw
  | ExchangeRateErrors;

export type TradeMethod = "fixed" | "float";

export type ExchangeRateRaw = {
  rate: string;
  magnitudeAwareRate: string;
  payoutNetworkFees?: string;
  toAmount: string;
  rateId?: string;
  provider: string;
  providerType: "CEX" | "DEX";
  tradeMethod: TradeMethod;
  error?: string;
  providerURL?: string;
};

export type AvailableProviderV2 = {
  provider: string;
  supportedCurrencies: string[];
};

export type AvailableProviderV3 = {
  provider: string;
  pairs: Pair[];
};

export interface Pair {
  from: string;
  to: string;
  tradeMethod: string;
}

export type GetProviders = () => Promise<AvailableProvider[]>;

type TradeMethodGroup = {
  methods: TradeMethod[];
  pairs: {
    [currencyIndex: number]: number[];
  };
};

export type ProvidersResponseV4 = {
  currencies: { [currencyIndex: number]: string };
  providers: { [providerName: string]: TradeMethodGroup[] };
};

export type AvailableProvider = AvailableProviderV3;

export type ExchangeObject = {
  exchange: ExchangeSwap;
  transaction: Transaction;
  currencyTo?: TokenCurrency | CryptoCurrency | undefined | null;
  providers?: AvailableProviderV3[];
  timeout?: number;
  timeoutErrorMessage?: string;
};

export type GetExchangeRates = (
  exchangeObject: ExchangeObject,
) => Promise<(ExchangeRate & { expirationDate?: Date })[]>;

type ValidSwapStatus = "pending" | "onhold" | "expired" | "finished" | "refunded";

export type SwapStatusRequest = {
  provider: string;
  swapId: string;
  transactionId?: string;
  operationId?: string;
};
export type SwapStatus = {
  provider: string;
  swapId: string;
  status: ValidSwapStatus;
  finalAmount?: string;
};

// -----
// Related to Swap state API call (accepted or cancelled)

export type FeatureFlags = {
  wallet40Ux?: boolean;
};

type SwapStateRequest = {
  provider: string;
  swapId: string;
} & Partial<{
  swapStep: string;
  statusCode: string;
  errorMessage: string;
  sourceCurrencyId: string;
  targetCurrencyId: string;
  hardwareWalletType: DeviceModelId;
  swapType: TradeMethod;
  swapAppVersion?: string;
  fromAccountAddress?: string;
  toAccountAddress?: string;
  fromAmount?: string;
  seedIdFrom?: string;
  seedIdTo?: string;
  refundAddress?: string;
  payoutAddress?: string;
  sponsored?: boolean;
  flags?: FeatureFlags;
}>;

export type SwapStateAcceptedRequest = SwapStateRequest & {
  transactionId: string;
};

export type SwapStateCancelledRequest = SwapStateRequest & {
  data?: string;
};

export type PostSwapAccepted = (arg0: SwapStateAcceptedRequest) => Promise<null>;

export type PostSwapCancelled = (arg0: SwapStateCancelledRequest) => Promise<null>;

// -----

export type UpdateAccountSwapStatus = (arg0: Account) => Promise<Account | null | undefined>;
export type GetMultipleStatus = (arg0: SwapStatusRequest[]) => Promise<SwapStatus[]>;

export type SwapHistorySection = {
  day: Date;
  data: MappedSwapOperation[];
};
export type MappedSwapOperation = {
  fromAccount: AccountLike;
  fromParentAccount?: Account;
  toAccount: AccountLike;
  toParentAccount?: Account;
  toExists: boolean;
  operation: Operation;
  provider: string;
  swapId: string;
  status: string;
  fromAmount: BigNumber;
  toAmount: BigNumber;
  finalAmount?: BigNumber;
};
export type SwapState = {
  // NB fromAccount and fromParentAccount and amount come from `useBridgeTransaction`
  useAllAmount?: boolean;
  loadingRates?: boolean;
  isTimerVisible?: boolean;
  error?: Error | null | undefined;
  fromCurrency?: (CryptoCurrency | TokenCurrency) | null | undefined;
  toCurrency?: (CryptoCurrency | TokenCurrency) | null | undefined;
  toAccount?: AccountLike | null | undefined;
  toParentAccount?: Account | null | undefined;
  ratesExpiration?: Date | null | undefined;
  exchangeRate?: ExchangeRate | null | undefined;
  withExpiration?: boolean;
};

export type SwapTransaction = Transaction & {
  tag?: number;
  memoValue?: string;
  memoType?: string;
};

export interface CustomMinOrMaxError extends Error {
  amount: BigNumber;
}

export type SwapSelectorStateType = {
  currency: TokenCurrency | CryptoCurrency | undefined;
  account: AccountLike | undefined;
  parentAccount: Account | undefined;
  amount: BigNumber | undefined;
};

export type OnNoRatesCallback = (arg: {
  fromState: SwapSelectorStateType;
  toState: SwapSelectorStateType;
}) => void;

export type OnBeforeFetchRates = () => void;

export type RatesReducerState = {
  status?: string | null;
  value?: ExchangeRate[];
  error?: Error;
};

export type SwapDataType = {
  from: SwapSelectorStateType;
  to: SwapSelectorStateType;
  isMaxEnabled: boolean;
  isMaxLoading: boolean;
  isSwapReversable: boolean;
  rates: RatesReducerState;
  refetchRates: () => void;
  updateSelectedRate: (selected?: ExchangeRate) => void;
  targetAccounts?: Account[];
  countdown: undefined | number;
};

export type SwapTransactionType = UseBridgeTransactionResult & {
  swap: SwapDataType;
  setFromAccount: (account: SwapSelectorStateType["account"]) => void;
  setToAccount: (
    currency: SwapSelectorStateType["currency"],
    account: SwapSelectorStateType["account"],
    parentAccount: SwapSelectorStateType["parentAccount"],
  ) => void;
  setFromAmount: (amount: BigNumber) => void;
  setToAmount: (amount: BigNumber) => void;
  setToCurrency: (currency: SwapSelectorStateType["currency"]) => void;
  toggleMax: () => void;
  reverseSwap: () => void;
  fromAmountError?: Error;
  fromAmountWarning?: Error;
};

export type SwapPayloadRequestData = {
  provider: string;
  deviceTransactionId: string;
  fromAccountAddress: string;
  toAccountAddress: string;
  fromAccountCurrency: string;
  toAccountCurrency: string;
  amount: string;
  amountInAtomicUnit: BigNumber;
  quoteId?: string;
  toNewTokenId?: string;
  flags?: FeatureFlags;
};
export type SwapPayloadResponse = {
  binaryPayload: string;
  signature: string;
  payinAddress: string;
  swapId: string;
  payinExtraId?: string;
  extraTransactionParameters?: string;
};

export type ConfirmSwapRequest = {
  provider: string;
  swapId: string;
  transactionId: string;
  sourceCurrencyId?: string;
  targetCurrencyId?: string;
  hardwareWalletType?: string;
};

export type CancelSwapRequest = {
  provider: string;
  swapId: string;
  statusCode?: string;
  errorMessage?: string;
  sourceCurrencyId?: string;
  targetCurrencyId?: string;
  hardwareWalletType?: string;
  swapType?: string;
  swapStep?: string;
};

export type SwapBackendResponse = {
  provider: string;
  swapId: string;
  apiExtraFee: number;
  apiFee: number;
  refundAddress: string;
  amountExpectedFrom: number;
  amountExpectedTo: number;
  status: string;
  from: string;
  to: string;
  payinAddress: string;
  payoutAddress: string;
  createdAt: string; // ISO-8601
  binaryPayload: string;
  signature: string;
  payinExtraId?: string;
  extraTransactionParameters?: string;
};
