import * as ACTIONS from 'constants/action_types';
import Lbry from 'lbry';
import { doToast } from 'redux/actions/notifications';
import {
  selectBalance,
  selectPendingSupportTransactions,
  selectTxoPageParams,
  selectPendingOtherTransactions,
  selectPendingConsolidateTxid,
  selectPendingMassClaimTxid,
} from 'redux/selectors/wallet';
import { creditsToString } from 'util/format-credits';
import { selectMyClaimsRaw, selectClaimsById } from 'redux/selectors/claims';
import { doFetchChannelListMine, doFetchClaimListMine, doClaimSearch } from 'redux/actions/claims';

const FIFTEEN_SECONDS = 15000;
let walletBalancePromise = null;

export function doUpdateBalance() {
  return (dispatch, getState) => {
    const {
      wallet: { totalBalance: totalInStore },
    } = getState();

    if (walletBalancePromise === null) {
      walletBalancePromise = Lbry.wallet_balance()
        .then((response) => {
          walletBalancePromise = null;

          const { available, reserved, reserved_subtotals, total } = response;
          const { claims, supports, tips } = reserved_subtotals;
          const totalFloat = parseFloat(total);

          if (totalInStore !== totalFloat) {
            dispatch({
              type: ACTIONS.UPDATE_BALANCE,
              data: {
                totalBalance: totalFloat,
                balance: parseFloat(available),
                reservedBalance: parseFloat(reserved),
                claimsBalance: parseFloat(claims),
                supportsBalance: parseFloat(supports),
                tipsBalance: parseFloat(tips),
              },
            });
          }
        })
        .catch(() => {
          walletBalancePromise = null;
        });
    }

    return walletBalancePromise;
  };
}

export function doBalanceSubscribe() {
  return (dispatch) => {
    dispatch(doUpdateBalance());
    setInterval(() => dispatch(doUpdateBalance()), 10000);
  };
}

export function doFetchTransactions(page = 1, pageSize = 999999) {
  return (dispatch) => {
    dispatch({
      type: ACTIONS.FETCH_TRANSACTIONS_STARTED,
    });

    Lbry.transaction_list({ page, page_size: pageSize }).then((result) => {
      dispatch({
        type: ACTIONS.FETCH_TRANSACTIONS_COMPLETED,
        data: {
          transactions: result.items,
        },
      });
    });
  };
}

export function doFetchTxoPage() {
  return (dispatch, getState) => {
    const fetchId = Math.random().toString(36).substr(2, 9);

    dispatch({
      type: ACTIONS.FETCH_TXO_PAGE_STARTED,
      data: fetchId,
    });

    const state = getState();
    const queryParams = selectTxoPageParams(state);

    Lbry.txo_list(queryParams)
      .then((res) => {
        const items = res.items || [];
        const claimsById = selectClaimsById(state);

        const channelIds = items.reduce((acc, cur) => {
          if (cur.type === 'support' && cur.signing_channel && !claimsById[cur.signing_channel.channel_id]) {
            acc.push(cur.signing_channel.channel_id);
          }
          return acc;
        }, []);

        if (channelIds.length) {
          const searchParams = {
            page_size: 9999,
            page: 1,
            no_totals: true,
            claim_ids: channelIds,
          };
          // make sure redux has these channels resolved
          dispatch(doClaimSearch(searchParams));
        }

        return res;
      })
      .then((res) => {
        dispatch({
          type: ACTIONS.FETCH_TXO_PAGE_COMPLETED,
          data: {
            result: res,
            fetchId: fetchId,
          },
        });
      })
      .catch((e) => {
        dispatch({
          type: ACTIONS.FETCH_TXO_PAGE_COMPLETED,
          data: {
            error: e.message,
            fetchId: fetchId,
          },
        });
      });
  };
}

export function doUpdateTxoPageParams(params) {
  return (dispatch) => {
    dispatch({
      type: ACTIONS.UPDATE_TXO_FETCH_PARAMS,
      data: params,
    });

    dispatch(doFetchTxoPage());
  };
}

export function doFetchSupports(page = 1, pageSize = 99999) {
  return (dispatch) => {
    dispatch({
      type: ACTIONS.FETCH_SUPPORTS_STARTED,
    });

    Lbry.support_list({ page, page_size: pageSize }).then((result) => {
      dispatch({
        type: ACTIONS.FETCH_SUPPORTS_COMPLETED,
        data: {
          supports: result.items,
        },
      });
    });
  };
}

