import Done from '@/components/Done'
import Questionnaire from '@/components/Questionnaire'
import type { QuestionAndAnswer } from '@/components/Questionnaire/types'
import { INVALID_VALUE } from '@/components/Questionnaire/utilities'
import type { ButtonProps } from '@blueprintjs/core'
import { Button, Callout, NonIdealState } from '@blueprintjs/core'
import type {
  Question,
  QuestionnaireDetails,
} from '@inject/graphql/fragment-types'
import { useTypedMutation, useTypedQuery } from '@inject/graphql/graphql'
import { AnswerQuestionnaire } from '@inject/graphql/mutations'
import { GetTeam, GetTeamQuestionnaireState } from '@inject/graphql/queries'
import Keys from '@inject/shared/localstorage/keys'
import { useLocalStorageState } from 'ahooks'
import type { FC } from 'react'
import { useCallback, useEffect, useMemo } from 'react'

interface TraineeQuestionnaireContentProps {
  details: QuestionnaireDetails
  teamId: string
  exerciseId: string
  onClose?: () => void
}

const getDefaultAnswers = (questions: Question[]): QuestionAndAnswer[] =>
  questions.map(question => ({
    question,
    answer: INVALID_VALUE,
  }))

const MultipleUsersWarning = () => (
  <Callout intent='warning'>
    <b>Answers can be submitted only once per team!</b>
    <br />
    <span>Make sure all team members agree before submitting</span>
  </Callout>
)

const TraineeQuestionnaireContent: FC<TraineeQuestionnaireContentProps> = ({
  details,
  teamId,
  exerciseId,
  onClose,
}) => {
  const defaultAnswers: QuestionAndAnswer[] = useMemo(
    () => getDefaultAnswers(details.questions),
    [details.questions]
  )

  const [questionsAndAnswers, setQuestionsAndAnswers] = useLocalStorageState<
    QuestionAndAnswer[]
  >(Keys.getQuestionnaireAnswersKey(details.id), {
    defaultValue: defaultAnswers,
    listenStorageChange: true,
  })

  // reset state when questions change
  useEffect(() => {
    setQuestionsAndAnswers(getDefaultAnswers(details.questions))
  }, [details.questions, setQuestionsAndAnswers])

  const [{ fetching: loading }, mutate] = useTypedMutation(AnswerQuestionnaire)

  // TODO: ensure this works once backend is updated
  /*
   * currently, teamData.team.users will always be an empty array
   * when called by a trainee
   *
   * Should be fixed by the following MR:
   * https://gitlab.fi.muni.cz/inject/backend/-/merge_requests/237
   */
  const [{ data: teamData }] = useTypedQuery({
    query: GetTeam,
    variables: { teamId },
    context: useMemo(
      () => ({
        suspense: true,
      }),
      []
    ),
  })
  const [{ data }] = useTypedQuery({
    query: GetTeamQuestionnaireState,
    variables: {
      teamId,
      questionnaireId: details.id,
    },
    context: useMemo(
      () => ({
        suspense: true,
      }),
      []
    ),
  })
  // reset state when answers change
  useEffect(() => {
    if (!data || !data.questionnaireState) {
      return
    }
    const teamAnswers = data.questionnaireState.answers
    setQuestionsAndAnswers(prev =>
      (prev || defaultAnswers).map(questionAndAnswer => ({
        ...questionAndAnswer,
        answer:
          teamAnswers.find(
            teamAnswer =>
              teamAnswer.question.id === questionAndAnswer.question.id
          )?.answer || questionAndAnswer.answer,
      }))
    )
  }, [data, defaultAnswers, details.questions, setQuestionsAndAnswers])

  const status = useMemo(
    () => data?.questionnaireState.status,
    [data?.questionnaireState.status]
  )

  const { title, disabled } = useMemo<ButtonProps>(() => {
    switch (status) {
      case undefined:
        return { title: '', disabled: true }
      case 'UNSENT':
        return { title: 'Questionnaire has not been sent yet', disabled: true }
      case 'SENT':
        if (
          (questionsAndAnswers || defaultAnswers).some(
            questionAndAnswer => questionAndAnswer.answer === INVALID_VALUE
          )
        ) {
          return {
            title: 'Answer all questions before submitting',
            disabled: true,
          }
        }
        return { title: undefined, disabled: false }
      case 'ANSWERED': // fallthrough
      case 'REVIEWED':
        return { title: 'Answers have already been submitted', disabled: true }
      default:
        throw new Error(`Unknown status: ${status}`)
    }
  }, [defaultAnswers, questionsAndAnswers, status])

  const handleClick = useCallback(() => {
    mutate({
      questInput: {
        answers: (questionsAndAnswers || defaultAnswers).map(
          questionAndAnswer => ({
            questionId: questionAndAnswer.question.id,
            value: questionAndAnswer.answer,
          })
        ),
        questionnaireId: details.id,
        teamId,
      },
    }).then(() => onClose?.())
  }, [defaultAnswers, details.id, mutate, onClose, questionsAndAnswers, teamId])

  const handleChange = useCallback(
    (value: string, questionId: string) => {
      setQuestionsAndAnswers(prev => {
        const newValue = prev || defaultAnswers
        const index = newValue.findIndex(
          questionAndAnswer => questionAndAnswer.question.id === questionId
        )
        if (index === -1) {
          throw new Error(`Question not found: ${questionId}`)
        }
        return [
          ...newValue.slice(0, index),
          { ...newValue[index], answer: value },
          ...newValue.slice(index + 1),
        ]
      })
    },
    [defaultAnswers, setQuestionsAndAnswers]
  )

  const done = useMemo<boolean>(() => {
    switch (status) {
      case 'UNSENT': // fallthrough
      case 'SENT':
        return false
      case 'ANSWERED': // fallthrough
      case 'REVIEWED':
        return true
      default:
        throw new Error(`Unknown status: ${status}`)
    }
  }, [status])

  if (!data?.questionnaireState || !teamData?.team) {
    return (
      <NonIdealState
        icon='low-voltage-pole'
        title='No data'
        description='Please wait for the data to come in'
      />
    )
  }
  const numUsers = teamData.team.users.length || 0

  return (
    <Questionnaire
      inInstructor={false}
      teamId={teamId}
      exerciseId={exerciseId}
      type='answering'
      callout={status === 'SENT' && numUsers > 1 && <MultipleUsersWarning />}
      questionsAndAnswers={questionsAndAnswers || defaultAnswers}
      content={details.content}
      disabled={() => status !== 'SENT'}
      onChange={questionId => value => handleChange(value, questionId)}
      actions={
        <Done done={done}>
          <Button
            intent='primary'
            onClick={handleClick}
            disabled={disabled}
            title={title}
            loading={loading}
          >
            Submit
          </Button>
        </Done>
      }
    />
  )
}

export default TraineeQuestionnaireContent
