import { VictoryAxis, VictoryChart, VictoryLine, VictoryScatter } from 'victory'
import { useContext, useEffect, useState } from 'react'
import createDpaLib from 'dp-auctions-lib'
import useSWR from 'swr'
import useTranslation from 'next-translate/useTranslation'
import { useWeb3React } from '@web3-react/core'
import watchAsset from 'wallet-watch-asset'

import TransactionsContext, {
  TransactionsContextProvider
} from '../../../components/context/Transactions'
import Button from '../../../components/Button'
import { EtherscanLink } from '../../../components/EtherscanLink'
import Layout from '../../../components/Layout'
import TokenAmount from '../../../components/TokenAmount'
import Transactions from '../../../components/Transactions'
import fetchJson from '../../../utils/fetch-json'
import { fromUnit } from '../../../utils'
import ssDpa from '../../../utils/dp-auctions'

const ETH_BLOCK_TIME = 13 // Average block time in Ethereum

const numberFromUnit = (number, decimals) =>
  Number.parseFloat(fromUnit(number, decimals))

// The price chart shows the evolution of the price and the current or ending
// point of the auction, depending on the state.
//
// There are 6 possible states:
//
//   [running, won, stopped] x [descending, floored]
//
// When still descending, the chart looks like this:
//
//   \
//    .
//     \
//
// When floored, the chart looks like this:
//
//   \_.
//
// In addition, when running, the current state is shown as a circle while when
// stopped or won is shown as a full disc.
const DPAuctionPriceChart = function ({ auction }) {
  const { t } = useTranslation('common')

  const startPoint = {
    block: auction.startBlock,
    price: auction.ceiling
  }
  const endPoint = {
    block: auction.endBlock,
    price: auction.floor
  }
  const currentPoint = {
    block: auction.currentBlock,
    price: auction.currentPrice
  }
  const winningPoint = {
    block: auction.winningBlock,
    price: auction.winningPrice
  }
  const stoppingPoint = {
    block: auction.stoppingBlock,
    price: auction.stoppingPrice
  }

  const basePlotData =
    auction.status === 'running'
      ? [startPoint, currentPoint, endPoint]
      : auction.status === 'stopped'
      ? [startPoint, stoppingPoint, endPoint]
      : auction.status === 'won'
      ? [startPoint, winningPoint, endPoint]
      : [startPoint, currentPoint, endPoint]

  const plotData = basePlotData
    .map(({ block, price }) => ({
      block: Number.parseInt(block),
      price: numberFromUnit(price, auction.paymentToken.decimals)
    }))
    .sort((a, b) => a.block - b.block)

  // Calculating the x-axis ticks manually prevents x-labels to overlap, to
  // repeat or to show decimal block numbers. And since the auctions can be live
  // for many blocks or just a few, black math magic is required.
  //
  // First, start by defining the start, end blocks and the domain length.
  const xStart = plotData[0].block
  const xEnd = plotData[2].block
  const xLen = xEnd - xStart
  // Then split the domain length in 3 to have at most 4 ticks. Since the chart
  // is relatively small and the block numbers are large, having just a few
  // ticks is ok.
  // Finally force the steps to be a whole number and force it to be at least 1.
  const xStep = Math.max(Math.floor(xLen / 3), 1)
  // Once the steps are defined, calculate how many ticks fit in the domain. Sum
  // one to add the "ending" tick. Otherwise only the start and "inner" ticks
  // will be shown.
  const xTicks = Math.floor(xLen / xStep) + 1
  // Finally create an array of that length whose values will be one step
  // appart. To get a better look, start from the end, subtract one step at a
  // time and then revert the array. That way the end tick will always match the
  // end block.
  const xTickValues = new Array(Math.max(xTicks, 1))
    .fill(null)
    .map((_, i) => xEnd - xStep * i)
    .reverse()

  return (
    <div>
      <VictoryChart
        minDomain={{ y: 0 }}
        padding={{ bottom: 55, left: 90, right: 30, top: 10 }}
        width={450}
      >
        <VictoryAxis
          label={t('block-number')}
          style={{
            axisLabel: { padding: 40 },
            ticks: { stroke: 'black', size: 5 }
          }}
          tickFormat={tick => tick.toString()}
          tickValues={xTickValues}
        />
        <VictoryAxis
          dependentAxis
          label={auction.paymentToken.symbol}
          style={{
            axisLabel: { padding: 75 },
            ticks: { stroke: 'black', size: 5 }
          }}
        />
        <VictoryLine
          data={plotData.slice(0, 2)}
          style={{
            data: { strokeWidth: 2 }
          }}
          x="block"
          y="price"
        />
        <VictoryLine
          data={plotData.slice(1)}
          style={{
            data:
              auction.status === 'floored' ||
              auction.winningPrice === auction.floor ||
              auction.stoppingPrice === auction.floor
                ? { strokeWidth: 3 }
                : { strokeWidth: 1, strokeDasharray: '10,10' }
          }}
          x="block"
          y="price"
        />
        <VictoryScatter
          data={[
            plotData[
              auction.status === 'floored' ||
              auction.winningPrice === auction.floor ||
              auction.stoppingPrice === auction.floor
                ? 2
                : 1
            ]
          ]}
          size={8}
          style={{
            data: {
              strokeWidth: 1,
              fill: auction.stopped ? 'black' : 'white',
              stroke: 'black'
            }
          }}
          x="block"
          y="price"
        />
      </VictoryChart>
    </div>
  )
}