export function doFetchUtxoCounts() {
  return async (dispatch) => {
    dispatch({
      type: ACTIONS.FETCH_UTXO_COUNT_STARTED,
    });

    let resultSets = await Promise.all([
      Lbry.txo_list({ type: 'other', is_not_spent: true, page: 1, page_size: 1 }),
      Lbry.txo_list({ type: 'support', is_not_spent: true, page: 1, page_size: 1 }),
    ]);
    const counts = {};
    const paymentCount = resultSets[0]['total_items'];
    const supportCount = resultSets[1]['total_items'];
    counts['other'] = typeof paymentCount === 'number' ? paymentCount : 0;
    counts['support'] = typeof supportCount === 'number' ? supportCount : 0;

    dispatch({
      type: ACTIONS.FETCH_UTXO_COUNT_COMPLETED,
      data: counts,
      debug: { resultSets },
    });
  };
}

export function doUtxoConsolidate() {
  return async (dispatch) => {
    dispatch({
      type: ACTIONS.DO_UTXO_CONSOLIDATE_STARTED,
    });

    const results = await Lbry.txo_spend({ type: 'other' });
    const result = results[0];

    dispatch({
      type: ACTIONS.PENDING_CONSOLIDATED_TXOS_UPDATED,
      data: { txids: [result.txid] },
    });

    dispatch({
      type: ACTIONS.DO_UTXO_CONSOLIDATE_COMPLETED,
      data: { txid: result.txid },
    });
    dispatch(doCheckPendingTxs());
  };
}

export function doTipClaimMass() {
  return async (dispatch) => {
    dispatch({
      type: ACTIONS.TIP_CLAIM_MASS_STARTED,
    });

    const results = await Lbry.txo_spend({ type: 'support', is_not_my_input: true });
    const result = results[0];

    dispatch({
      type: ACTIONS.PENDING_CONSOLIDATED_TXOS_UPDATED,
      data: { txids: [result.txid] },
    });

    dispatch({
      type: ACTIONS.TIP_CLAIM_MASS_COMPLETED,
      data: { txid: result.txid },
    });
    dispatch(doCheckPendingTxs());
  };
}

export function doGetNewAddress() {
  return (dispatch) => {
    dispatch({
      type: ACTIONS.GET_NEW_ADDRESS_STARTED,
    });

    Lbry.address_unused().then((address) => {
      dispatch({
        type: ACTIONS.GET_NEW_ADDRESS_COMPLETED,
        data: { address },
      });
    });
  };
}

export function doCheckAddressIsMine(address) {
  return (dispatch) => {
    dispatch({
      type: ACTIONS.CHECK_ADDRESS_IS_MINE_STARTED,
    });

    Lbry.address_is_mine({ address }).then((isMine) => {
      if (!isMine) dispatch(doGetNewAddress());

      dispatch({
        type: ACTIONS.CHECK_ADDRESS_IS_MINE_COMPLETED,
      });
    });
  };
}

export function doSendDraftTransaction(address, amount) {
  return (dispatch, getState) => {
    const state = getState();
    const balance = selectBalance(state);

    if (balance - amount <= 0) {
      dispatch(
        doToast({
          title: __('Insufficient credits'),
          message: __('Insufficient credits'),
        })
      );
      return;
    }

    dispatch({
      type: ACTIONS.SEND_TRANSACTION_STARTED,
    });

    const successCallback = (response) => {
      if (response.txid) {
        dispatch({
          type: ACTIONS.SEND_TRANSACTION_COMPLETED,
        });
        dispatch(
          doToast({
            message: __('You sent %amount% LBRY Credits', { amount: amount }),
            linkText: __('History'),
            linkTarget: '/wallet',
          })
        );
      } else {
        dispatch({
          type: ACTIONS.SEND_TRANSACTION_FAILED,
          data: { error: response },
        });
        dispatch(
          doToast({
            message: __('Transaction failed'),
            isError: true,
          })
        );
      }
    };

    const errorCallback = (error) => {
      dispatch({
        type: ACTIONS.SEND_TRANSACTION_FAILED,
        data: { error: error.message },
      });
      dispatch(
        doToast({
          message: __('Transaction failed'),
          isError: true,
        })
      );
    };

    Lbry.wallet_send({
      addresses: [address],
      amount: creditsToString(amount),
    }).then(successCallback, errorCallback);
  };
}

export function doSetDraftTransactionAmount(amount) {
  return {
    type: ACTIONS.SET_DRAFT_TRANSACTION_AMOUNT,
    data: { amount },
  };
}

export function doSetDraftTransactionAddress(address) {
  return {
    type: ACTIONS.SET_DRAFT_TRANSACTION_ADDRESS,
    data: { address },
  };
}

