import { IncentivisedVotingLockup__factory } from '@apps/artifacts/typechain'
import { AssetInputSingle, SendButton } from '@apps/base/components/forms'
import { useSigner } from '@apps/base/context/account'
import { useModalData } from '@apps/base/context/modal-data'
import { useNetworkAddresses } from '@apps/base/context/network'
import { useTokenAllowance, useTokenSubscription } from '@apps/base/context/tokens'
import { usePropose } from '@apps/base/context/transactions'
import { Tooltip, Warning } from '@apps/dumb-components'
import { ToggleInput } from '@apps/dumb-components'
import { truncateAddress } from '@apps/formatters'
import { useBigDecimalInput } from '@apps/hooks'
import { TransactionManifest } from '@apps/transaction-manifest'
import { constants } from 'ethers'
import { useToggle } from 'react-use'
import styled from 'styled-components'

import { DelegateSelection } from '../../components/DelegateSelection'
import { useStakingQuery } from '../../context'
import { useStakedToken, useStakedTokenContract, useStakedTokenQuery } from '../../context/StakedToken'
import { useStakingStatus, useStakingStatusDispatch } from '../../context/StakingStatus'
import { TimeMultiplierImpact } from './TimeMultiplierImpact'

import type { Interfaces } from '@apps/transaction-manifest'
import type { FC } from 'react'

const DAY = 86400

interface Props {
  className?: string
  isMigrating?: boolean
}

const StyledDelegateSelection = styled(DelegateSelection)<{ isMigrating: boolean }>`
  background: ${({ theme, isMigrating }) => isMigrating && theme.color.background[1]};
  margin-top: 0.75rem;

  button {
    background: ${({ theme, isMigrating }) => isMigrating && theme.color.background[3]};
  }
`

const Input = styled(AssetInputSingle)<{ isMigrating: boolean }>`
  background: ${({ theme, isMigrating }) => isMigrating && theme.color.background[1]};
`

const DelegateToggle = styled.div`
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: center;

  h3 {
    margin-left: 0.25rem;
    display: flex;
    align-items: center;
  }
`

const Warnings = styled.div`
  background: ${({ theme }) => theme.color.background[0]};
  border: 1px solid ${({ theme }) => theme.color.defaultBorder};
  border-radius: 0.875rem;
  padding: 0.75rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
`

const Container = styled.div`
  display: flex;
  flex-direction: column;
  gap: 1rem;
`

