import { Bet } from 'common/bet'
import { Comment } from 'common/comment'
import { User } from 'common/user'
import { Contract } from 'common/contract'
import React, { useEffect, useState } from 'react'
import { minBy, maxBy, groupBy, partition, sumBy, Dictionary } from 'lodash'
import { useUser } from 'web/hooks/use-user'
import { formatMoney } from 'common/util/format'
import { useRouter } from 'next/router'
import { Row } from 'web/components/layout/row'
import clsx from 'clsx'
import { Avatar } from 'web/components/avatar'
import { UserLink } from 'web/components/user-page'
import { OutcomeLabel } from 'web/components/outcome-label'
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
import { contractPath } from 'web/lib/firebase/contracts'
import { firebaseLogin } from 'web/lib/firebase/users'
import {
  createCommentOnContract,
  MAX_COMMENT_LENGTH,
} from 'web/lib/firebase/comments'
import Textarea from 'react-expanding-textarea'
import { Linkify } from 'web/components/linkify'
import { SiteLink } from 'web/components/site-link'
import { BetStatusText } from 'web/components/feed/feed-bets'
import { Col } from 'web/components/layout/col'
import { getProbability } from 'common/calculate'
import { LoadingIndicator } from 'web/components/loading-indicator'
import { PaperAirplaneIcon } from '@heroicons/react/outline'
import { track } from 'web/lib/service/analytics'
import { useEvent } from 'web/hooks/use-event'
import { Tipper } from '../tipper'
import { CommentTipMap, CommentTips } from 'web/hooks/use-tip-txns'
import { useWindowSize } from 'web/hooks/use-window-size'

export function FeedCommentThread(props: {
  contract: Contract
  comments: Comment[]
  tips: CommentTipMap
  parentComment: Comment
  bets: Bet[]
  truncate?: boolean
  smallAvatar?: boolean
}) {
  const {
    contract,
    comments,
    bets,
    tips,
    truncate,
    smallAvatar,
    parentComment,
  } = props
  const [showReply, setShowReply] = useState(false)
  const [replyToUsername, setReplyToUsername] = useState('')
  const betsByUserId = groupBy(bets, (bet) => bet.userId)
  const user = useUser()
  const commentsList = comments.filter(
    (comment) =>
      parentComment.id && comment.replyToCommentId === parentComment.id
  )
  commentsList.unshift(parentComment)
  const [inputRef, setInputRef] = useState<HTMLTextAreaElement | null>(null)
  function scrollAndOpenReplyInput(comment: Comment) {
    setReplyToUsername(comment.userUsername)
    setShowReply(true)
    inputRef?.focus()
  }
  useEffect(() => {
    if (showReply && inputRef) inputRef.focus()
  }, [inputRef, showReply])
  return (
    <div className={'w-full flex-col pr-1'}>
      <span
        className="absolute top-5 left-5 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200"
        aria-hidden="true"
      />
      <CommentRepliesList
        contract={contract}
        commentsList={commentsList}
        betsByUserId={betsByUserId}
        tips={tips}
        smallAvatar={smallAvatar}
        truncate={truncate}
        bets={bets}
        scrollAndOpenReplyInput={scrollAndOpenReplyInput}
      />
      {showReply && (
        <div className={'-pb-2 ml-6 flex flex-col pt-5'}>
          <span
            className="absolute -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
            aria-hidden="true"
          />
          <CommentInput
            contract={contract}
            betsByCurrentUser={(user && betsByUserId[user.id]) ?? []}
            commentsByCurrentUser={comments.filter(
              (c) => c.userId === user?.id
            )}
            parentCommentId={parentComment.id}
            replyToUsername={replyToUsername}
            parentAnswerOutcome={comments[0].answerOutcome}
            setRef={setInputRef}
            onSubmitComment={() => {
              setShowReply(false)
              setReplyToUsername('')
            }}
          />
        </div>
      )}
    </div>
  )
}

