import { sum, groupBy, sumBy, mapValues } from 'lodash'

import { Bet, NumericBet } from './bet'
import { deductDpmFees, getDpmProbability } from './calculate-dpm'
import {
  DPMContract,
  FreeResponseContract,
  MultipleChoiceContract,
} from './contract'
import { DPM_CREATOR_FEE, DPM_FEES, DPM_PLATFORM_FEE } from './fees'
import { addObjects } from './util/object'

export const getDpmCancelPayouts = (contract: DPMContract, bets: Bet[]) => {
  const { pool } = contract
  const poolTotal = sum(Object.values(pool))

  const betSum = sumBy(bets, (b) => b.amount)

  const payouts = bets.map((bet) => ({
    userId: bet.userId,
    payout: (bet.amount / betSum) * poolTotal,
  }))

  return {
    payouts,
    creatorPayout: 0,
    liquidityPayouts: [],
    collectedFees: contract.collectedFees,
  }
}

export const getDpmStandardPayouts = (
  outcome: string,
  contract: DPMContract,
  bets: Bet[]
) => {
  const winningBets = bets.filter((bet) => bet.outcome === outcome)

  const poolTotal = sum(Object.values(contract.pool))
  const totalShares = sumBy(winningBets, (b) => b.shares)

  const payouts = winningBets.map(({ userId, amount, shares }) => {
    const winnings = (shares / totalShares) * poolTotal
    const profit = winnings - amount

    // profit can be negative if using phantom shares
    const payout = amount + (1 - DPM_FEES) * Math.max(0, profit)
    return { userId, profit, payout }
  })

  const profits = sumBy(payouts, (po) => Math.max(0, po.profit))
  const creatorFee = DPM_CREATOR_FEE * profits
  const platformFee = DPM_PLATFORM_FEE * profits
  const collectedFees = addObjects(contract.collectedFees, {
    creatorFee,
    platformFee,
    liquidityFee: 0,
  })

  return {
    payouts: payouts.map(({ userId, payout }) => ({ userId, payout })),
    creatorPayout: creatorFee,
    liquidityPayouts: [],
    collectedFees,
  }
}

export const getNumericDpmPayouts = (
  outcome: string,
  contract: DPMContract,
  bets: NumericBet[]
) => {
  const totalShares = sumBy(bets, (bet) => bet.allOutcomeShares[outcome] ?? 0)
  const winningBets = bets.filter((bet) => !!bet.allOutcomeShares[outcome])

  const poolTotal = sum(Object.values(contract.pool))

  const payouts = winningBets.map(
    ({ userId, allBetAmounts, allOutcomeShares }) => {
      const shares = allOutcomeShares[outcome] ?? 0
      const winnings = (shares / totalShares) * poolTotal

      const amount = allBetAmounts[outcome] ?? 0
      const profit = winnings - amount

      // profit can be negative if using phantom shares
      const payout = amount + (1 - DPM_FEES) * Math.max(0, profit)
      return { userId, profit, payout }
    }
  )

  const profits = sumBy(payouts, (po) => Math.max(0, po.profit))
  const creatorFee = DPM_CREATOR_FEE * profits
  const platformFee = DPM_PLATFORM_FEE * profits
  const collectedFees = addObjects(contract.collectedFees, {
    creatorFee,
    platformFee,
    liquidityFee: 0,
  })

  return {
    payouts: payouts.map(({ userId, payout }) => ({ userId, payout })),
    creatorPayout: creatorFee,
    liquidityPayouts: [],
    collectedFees,
  }
}

export const getDpmMktPayouts = (
  contract: DPMContract,
  bets: Bet[],
  resolutionProbability?: number
) => {
  const p =
    resolutionProbability === undefined
      ? getDpmProbability(contract.totalShares)
      : resolutionProbability

  const weightedShareTotal = sumBy(bets, (b) =>
    b.outcome === 'YES' ? p * b.shares : (1 - p) * b.shares
  )

  const pool = contract.pool.YES + contract.pool.NO

  const payouts = bets.map(({ userId, outcome, amount, shares }) => {
    const betP = outcome === 'YES' ? p : 1 - p
    const winnings = ((betP * shares) / weightedShareTotal) * pool
    const profit = winnings - amount
    const payout = deductDpmFees(amount, winnings)
    return { userId, profit, payout }
  })

  const profits = sumBy(payouts, (po) => Math.max(0, po.profit))

  const creatorFee = DPM_CREATOR_FEE * profits
  const platformFee = DPM_PLATFORM_FEE * profits
  const collectedFees = addObjects(contract.collectedFees, {
    creatorFee,
    platformFee,
    liquidityFee: 0,
  })

  return {
    payouts: payouts.map(({ userId, payout }) => ({ userId, payout })),
    creatorPayout: creatorFee,
    liquidityPayouts: [],
    collectedFees,
  }
}

export const getPayoutsMultiOutcome = (
  resolutions: { [outcome: string]: number },
  contract: FreeResponseContract | MultipleChoiceContract,
  bets: Bet[]
) => {
  const poolTotal = sum(Object.values(contract.pool))
  const winningBets = bets.filter((bet) => resolutions[bet.outcome])

  const betsByOutcome = groupBy(winningBets, (bet) => bet.outcome)
  const sharesByOutcome = mapValues(betsByOutcome, (bets) =>
    sumBy(bets, (bet) => bet.shares)
  )

  const probTotal = sum(Object.values(resolutions))

  const payouts = winningBets.map(({ userId, outcome, amount, shares }) => {
    const prob = resolutions[outcome] / probTotal
    const winnings = (shares / sharesByOutcome[outcome]) * prob * poolTotal
    const profit = winnings - amount

    const payout = amount + (1 - DPM_FEES) * profit
    return { userId, profit, payout }
  })

  const profits = sumBy(payouts, (po) => po.profit)

  const creatorFee = DPM_CREATOR_FEE * profits
  const platformFee = DPM_PLATFORM_FEE * profits
  const collectedFees = addObjects(contract.collectedFees, {
    creatorFee,
    platformFee,
    liquidityFee: 0,
  })

  return {
    payouts: payouts.map(({ userId, payout }) => ({ userId, payout })),
    creatorPayout: creatorFee,
    liquidityPayouts: [],
    collectedFees,
  }
}