const DPAuctionContentsRow = ({ paymentToken, token }) => (
  <tr>
    <td className="border-2">
      <TokenAmount {...token} />
    </td>
    <td className="border-2">
      <TokenAmount {...paymentToken} amount={token.value} />
    </td>
  </tr>
)

const DPAuctionTokens = function ({ auction }) {
  const { t } = useTranslation('common')

  return (
    <table className="w-full border-collapse">
      <thead>
        <tr className="font-bold bg-gray-200">
          <td className="border-2">{t('token')}</td>
          <td className="border-2">{t('value')}</td>
        </tr>
      </thead>
      <tbody>
        {auction.tokens.map(token => (
          <DPAuctionContentsRow
            key={token.address}
            paymentToken={auction.paymentToken}
            token={token}
          />
        ))}
      </tbody>
      <tfoot>
        <tr className="bg-gray-200">
          <td className="border-2">{t('total')}</td>
          <td className="border-2">
            <TokenAmount
              {...auction.paymentToken}
              amount={auction.currentValue}
            />
          </td>
        </tr>
      </tfoot>
    </table>
  )
}

const DPAuctionBuyControl = function ({ auction }) {
  const { t } = useTranslation('common')
  const { account, active, library: web3 } = useWeb3React()
  const { addTransactionStatus } = useContext(TransactionsContext)

  const [dpa, setDpa] = useState(null)
  useEffect(
    function () {
      setDpa(active && web3 ? createDpaLib(web3) : null)
    },
    [active, web3]
  )

  const [canBid, setCanBid] = useState(false)
  useEffect(
    function () {
      if (!dpa || account === auction.payee) {
        setCanBid(false)
        return
      }
      // eslint-disable-next-line promise/catch-or-return
      dpa
        .canBidAuction(account, auction.id)
        .catch(function () {
          console.warn('Could not check if user can bid')
          return false
        })
        .then(setCanBid)
    },
    [dpa, account, auction]
  )

  const handleBuyAuctionClick = function () {
    const opId = Date.now()

    const { emitter } = dpa.bidAuction(auction.id, { from: account })

    emitter
      .on('transactions', function (transactions) {
        addTransactionStatus({
          expectedFee: fromUnit(transactions.expectedFee),
          operation: 'bid',
          opId,
          received: auction.tokens.map(token => ({
            value: fromUnit(token.amount, token.decilams),
            symbol: token.symbol
          })),
          sent: fromUnit(auction.currentPrice, auction.paymentToken.decimals),
          sentSymbol: auction.paymentToken.symbol,
          suffixes: transactions.suffixes,
          transactionStatus: 'created'
        })
        transactions.suffixes.forEach(function (suffix, i) {
          emitter.on(`transactionHash-${suffix}`, function (transactionHash) {
            addTransactionStatus({
              opId,
              transactionStatus: 'in-progress',
              [`transactionHash-${i}`]: transactionHash,
              [`transactionStatus-${i}`]: 'waiting-to-be-mined'
            })
          })
          emitter.on(`receipt-${suffix}`, function ({ receipt }) {
            addTransactionStatus({
              currentTransaction: i + 1,
              opId,
              [`transactionHash-${i}`]: receipt.transactionHash,
              [`transactionStatus-${i}`]: receipt.status
                ? 'confirmed'
                : 'canceled'
            })
          })
        })
      })
      .on('result', function ({ fees, status, price }) {
        addTransactionStatus({
          actualFee: fromUnit(fees),
          opId,
          transactionStatus: status ? 'confirmed' : 'canceled',
          sent: fromUnit(price, auction.paymentToken.decimals)
        })
        auction.tokens.forEach(function (token) {
          watchAsset({ account, token })
        })
      })
      .on('error', function (err) {
        addTransactionStatus({
          message: err.message,
          opId,
          transactionStatus: 'error'
        })
      })
  }

  return (
    <div className="text-xl">
      {!auction.stopped ? (
        <>
          <div className="w-full">
            <div>{t('current-price')}:</div>
            <div className="font-bold">
              <TokenAmount
                amount={auction.currentPrice}
                {...auction.paymentToken}
              />
            </div>
            <div className="text-sm">
              (
              {(
                (100n * BigInt(auction.currentPrice)) /
                BigInt(auction.currentValue)
              ).toString()}
              % {t('of-value')})
            </div>
          </div>
          <div className="mt-4 w-full">
            <Button disabled={!canBid} onClick={handleBuyAuctionClick}>
              {t('buy-auction')}
            </Button>
          </div>
        </>
      ) : (
        <span>{t('auction-ended')}</span>
      )}
    </div>
  )
}

