import Done from '@/components/Done'
import Questionnaire from '@/components/Questionnaire'
import type {
  QuestionAndAnswer,
  QuestionReview,
} from '@/components/Questionnaire/types'
import { INVALID_VALUE } from '@/components/Questionnaire/utilities'
import type { ButtonProps } from '@blueprintjs/core'
import { Button } from '@blueprintjs/core'
import type {
  Content,
  FreeFormQuestionDetails,
  Question,
  QuestionnaireAnswer,
  QuestionRelatedMilestones,
  TeamQuestionnaireState,
} from '@inject/graphql/fragment-types'
import type { VariablesOf } from '@inject/graphql/graphql'
import { useTypedMutation } from '@inject/graphql/graphql'
import { ReviewQuestionnaire } from '@inject/graphql/mutations'
import { SetTeamQuestionnaireTodo } from '@inject/graphql/mutations.client'
import type { FC, ReactNode } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useClient } from 'urql'
import ReviewButton from './ReviewButton'
import { canBeReviewed } from './utils'

interface InstructorQuestionnaireProps {
  questionnaireId: string
  teamStateId: string
  teamId: string
  exerciseId: string
  status: TeamQuestionnaireState['status']
  content: Content
  questions: Question[]
  teamAnswers: QuestionnaireAnswer[]
  relatedMilestones: QuestionRelatedMilestones[]
  hideReview?: boolean
}

// TODO: simplify the interface, it can be done better with better queries
const InstructorQuestionnaire: FC<InstructorQuestionnaireProps> = ({
  questionnaireId,
  teamStateId,
  teamId,
  exerciseId,
  status,
  content,
  questions,
  teamAnswers,
  relatedMilestones,
  hideReview,
}) => {
  const client = useClient()
  const [{ fetching: loading }, mutate] = useTypedMutation(ReviewQuestionnaire)
  const questionsAndAnswers: QuestionAndAnswer[] = useMemo(
    () =>
      questions.map(question => ({
        question,
        answer:
          teamAnswers.find(teamAnswer => teamAnswer.question.id === question.id)
            ?.answer || INVALID_VALUE,
      })),
    [questions, teamAnswers]
  )

  /*
   * currently, milestones are activated instantly when reviewing individual
   * questions, the activateMilestones and deactivateMilestones arrays are
   * therefore always empty
   *
   * this implementation is future-proof: if milestones are to be activated
   * only when the review is submitted
   */
  const [review, setReview] = useState<QuestionReview[]>(
    questions
      .filter(
        question =>
          question.type === 'FREE_FORM' &&
          (question.details as FreeFormQuestionDetails).relatedMilestones
            .length > 0
      )
      .map(question => ({
        questionId: question.id,
        activateMilestones: [],
        deactivateMilestones: [],
        done: false,
      }))
  )

  const SubmitButton: FC<ButtonProps> = useCallback(
    ({ disabled, title }) => (
      <Button
        intent='primary'
        disabled={disabled}
        title={title}
        loading={loading}
        onClick={() => {
          mutate({
            reviewInput: {
              questionnaireId,
              teamId,
              activateMilestones: review.flatMap(
                ({ activateMilestones }) => activateMilestones
              ),
              deactivateMilestones: review.flatMap(
                ({ deactivateMilestones }) => deactivateMilestones
              ),
            },
          }).then(() => {
            client
              .mutation<unknown, VariablesOf<typeof SetTeamQuestionnaireTodo>>(
                SetTeamQuestionnaireTodo,
                {
                  teamQuestionnaireId: teamStateId,
                  state: true,
                }
              )
              .toPromise()
          })
        }}
      >
        Submit review
      </Button>
    ),
    [loading, mutate, questionnaireId, review, teamId]
  )

  const actions = useMemo<ReactNode>(() => {
    if (hideReview || !canBeReviewed(questions)) {
      return undefined
    }

    switch (status) {
      case 'UNSENT': // fallthrough
      case 'SENT':
        return (
          <SubmitButton disabled title='Answers have not been submitted yet' />
        )
      case 'ANSWERED':
        if (review.some(({ done }) => !done)) {
          return (
            <SubmitButton
              disabled
              title='Review all free form questions before submitting'
            />
          )
        }
        return <SubmitButton />
      case 'REVIEWED':
        return (
          <Done done title='Answers have already been reviewed'>
            <SubmitButton disabled title='Answers have already been reviewed' />
          </Done>
        )
      default:
        throw new Error(`Unknown status: ${status}`)
    }
  }, [SubmitButton, hideReview, questions, review, status])

  const questionActions = useCallback(
    (question: Question) => {
      if (hideReview || question.type !== 'FREE_FORM') {
        return undefined
      }
      const questionReview = review.find(
        questionReview => questionReview.questionId === question.id
      )
      if (!questionReview) {
        return undefined
      }
      return (
        <Done done={questionReview.done}>
          <ReviewButton
            status={status}
            milestones={relatedMilestones
              .filter(milestone => milestone.questionId === question.id)
              .flatMap(milestone => milestone.milestones)}
            teamId={teamId}
            questionReview={questionReview}
            setQuestionReview={newQuestionReview =>
              setReview(prevReview =>
                prevReview.map(review =>
                  review.questionId === question.id ? newQuestionReview : review
                )
              )
            }
          />
        </Done>
      )
    },
    [hideReview, relatedMilestones, review, status, teamId]
  )

  return (
    <Questionnaire
      teamId={teamId}
      exerciseId={exerciseId}
      type='reviewing'
      questionsAndAnswers={questionsAndAnswers}
      content={content}
      questionActions={questionActions}
      actions={actions}
      inInstructor
    />
  )
}

export default InstructorQuestionnaire
