import _ from 'lodash'
import Link from 'next/link'
import clsx from 'clsx'
import { useEffect, useState } from 'react'

import {
  contractMetrics,
  Contract,
  listContracts,
  getBinaryProb,
} from '../lib/firebase/contracts'
import { User } from '../lib/firebase/users'
import { Col } from './layout/col'
import { SiteLink } from './site-link'
import { ContractCard } from './contract-card'
import { Sort, useQueryAndSortParams } from '../hooks/use-sort-and-query-params'
import { Answer } from '../../common/answer'

export function ContractsGrid(props: {
  contracts: Contract[]
  showHotVolume?: boolean
  showCloseTime?: boolean
}) {
  const { showCloseTime } = props

  const [resolvedContracts, activeContracts] = _.partition(
    props.contracts,
    (c) => c.isResolved
  )
  const contracts = [...activeContracts, ...resolvedContracts].slice(
    0,
    MAX_CONTRACTS_DISPLAYED
  )

  if (contracts.length === 0) {
    return (
      <p className="mx-2 text-gray-500">
        No markets found. Why not{' '}
        <SiteLink href="/home" className="font-bold text-gray-700">
          create one?
        </SiteLink>
      </p>
    )
  }

  return (
    <ul className="grid w-full grid-cols-1 gap-6 md:grid-cols-2">
      {contracts.map((contract) => (
        <ContractCard
          contract={contract}
          key={contract.id}
          // showHotVolume={showHotVolume}
          showCloseTime={showCloseTime}
        />
      ))}
    </ul>
  )
}

const MAX_GROUPED_CONTRACTS_DISPLAYED = 6

function CreatorContractsGrid(props: { contracts: Contract[] }) {
  const { contracts } = props

  const byCreator = _.groupBy(contracts, (contract) => contract.creatorId)
  const creator7DayVol = _.mapValues(byCreator, (contracts) =>
    _.sumBy(contracts, (contract) => contract.volume7Days)
  )
  const creatorIds = _.sortBy(
    Object.keys(byCreator),
    (creatorId) => -1 * creator7DayVol[creatorId]
  )

  let numContracts = 0
  let maxIndex = 0
  for (; maxIndex < creatorIds.length; maxIndex++) {
    numContracts += Math.min(
      MAX_GROUPED_CONTRACTS_DISPLAYED,
      byCreator[creatorIds[maxIndex]].length
    )
    if (numContracts > MAX_CONTRACTS_DISPLAYED) break
  }

  const creatorIdsSubset = creatorIds.slice(0, maxIndex)

  return (
    <Col className="gap-6">
      {creatorIdsSubset.map((creatorId) => {
        const { creatorUsername, creatorName } = byCreator[creatorId][0]

        return (
          <Col className="gap-4" key={creatorUsername}>
            <SiteLink className="text-lg" href={`/${creatorUsername}`}>
              {creatorName}
            </SiteLink>

            <ul role="list" className="grid grid-cols-1 gap-4 lg:grid-cols-2">
              {byCreator[creatorId]
                .slice(0, MAX_GROUPED_CONTRACTS_DISPLAYED)
                .map((contract) => (
                  <ContractCard contract={contract} key={contract.id} />
                ))}
            </ul>

            {byCreator[creatorId].length > MAX_GROUPED_CONTRACTS_DISPLAYED ? (
              <Link href={`/${creatorUsername}`}>
                <a
                  className={clsx(
                    'self-end hover:underline hover:decoration-indigo-400 hover:decoration-2'
                  )}
                  onClick={(e) => e.stopPropagation()}
                >
                  See all
                </a>
              </Link>
            ) : (
              <div />
            )}
          </Col>
        )
      })}
    </Col>
  )
}

function TagContractsGrid(props: { contracts: Contract[] }) {
  const { contracts } = props

  const contractTags = _.flatMap(contracts, (contract) => {
    const { tags } = contract
    return tags.map((tag) => ({
      tag,
      contract,
    }))
  })
  const groupedByTag = _.groupBy(contractTags, ({ tag }) => tag)
  const byTag = _.mapValues(groupedByTag, (contractTags) =>
    contractTags.map(({ contract }) => contract)
  )
  const tag7DayVol = _.mapValues(byTag, (contracts) =>
    _.sumBy(contracts, (contract) => contract.volume7Days)
  )
  const tags = _.sortBy(
    Object.keys(byTag),
    (creatorId) => -1 * tag7DayVol[creatorId]
  )

  let numContracts = 0
  let maxIndex = 0
  for (; maxIndex < tags.length; maxIndex++) {
    numContracts += Math.min(
      MAX_GROUPED_CONTRACTS_DISPLAYED,
      byTag[tags[maxIndex]].length
    )
    if (numContracts > MAX_CONTRACTS_DISPLAYED) break
  }

  const tagsSubset = tags.slice(0, maxIndex)

  return (
    <Col className="gap-6">
      {tagsSubset.map((tag) => {
        return (
          <Col className="gap-4" key={tag}>
            <SiteLink className="text-lg" href={`/tag/${tag}`}>
              #{tag}
            </SiteLink>

            <ul role="list" className="grid grid-cols-1 gap-4 lg:grid-cols-2">
              {byTag[tag]
                .slice(0, MAX_GROUPED_CONTRACTS_DISPLAYED)
                .map((contract) => (
                  <ContractCard contract={contract} key={contract.id} />
                ))}
            </ul>

            {byTag[tag].length > MAX_GROUPED_CONTRACTS_DISPLAYED ? (
              <Link href={`/tag/${tag}`}>
                <a
                  className={clsx(
                    'self-end hover:underline hover:decoration-indigo-400 hover:decoration-2'
                  )}
                  onClick={(e) => e.stopPropagation()}
                >
                  See all
                </a>
              </Link>
            ) : (
              <div />
            )}
          </Col>
        )
      })}
    </Col>
  )
}