export function doSendTip(params, isSupport, successCallback, errorCallback, shouldNotify = true) {
  return (dispatch, getState) => {
    const state = getState();
    const balance = selectBalance(state);
    const myClaims = selectMyClaimsRaw(state);

    const shouldSupport =
      isSupport || (myClaims ? myClaims.find((claim) => claim.claim_id === params.claim_id) : false);

    if (balance - params.amount <= 0) {
      dispatch(
        doToast({
          message: __('Insufficient credits'),
          isError: true,
        })
      );
      return;
    }

    const success = (response) => {
      if (shouldNotify) {
        dispatch(
          doToast({
            message: shouldSupport
              ? __('You deposited %amount% LBRY Credits as a support!', { amount: params.amount })
              : __('You sent %amount% LBRY Credits as a tip, Mahalo!', { amount: params.amount }),
            linkText: __('History'),
            linkTarget: '/wallet',
          })
        );
      }

      dispatch({
        type: ACTIONS.SUPPORT_TRANSACTION_COMPLETED,
      });

      if (successCallback) {
        successCallback(response);
      }
    };

    const error = (err) => {
      dispatch(
        doToast({
          message: __(`There was an error sending support funds.`),
          isError: true,
        })
      );

      dispatch({
        type: ACTIONS.SUPPORT_TRANSACTION_FAILED,
        data: {
          error: err,
        },
      });

      if (errorCallback) {
        errorCallback();
      }
    };

    dispatch({
      type: ACTIONS.SUPPORT_TRANSACTION_STARTED,
    });

    Lbry.support_create({
      ...params,
      tip: !shouldSupport,
      blocking: true,
      amount: creditsToString(params.amount),
    }).then(success, error);
  };
}

export function doClearSupport() {
  return {
    type: ACTIONS.CLEAR_SUPPORT_TRANSACTION,
  };
}

export function doWalletEncrypt(newPassword) {
  return (dispatch) => {
    dispatch({
      type: ACTIONS.WALLET_ENCRYPT_START,
    });

    Lbry.wallet_encrypt({ new_password: newPassword }).then((result) => {
      if (result === true) {
        dispatch({
          type: ACTIONS.WALLET_ENCRYPT_COMPLETED,
          result,
        });
      } else {
        dispatch({
          type: ACTIONS.WALLET_ENCRYPT_FAILED,
          result,
        });
      }
    });
  };
}

export function doWalletUnlock(password) {
  return (dispatch) => {
    dispatch({
      type: ACTIONS.WALLET_UNLOCK_START,
    });

    Lbry.wallet_unlock({ password }).then((result) => {
      if (result === true) {
        dispatch({
          type: ACTIONS.WALLET_UNLOCK_COMPLETED,
          result,
        });
      } else {
        dispatch({
          type: ACTIONS.WALLET_UNLOCK_FAILED,
          result,
        });
      }
    });
  };
}

export function doWalletLock() {
  return (dispatch) => {
    dispatch({
      type: ACTIONS.WALLET_LOCK_START,
    });

    Lbry.wallet_lock().then((result) => {
      if (result === true) {
        dispatch({
          type: ACTIONS.WALLET_LOCK_COMPLETED,
          result,
        });
      } else {
        dispatch({
          type: ACTIONS.WALLET_LOCK_FAILED,
          result,
        });
      }
    });
  };
}

// Collect all tips for a claim
export function doSupportAbandonForClaim(claimId, claimType, keep, preview) {
  return (dispatch) => {
    if (preview) {
      dispatch({
        type: ACTIONS.ABANDON_CLAIM_SUPPORT_PREVIEW,
      });
    } else {
      dispatch({
        type: ACTIONS.ABANDON_CLAIM_SUPPORT_STARTED,
      });
    }

    const params = { claim_id: claimId };
    if (preview) params['preview'] = true;
    if (keep) params['keep'] = keep;
    return Lbry.support_abandon(params)
      .then((res) => {
        if (!preview) {
          dispatch({
            type: ACTIONS.ABANDON_CLAIM_SUPPORT_COMPLETED,
            data: { claimId, txid: res.txid, effective: res.outputs[0].amount, type: claimType },
          });
          dispatch(doCheckPendingTxs());
        }
        return res;
      })
      .catch((e) => {
        dispatch({
          type: ACTIONS.ABANDON_CLAIM_SUPPORT_FAILED,
          data: e.message,
        });
      });
  };
}

export function doWalletReconnect() {
  return (dispatch) => {
    dispatch({
      type: ACTIONS.WALLET_RESTART,
    });
    let failed = false;
    // this basically returns null when it's done. :(
    // might be good to  dispatch ACTIONS.WALLET_RESTARTED
    const walletTimeout = setTimeout(() => {
      failed = true;
      dispatch({
        type: ACTIONS.WALLET_RESTART_COMPLETED,
      });
      dispatch(
        doToast({
          message: __('Your servers were not available. Check your url and port, or switch back to defaults.'),
          isError: true,
        })
      );
    }, FIFTEEN_SECONDS);
    Lbry.wallet_reconnect().then(() => {
      clearTimeout(walletTimeout);
      if (!failed) dispatch({ type: ACTIONS.WALLET_RESTART_COMPLETED });
    });
  };
}