export function CommentRepliesList(props: {
  contract: Contract
  commentsList: Comment[]
  betsByUserId: Dictionary<Bet[]>
  tips: CommentTipMap
  scrollAndOpenReplyInput: (comment: Comment) => void
  bets: Bet[]
  treatFirstIndexEqually?: boolean
  smallAvatar?: boolean
  truncate?: boolean
}) {
  const {
    contract,
    commentsList,
    betsByUserId,
    tips,
    truncate,
    smallAvatar,
    bets,
    scrollAndOpenReplyInput,
    treatFirstIndexEqually,
  } = props
  return (
    <>
      {commentsList.map((comment, commentIdx) => (
        <div
          key={comment.id}
          id={comment.id}
          className={clsx(
            'relative',
            !treatFirstIndexEqually && commentIdx === 0 ? '' : 'mt-3 ml-6'
          )}
        >
          {/*draw a gray line from the comment to the left:*/}
          {(treatFirstIndexEqually || commentIdx != 0) && (
            <span
              className="absolute -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
              aria-hidden="true"
            />
          )}
          <FeedComment
            contract={contract}
            comment={comment}
            tips={tips[comment.id]}
            betsBySameUser={betsByUserId[comment.userId] ?? []}
            onReplyClick={scrollAndOpenReplyInput}
            probAtCreatedTime={
              contract.outcomeType === 'BINARY'
                ? minBy(bets, (bet) => {
                    return bet.createdTime < comment.createdTime
                      ? comment.createdTime - bet.createdTime
                      : comment.createdTime
                  })?.probAfter
                : undefined
            }
            smallAvatar={smallAvatar}
            truncate={truncate}
          />
        </div>
      ))}
    </>
  )
}

export function FeedComment(props: {
  contract: Contract
  comment: Comment
  tips: CommentTips
  betsBySameUser: Bet[]
  probAtCreatedTime?: number
  truncate?: boolean
  smallAvatar?: boolean
  onReplyClick?: (comment: Comment) => void
}) {
  const {
    contract,
    comment,
    tips,
    betsBySameUser,
    probAtCreatedTime,
    truncate,
    onReplyClick,
  } = props
  const { text, userUsername, userName, userAvatarUrl, createdTime } = comment
  let betOutcome: string | undefined,
    bought: string | undefined,
    money: string | undefined

  const matchedBet = betsBySameUser.find((bet) => bet.id === comment.betId)
  if (matchedBet) {
    betOutcome = matchedBet.outcome
    bought = matchedBet.amount >= 0 ? 'bought' : 'sold'
    money = formatMoney(Math.abs(matchedBet.amount))
  }

  const [highlighted, setHighlighted] = useState(false)
  const router = useRouter()
  useEffect(() => {
    if (router.asPath.endsWith(`#${comment.id}`)) {
      setHighlighted(true)
    }
  }, [comment.id, router.asPath])

  // Only calculated if they don't have a matching bet
  const { userPosition, outcome } = getBettorsLargestPositionBeforeTime(
    contract,
    comment.createdTime,
    matchedBet ? [] : betsBySameUser
  )

  return (
    <Row
      className={clsx(
        'flex space-x-1.5 sm:space-x-3',
        highlighted ? `-m-1 rounded bg-indigo-500/[0.2] p-2` : ''
      )}
    >
      <Avatar
        className={'ml-1'}
        size={'sm'}
        username={userUsername}
        avatarUrl={userAvatarUrl}
      />
      <div className="min-w-0 flex-1">
        <div className="mt-0.5 pl-0.5 text-sm text-gray-500">
          <UserLink
            className="text-gray-500"
            username={userUsername}
            name={userName}
          />{' '}
          {!matchedBet &&
            userPosition > 0 &&
            contract.outcomeType !== 'NUMERIC' && (
              <>
                {'is '}
                <CommentStatus
                  prob={probAtCreatedTime}
                  outcome={outcome}
                  contract={contract}
                />
              </>
            )}
          <>
            {bought} {money}
            {contract.outcomeType !== 'FREE_RESPONSE' && betOutcome && (
              <>
                {' '}
                of{' '}
                <OutcomeLabel
                  outcome={betOutcome ? betOutcome : ''}
                  value={(matchedBet as any).value}
                  contract={contract}
                  truncate="short"
                />
              </>
            )}
          </>
          <CopyLinkDateTimeComponent
            prefix={contract.creatorUsername}
            slug={contract.slug}
            createdTime={createdTime}
            elementId={comment.id}
          />
        </div>
        <TruncatedComment
          comment={text}
          moreHref={contractPath(contract)}
          shouldTruncate={truncate}
        />
        <Row className="mt-2 items-center gap-6 text-xs text-gray-500">
          <Tipper comment={comment} tips={tips ?? {}} />
          {onReplyClick && (
            <button
              className="font-bold hover:underline"
              onClick={() => onReplyClick(comment)}
            >
              Reply
            </button>
          )}
        </Row>
      </div>
    </Row>
  )
}

export function getMostRecentCommentableBet(
  betsByCurrentUser: Bet[],
  commentsByCurrentUser: Comment[],
  user?: User | null,
  answerOutcome?: string
) {
  let sortedBetsByCurrentUser = betsByCurrentUser.sort(
    (a, b) => b.createdTime - a.createdTime
  )
  if (answerOutcome) {
    sortedBetsByCurrentUser = sortedBetsByCurrentUser.slice(0, 1)
  }
  return sortedBetsByCurrentUser
    .filter((bet) => {
      if (
        canCommentOnBet(bet, user) &&
        !commentsByCurrentUser.some(
          (comment) => comment.createdTime > bet.createdTime
        )
      ) {
        if (!answerOutcome) return true
        return answerOutcome === bet.outcome
      }
      return false
    })
    .pop()
}