const MAX_CONTRACTS_DISPLAYED = 99

export function SearchableGrid(props: {
  contracts: Contract[]
  byOneCreator?: boolean
  querySortOptions?: {
    defaultSort: Sort
    shouldLoadFromStorage?: boolean
  }
}) {
  const { contracts, byOneCreator, querySortOptions } = props

  const { query, setQuery, sort, setSort } =
    useQueryAndSortParams(querySortOptions)

  const queryWords = query.toLowerCase().split(' ')
  function check(corpus: String) {
    return queryWords.every((word) => corpus.toLowerCase().includes(word))
  }

  let matches = contracts.filter(
    (c) =>
      check(c.question) ||
      check(c.description) ||
      check(c.creatorName) ||
      check(c.creatorUsername) ||
      check(c.lowercaseTags.map((tag) => `#${tag}`).join(' ')) ||
      check(
        ((c as any).answers ?? [])
          .map((answer: Answer) => answer.text)
          .join(' ')
      )
  )

  if (sort === 'newest' || sort === 'all') {
    matches.sort((a, b) => b.createdTime - a.createdTime)
  } else if (sort === 'resolved') {
    matches = _.sortBy(
      matches,
      (contract) => -1 * (contract.resolutionTime ?? 0)
    )
  } else if (sort === 'oldest') {
    matches.sort((a, b) => a.createdTime - b.createdTime)
  } else if (sort === 'close-date' || sort === 'closed') {
    matches = _.sortBy(matches, ({ volume24Hours }) => -1 * volume24Hours)
    matches = _.sortBy(
      matches,
      (contract) =>
        (sort === 'closed' ? -1 : 1) * (contract.closeTime ?? Infinity)
    )
    const hideClosed = sort === 'closed'
    matches = matches.filter(
      ({ closeTime }) => closeTime && closeTime > Date.now() !== hideClosed
    )
  } else if (sort === 'most-traded') {
    matches.sort((a, b) => b.volume - a.volume)
  } else if (sort === '24-hour-vol') {
    // Use lodash for stable sort, so previous sort breaks all ties.
    matches = _.sortBy(matches, ({ volume7Days }) => -1 * volume7Days)
    matches = _.sortBy(matches, ({ volume24Hours }) => -1 * volume24Hours)
  } else if (sort === 'creator' || sort === 'tag') {
    matches.sort((a, b) => b.volume7Days - a.volume7Days)
  } else if (sort === 'most-likely') {
    matches = _.sortBy(matches, (contract) => -getBinaryProb(contract))
  } else if (sort === 'least-likely') {
    // Exclude non-binary contracts
    matches = matches.filter((contract) => getBinaryProb(contract) !== 0)
    matches = _.sortBy(matches, (contract) => getBinaryProb(contract))
  }

  if (sort !== 'all') {
    // Filter for (or filter out) resolved contracts
    matches = matches.filter((c) =>
      sort === 'resolved' ? c.resolution : !c.resolution
    )

    // Filter out closed contracts.
    if (sort !== 'closed' && sort !== 'resolved') {
      matches = matches.filter((c) => !c.closeTime || c.closeTime > Date.now())
    }
  }

  return (
    <div>
      {/* Show a search input next to a sort dropdown */}
      <div className="mt-2 mb-8 flex justify-between gap-2">
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Search markets"
          className="input input-bordered w-full"
        />
        <select
          className="select select-bordered"
          value={sort}
          onChange={(e) => setSort(e.target.value as Sort)}
        >
          <option value="most-traded">Most traded</option>
          <option value="24-hour-vol">24h volume</option>
          <option value="close-date">Closing soon</option>
          <option value="closed">Closed</option>
          <option value="newest">Newest</option>
          <option value="oldest">Oldest</option>
          <option value="most-likely">Most likely</option>
          <option value="least-likely">Least likely</option>

          <option value="tag">By tag</option>
          {!byOneCreator && <option value="creator">By creator</option>}
          <option value="resolved">Resolved</option>
          {byOneCreator && <option value="all">All markets</option>}
        </select>
      </div>

      {sort === 'tag' ? (
        <TagContractsGrid contracts={matches} />
      ) : !byOneCreator && sort === 'creator' ? (
        <CreatorContractsGrid contracts={matches} />
      ) : (
        <ContractsGrid
          contracts={matches}
          showCloseTime={['close-date', 'closed'].includes(sort)}
        />
      )}
    </div>
  )
}

export function CreatorContractsList(props: { creator: User }) {
  const { creator } = props
  const [contracts, setContracts] = useState<Contract[] | 'loading'>('loading')

  useEffect(() => {
    if (creator?.id) {
      // TODO: stream changes from firestore
      listContracts(creator.id).then(setContracts)
    }
  }, [creator])

  if (contracts === 'loading') return <></>

  return (
    <SearchableGrid
      contracts={contracts}
      byOneCreator
      querySortOptions={{
        defaultSort: 'all',
        shouldLoadFromStorage: false,
      }}
    />
  )
}