// This component shows the end status of the auction.
const DPAuctionEndStatus = function ({ auction }) {
  const { t } = useTranslation('common')

  return auction.status === 'won' ? (
    <>
      <div>
        {t('won-by')}: <EtherscanLink address={auction.winner} />
      </div>
      <div>
        {t('winning-price')}:{' '}
        <TokenAmount {...auction.paymentToken} amount={auction.winningPrice} />
      </div>
    </>
  ) : auction.status === 'stopped' ? (
    <div>{t('auction-stopped')}</div>
  ) : null
}

// This component renders the details view of an auction.
const DPAuction = function ({ auction }) {
  const { t } = useTranslation('common')

  return (
    <>
      <div className="flex">
        <div className="mr-4 w-1/2">
          <DPAuctionPriceChart auction={auction} />
        </div>
        <div className="ml-4 w-1/2">
          <div className="mb-2">
            {t('seller')}: <EtherscanLink address={auction.payee} />
          </div>
          <DPAuctionTokens auction={auction} />
        </div>
      </div>
      <div className="flex mt-8">
        <div className="mr-4 w-1/2">
          <DPAuctionBuyControl auction={auction} />
        </div>
        <div className="mr-4 w-1/2">
          <DPAuctionEndStatus auction={auction} />
        </div>
      </div>
    </>
  )
}

// This is the main app component. It holds all the views like the auctions
// list, the auction detail, etc.
export default function DPAuctionsDetails({ auctionId, initialData, error }) {
  const { t } = useTranslation('common')

  const { data: auction } = useSWR(
    `/api/dp-auctions/auctions/${auctionId}`,
    fetchJson,
    { fallbackData: initialData, refreshInterval: ETH_BLOCK_TIME * 1000 }
  )

  return (
    <TransactionsContextProvider>
      <Layout title={t('dp-auctions')} walletConnection>
        <div className="mt-10 w-full">
          <div className="mb-1.5 text-gray-600 font-bold">
            {t('auction')} {auctionId}
          </div>
          {auction ? (
            <DPAuction auction={auction} />
          ) : (
            <>
              <div>{t('error-getting-auction')}:</div>
              <div>{error}</div>
            </>
          )}
        </div>
        <Transactions />
      </Layout>
    </TransactionsContextProvider>
  )
}

// Gather the `auctionId` from the path, get the auction data and send it as a
// prop to the main component. Rebuild the page in the server aprox. on every
// block (15 seconds).
export const getStaticProps = ({ params }) =>
  ssDpa
    .getAuction(params.auctionId, true)
    .then(initialData => ({
      notFound: !initialData,
      props: {
        auctionId: params.auctionId,
        initialData
      },
      revalidate: ETH_BLOCK_TIME
    }))
    .catch(err => ({
      props: {
        auctionId: params.auctionId,
        error: err.message
      }
    }))

// Do not statically render any auction page.
export const getStaticPaths = () => ({
  paths: [],
  fallback: 'blocking'
})