import { getMarketByPublicKey } from '@blockworks-foundation/mango-client'
import { PublicKey } from '@solana/web3.js'
import BigNumber from 'bignumber.js'
import { useEffect } from 'react'
import {
  fillsSelector,
  mangoAccountSelector,
  mangoGroupSelector,
  marketConfigSelector,
} from '../stores/selectors'
import useMangoStore from '../stores/useMangoStore'
import { perpContractPrecision } from '../utils'

const byTimestamp = (a, b) => {
  return (
    new Date(b.loadTimestamp || b.timestamp * 1000).getTime() -
    new Date(a.loadTimestamp || a.timestamp * 1000).getTime()
  )
}

const reverseSide = (side) => (side === 'buy' ? 'sell' : 'buy')

function getMarketName(event) {
  const mangoGroupConfig = useMangoStore.getState().selectedMangoGroup.config

  let marketName
  if (!event.marketName && event.address) {
    const marketInfo = getMarketByPublicKey(mangoGroupConfig, event.address)
    marketName = marketInfo.name
  }
  return event.marketName || marketName
}

const parsedPerpEvent = (mangoAccountPk: PublicKey, event) => {
  const maker = event.maker.toString() === mangoAccountPk.toString()
  const orderId = maker ? event.makerOrderId : event.takerOrderId
  const value = event.quantity * event.price
  const feeRate = maker ? event.makerFee : event.takerFee
  const side = maker ? reverseSide(event.takerSide) : event.takerSide
  const marketName = getMarketName(event)
  const sizeDecimalCount = perpContractPrecision[marketName.split('-')[0]]
  const size = new BigNumber(event.quantity).toFormat(sizeDecimalCount)

  return {
    ...event,
    key: orderId?.toString(),
    liquidity: maker ? 'Maker' : 'Taker',
    size,
    price: event.price,
    value,
    feeCost: (feeRate * value).toFixed(4),
    side,
    marketName,
  }
}

const parsedSerumEvent = (event) => {
  let liquidity
  if (event.eventFlags) {
    liquidity = event?.eventFlags?.maker ? 'Maker' : 'Taker'
  } else {
    liquidity = event.maker ? 'Maker' : 'Taker'
  }
  return {
    ...event,
    liquidity,
    key: `${event.maker}-${event.price}`,
    value: event.price * event.size,
    side: event.side,
    marketName: getMarketName(event),
  }
}

const formatTradeHistory = (mangoAccountPk: PublicKey, tradeHistory: any[]) => {
  return tradeHistory
    .flat()
    .map((event) => {
      if (event.eventFlags || event.nativeQuantityPaid) {
        return parsedSerumEvent(event)
      } else if (event.maker) {
        return parsedPerpEvent(mangoAccountPk, event)
      } else {
        return event
      }
    })
    .sort(byTimestamp)
}

export const useTradeHistory = () => {
  const marketConfig = useMangoStore(marketConfigSelector)
  const fills = useMangoStore(fillsSelector)
  const mangoAccount = useMangoStore(mangoAccountSelector)
  const selectedMangoGroup = useMangoStore(mangoGroupSelector)
  const spotTradeHistory = useMangoStore((s) => s.tradeHistory.spot)
  const perpTradeHistory = useMangoStore((s) => s.tradeHistory.perp)
  const setMangoStore = useMangoStore.getState().set

  useEffect(() => {
    if (!mangoAccount || !selectedMangoGroup) return null
    const previousTradeHistory = useMangoStore.getState().tradeHistory.parsed

    // combine the trade histories loaded from the DB
    let tradeHistory = spotTradeHistory.concat(perpTradeHistory)

    const openOrdersAccount =
      mangoAccount.spotOpenOrdersAccounts[marketConfig.marketIndex]

    // Look through the loaded fills from the event queue to see if
    // there are any that match the user's open orders account
    const tradeHistoryFromEventQueue = fills
      .filter((fill) => {
        if (fill.openOrders) {
          // handles serum event queue for spot trades
          return openOrdersAccount?.publicKey
            ? fill.openOrders.equals(openOrdersAccount?.publicKey)
            : false
        } else {
          // handles mango event queue for perp trades
          return (
            fill.taker.equals(mangoAccount.publicKey) ||
            fill.maker.equals(mangoAccount.publicKey)
          )
        }
      })
      .map((fill) => ({ ...fill, marketName: marketConfig.name }))

    // filters the trades from tradeHistoryFromEventQueue to find the ones we don't already have in the DB
    if (tradeHistoryFromEventQueue?.length) {
      const newFills = tradeHistoryFromEventQueue.filter(
        (fill) =>
          !tradeHistory.find((t) => {
            if (t.orderId) {
              return t.orderId === fill.orderId?.toString()
            } else {
              return t.seqNum === fill.seqNum?.toString()
            }
          })
      )
      tradeHistory = [...newFills, ...tradeHistory]
    }

    if (previousTradeHistory.length !== tradeHistory.length) {
      const formattedTradeHistory = formatTradeHistory(
        mangoAccount.publicKey,
        tradeHistory
      )

      setMangoStore((state) => {
        state.tradeHistory.parsed = formattedTradeHistory
      })
    }
  }, [spotTradeHistory, perpTradeHistory, fills, mangoAccount])
}

export default useTradeHistory