function CommentStatus(props: {
  contract: Contract
  outcome: string
  prob?: number
}) {
  const { contract, outcome, prob } = props
  return (
    <>
      {' betting '}
      <OutcomeLabel outcome={outcome} contract={contract} truncate="short" />
      {prob && ' at ' + Math.round(prob * 100) + '%'}
    </>
  )
}

//TODO: move commentinput and comment input text area into their own files
export function CommentInput(props: {
  contract: Contract
  betsByCurrentUser: Bet[]
  commentsByCurrentUser: Comment[]
  replyToUsername?: string
  setRef?: (ref: HTMLTextAreaElement) => void
  // Reply to a free response answer
  parentAnswerOutcome?: string
  // Reply to another comment
  parentCommentId?: string
  onSubmitComment?: () => void
}) {
  const {
    contract,
    betsByCurrentUser,
    commentsByCurrentUser,
    parentAnswerOutcome,
    parentCommentId,
    replyToUsername,
    onSubmitComment,
    setRef,
  } = props
  const user = useUser()
  const [comment, setComment] = useState('')
  const [isSubmitting, setIsSubmitting] = useState(false)

  const mostRecentCommentableBet = getMostRecentCommentableBet(
    betsByCurrentUser,
    commentsByCurrentUser,
    user,
    parentAnswerOutcome
  )
  const { id } = mostRecentCommentableBet || { id: undefined }

  async function submitComment(betId: string | undefined) {
    if (!user) {
      track('sign in to comment')
      return await firebaseLogin()
    }
    if (!comment || isSubmitting) return
    setIsSubmitting(true)
    await createCommentOnContract(
      contract.id,
      comment,
      user,
      betId,
      parentAnswerOutcome,
      parentCommentId
    )
    onSubmitComment?.()
    setComment('')
    setIsSubmitting(false)
  }

  const { userPosition, outcome } = getBettorsLargestPositionBeforeTime(
    contract,
    Date.now(),
    betsByCurrentUser
  )

  const isNumeric = contract.outcomeType === 'NUMERIC'

  return (
    <>
      <Row className={'mb-2 gap-1 sm:gap-2'}>
        <div className={'mt-2'}>
          <Avatar
            avatarUrl={user?.avatarUrl}
            username={user?.username}
            size={'sm'}
            className={'ml-1'}
          />
        </div>
        <div className={'min-w-0 flex-1'}>
          <div className="pl-0.5 text-sm text-gray-500">
            <div className={'mb-1'}>
              {mostRecentCommentableBet && (
                <BetStatusText
                  contract={contract}
                  bet={mostRecentCommentableBet}
                  isSelf={true}
                  hideOutcome={
                    isNumeric || contract.outcomeType === 'FREE_RESPONSE'
                  }
                />
              )}
              {!mostRecentCommentableBet &&
                user &&
                userPosition > 0 &&
                !isNumeric && (
                  <>
                    {"You're"}
                    <CommentStatus
                      outcome={outcome}
                      contract={contract}
                      prob={
                        contract.outcomeType === 'BINARY'
                          ? getProbability(contract)
                          : undefined
                      }
                    />
                  </>
                )}
            </div>
            <CommentInputTextArea
              commentText={comment}
              setComment={setComment}
              isReply={!!parentCommentId || !!parentAnswerOutcome}
              replyToUsername={replyToUsername ?? ''}
              user={user}
              submitComment={submitComment}
              isSubmitting={isSubmitting}
              setRef={setRef}
              presetId={id}
            />
          </div>
        </div>
      </Row>
    </>
  )
}