export function doWalletDecrypt() {
  return (dispatch) => {
    dispatch({
      type: ACTIONS.WALLET_DECRYPT_START,
    });

    Lbry.wallet_decrypt().then((result) => {
      if (result === true) {
        dispatch({
          type: ACTIONS.WALLET_DECRYPT_COMPLETED,
          result,
        });
      } else {
        dispatch({
          type: ACTIONS.WALLET_DECRYPT_FAILED,
          result,
        });
      }
    });
  };
}

export function doWalletStatus() {
  return (dispatch) => {
    dispatch({
      type: ACTIONS.WALLET_STATUS_START,
    });

    Lbry.wallet_status().then((status) => {
      if (status) {
        dispatch({
          type: ACTIONS.WALLET_STATUS_COMPLETED,
          result: status.is_encrypted,
        });
      }
    });
  };
}

export function doSetTransactionListFilter(filterOption) {
  return {
    type: ACTIONS.SET_TRANSACTION_LIST_FILTER,
    data: filterOption,
  };
}

export function doUpdateBlockHeight() {
  return (dispatch) =>
    Lbry.status().then((status) => {
      if (status.wallet) {
        dispatch({
          type: ACTIONS.UPDATE_CURRENT_HEIGHT,
          data: status.wallet.blocks,
        });
      }
    });
}

// Calls transaction_show on txes until any pending txes are confirmed
export const doCheckPendingTxs = () => (dispatch, getState) => {
  const state = getState();
  const pendingTxsById = selectPendingSupportTransactions(state); // {}
  const pendingOtherTxes = selectPendingOtherTransactions(state);

  if (!Object.keys(pendingTxsById).length && !pendingOtherTxes.length) {
    return;
  }
  let txCheckInterval;
  const checkTxList = () => {
    const state = getState();
    const pendingSupportTxs = selectPendingSupportTransactions(state); // {}
    const pendingConsolidateTxes = selectPendingOtherTransactions(state);
    const pendingConsTxid = selectPendingConsolidateTxid(state);
    const pendingMassCLaimTxid = selectPendingMassClaimTxid(state);

    const promises = [];
    const newPendingTxes = {};
    const noLongerPendingConsolidate = [];
    const types = new Set([]);
    // { claimId: {txid: 123, amount 12.3}, }
    const entries = Object.entries(pendingSupportTxs);
    entries.forEach(([claim, data]) => {
      promises.push(Lbry.transaction_show({ txid: data.txid }));
      types.add(data.type);
    });
    if (pendingConsolidateTxes.length) {
      pendingConsolidateTxes.forEach((txid) => promises.push(Lbry.transaction_show({ txid })));
    }

    Promise.all(promises).then((txShows) => {
      let changed = false;
      txShows.forEach((result) => {
        if (pendingConsolidateTxes.includes(result.txid)) {
          if (result.height > 0) {
            noLongerPendingConsolidate.push(result.txid);
          }
        } else {
          if (result.height <= 0) {
            const match = entries.find((entry) => entry[1].txid === result.txid);
            newPendingTxes[match[0]] = match[1];
          } else {
            changed = true;
          }
        }
      });

      if (changed) {
        dispatch({
          type: ACTIONS.PENDING_SUPPORTS_UPDATED,
          data: newPendingTxes,
        });
        if (types.has('channel')) {
          dispatch(doFetchChannelListMine());
        }
        if (types.has('stream')) {
          dispatch(doFetchClaimListMine());
        }
      }
      if (noLongerPendingConsolidate.length) {
        if (noLongerPendingConsolidate.includes(pendingConsTxid)) {
          dispatch(
            doToast({
              message: __('Your wallet is finished consolidating'),
            })
          );
        }
        if (noLongerPendingConsolidate.includes(pendingMassCLaimTxid)) {
          dispatch(
            doToast({
              message: __('Your tips have been collected'),
            })
          );
        }
        dispatch({
          type: ACTIONS.PENDING_CONSOLIDATED_TXOS_UPDATED,
          data: { txids: noLongerPendingConsolidate, remove: true },
        });
      }

      if (!Object.keys(pendingTxsById).length && !pendingOtherTxes.length) {
        clearInterval(txCheckInterval);
      }
    });
  };

  txCheckInterval = setInterval(() => {
    checkTxList();
  }, 30000);
};