export const StakeForm: FC<Props> = ({ className, isMigrating = false }) => {
  const { data, loading } = useStakedTokenQuery()
  const stakingQuery = useStakingQuery()
  const { selected: stakedTokenAddress } = useStakedToken()
  const networkAddresses = useNetworkAddresses()
  const { delegateSelection: delegate } = useModalData()
  const { hasWithdrawnV1Balance, lockedV1 } = useStakingStatus()
  const { setWithdrewV1Balance } = useStakingStatusDispatch()
  const stakingToken = useTokenSubscription(data?.stakedToken?.stakingToken.address)

  const propose = usePropose()
  const signer = useSigner()
  const stakedTokenContract = useStakedTokenContract()
  const allowance = useTokenAllowance(data?.stakedToken?.stakingToken.address, stakedTokenContract?.address)

  const [amount, formValue, setFormValue] = useBigDecimalInput('0')
  const [isDelegating, toggleIsDelegating] = useToggle(true)

  const cooldown = parseInt(data?.stakedToken?.COOLDOWN_SECONDS) / DAY
  const unstakeWindow = parseInt(data?.stakedToken?.UNSTAKE_WINDOW) / DAY

  const balanceV1 = lockedV1?.value?.balance
  const balanceV2 = data?.stakedToken?.accounts?.[0]?.balance?.rawBD
  const canUserStake =
    ((isDelegating && !!delegate) || !isDelegating) && amount?.exact?.gt(0) && allowance?.exact && amount?.exact?.lte(allowance?.exact)

  const otherStakedToken = useTokenSubscription(
    stakedTokenAddress ? stakingQuery.data?.stakedTokens.find(st => st.id !== stakedTokenAddress)?.id : undefined,
  )
  const stakedInOtherToken = stakingToken?.balance?.exact.eq(0) && otherStakedToken?.balance?.exact.gt(0)

  const handleWithdrawV1 = () => {
    if (!signer || !data || !balanceV1?.simple) return

    propose<Interfaces.IncentivisedVotingLockup, 'exit'>(
      new TransactionManifest(IncentivisedVotingLockup__factory.connect(networkAddresses.vMTA, signer), 'exit', [], {
        present: `Withdrawing from Staking V1`,
        past: `Withdrew from Staking V1`,
      }),
    )

    if (!hasWithdrawnV1Balance) {
      setWithdrewV1Balance()
    }
  }

  const handleDeposit = () => {
    if (!stakedTokenContract || amount.exact.lte(0) || !stakingToken) return

    if (delegate && isDelegating) {
      if (delegate === constants.AddressZero) return
      return propose<Interfaces.StakedToken, 'stake(uint256,address)'>(
        new TransactionManifest(stakedTokenContract, 'stake(uint256,address)', [amount.exact, delegate], {
          present: `Staking ${amount.toFixed(2)} ${stakingToken.symbol} and delegating to ${truncateAddress(delegate)}`,
          past: `Staked ${amount.toFixed(2)} ${stakingToken.symbol} and delegated to ${truncateAddress(delegate)}`,
        }),
      )
    }

    propose<Interfaces.StakedToken, 'stake(uint256)'>(
      new TransactionManifest(stakedTokenContract, 'stake(uint256)', [amount.exact], {
        present: `Staking ${amount.toFixed(2)} ${stakingToken.symbol}`,
        past: `Staked ${amount.toFixed(2)} ${stakingToken.symbol}`,
      }),
    )
  }

  return (
    <Container className={className}>
      <Input
        isFetching={loading}
        token={stakingToken}
        formValue={formValue}
        handleSetMax={setFormValue}
        handleSetAmount={setFormValue}
        spender={stakedTokenAddress}
        stakedBalance={isMigrating ? balanceV1 : undefined}
        isMigrating={isMigrating}
        preferStaked={isMigrating}
      />
      <div>
        <DelegateToggle>
          <h3>
            Delegate voting power <Tooltip tip="Delegating your voting power will enable a vote in absence." />
          </h3>
          <ToggleInput onClick={toggleIsDelegating} checked={isDelegating} />
        </DelegateToggle>
        {isDelegating && <StyledDelegateSelection isMigrating={isMigrating} />}
      </div>
      {!!balanceV2?.simple && <TimeMultiplierImpact isStaking stakeDelta={amount?.exact} />}
      <Warnings>
        {stakedInOtherToken && (
          <Warning highlight>It is generally not advisable to stake in both MTA and BPT because of increased gas costs.</Warning>
        )}
        <Warning>
          Unstaking is subject to a cooldown period of {cooldown} days, followed by a {unstakeWindow} day withdrawable period.
        </Warning>
        <Warning>A redemption fee applies to all withdrawals. The longer you stake, the lower the redemption fee.</Warning>
        <Warning>
          In the event that the mStable protocol requires recollateralisation, you risk getting diluted{' '}
          <a href="https://docs.mstable.org/using-mstable/mta-staking/staking-v2" target="_blank" rel="noopener noreferrer">
            Learn More
          </a>
        </Warning>
      </Warnings>
      {isMigrating ? (
        <div>
          {!hasWithdrawnV1Balance && <SendButton valid={!!balanceV1?.simple} title="Withdraw from V1" handleSend={handleWithdrawV1} />}
          <SendButton valid={canUserStake} title="Stake in V2" handleSend={handleDeposit} />
        </div>
      ) : (
        <SendButton valid title="Stake" handleSend={handleDeposit} />
      )}
    </Container>
  )
}