export function CommentInputTextArea(props: {
  user: User | undefined | null
  isReply: boolean
  replyToUsername: string
  commentText: string
  setComment: (text: string) => void
  submitComment: (id?: string) => void
  isSubmitting: boolean
  setRef?: (ref: HTMLTextAreaElement) => void
  presetId?: string
  enterToSubmitOnDesktop?: boolean
}) {
  const {
    isReply,
    setRef,
    user,
    commentText,
    setComment,
    submitComment,
    presetId,
    isSubmitting,
    replyToUsername,
    enterToSubmitOnDesktop,
  } = props
  const { width } = useWindowSize()
  const memoizedSetComment = useEvent(setComment)
  useEffect(() => {
    if (!replyToUsername || !user || replyToUsername === user.username) return
    const replacement = `@${replyToUsername} `
    memoizedSetComment(replacement + commentText.replace(replacement, ''))
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [user, replyToUsername, memoizedSetComment])
  return (
    <>
      <Row className="gap-1.5 text-gray-700">
        <Textarea
          ref={setRef}
          value={commentText}
          onChange={(e) => setComment(e.target.value)}
          className={clsx('textarea textarea-bordered w-full resize-none')}
          // Make room for floating submit button.
          style={{ paddingRight: 48 }}
          placeholder={
            isReply
              ? 'Write a reply... '
              : enterToSubmitOnDesktop
              ? 'Send a message'
              : 'Write a comment...'
          }
          autoFocus={false}
          maxLength={MAX_COMMENT_LENGTH}
          disabled={isSubmitting}
          onKeyDown={(e) => {
            if (
              (enterToSubmitOnDesktop &&
                e.key === 'Enter' &&
                !e.shiftKey &&
                width &&
                width > 768) ||
              (e.key === 'Enter' && (e.ctrlKey || e.metaKey))
            ) {
              e.preventDefault()
              submitComment(presetId)
              e.currentTarget.blur()
            }
          }}
        />

        <Col className={clsx('relative justify-end')}>
          {user && !isSubmitting && (
            <button
              className={clsx(
                'btn btn-ghost btn-sm absolute right-2 bottom-2 flex-row pl-2 capitalize',
                !commentText && 'pointer-events-none text-gray-500'
              )}
              onClick={() => {
                submitComment(presetId)
              }}
            >
              <PaperAirplaneIcon
                className={'m-0 min-w-[22px] rotate-90 p-0 '}
                height={25}
              />
            </button>
          )}
          {isSubmitting && (
            <LoadingIndicator spinnerClassName={'border-gray-500'} />
          )}
        </Col>
      </Row>
      <Row>
        {!user && (
          <button
            className={'btn btn-outline btn-sm mt-2 normal-case'}
            onClick={() => submitComment(presetId)}
          >
            Sign in to comment
          </button>
        )}
      </Row>
    </>
  )
}

export function TruncatedComment(props: {
  comment: string
  moreHref: string
  shouldTruncate?: boolean
}) {
  const { comment, moreHref, shouldTruncate } = props
  let truncated = comment

  // Keep descriptions to at most 400 characters
  const MAX_CHARS = 400
  if (shouldTruncate && truncated.length > MAX_CHARS) {
    truncated = truncated.slice(0, MAX_CHARS)
    // Make sure to end on a space
    const i = truncated.lastIndexOf(' ')
    truncated = truncated.slice(0, i)
  }

  return (
    <div
      className="mt-2 whitespace-pre-line break-words text-gray-700"
      style={{ fontSize: 15 }}
    >
      <Linkify text={truncated} />
      {truncated != comment && (
        <SiteLink href={moreHref} className="text-indigo-700">
          ... (show more)
        </SiteLink>
      )}
    </div>
  )
}

function getBettorsLargestPositionBeforeTime(
  contract: Contract,
  createdTime: number,
  bets: Bet[]
) {
  let yesFloorShares = 0,
    yesShares = 0,
    noShares = 0,
    noFloorShares = 0

  const emptyReturn = {
    userPosition: 0,
    outcome: '',
  }
  const previousBets = bets.filter(
    (prevBet) => prevBet.createdTime < createdTime && !prevBet.isAnte
  )

  if (contract.outcomeType === 'FREE_RESPONSE') {
    const answerCounts: { [outcome: string]: number } = {}
    for (const bet of previousBets) {
      if (bet.outcome) {
        if (!answerCounts[bet.outcome]) {
          answerCounts[bet.outcome] = bet.amount
        } else {
          answerCounts[bet.outcome] += bet.amount
        }
      }
    }
    const majorityAnswer =
      maxBy(Object.keys(answerCounts), (outcome) => answerCounts[outcome]) ?? ''
    return {
      userPosition: answerCounts[majorityAnswer] || 0,
      outcome: majorityAnswer,
    }
  }
  if (bets.length === 0) {
    return emptyReturn
  }

  const [yesBets, noBets] = partition(
    previousBets ?? [],
    (bet) => bet.outcome === 'YES'
  )
  yesShares = sumBy(yesBets, (bet) => bet.shares)
  noShares = sumBy(noBets, (bet) => bet.shares)
  yesFloorShares = Math.floor(yesShares)
  noFloorShares = Math.floor(noShares)

  const userPosition = yesFloorShares || noFloorShares
  const outcome = yesFloorShares > noFloorShares ? 'YES' : 'NO'
  return { userPosition, outcome }
}

function canCommentOnBet(bet: Bet, user?: User | null) {
  const { userId, createdTime, isRedemption } = bet
  const isSelf = user?.id === userId
  // You can comment if your bet was posted in the last hour
  return !isRedemption && isSelf && Date.now() - createdTime < 60 * 60 * 1000
}