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 { css } from '@emotion/css'
import type {
  Answer,
  Question,
  QuestionnaireAnswer,
  TeamQuestionnaireStateWithQuestionnaire,
} from '@inject/graphql/fragment-types'
import { useTypedMutation, useTypedQuery } from '@inject/graphql/graphql'
import { AnswerQuestionnaire } from '@inject/graphql/mutations'
import { GetTeam } from '@inject/graphql/queries'
import { useLoopStatus } from '@inject/graphql/utils/useExerciseLoopStatusSubscription'
import Keys from '@inject/shared/localstorage/keys'
import type { FC } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'

interface TraineeQuestionnaireContentProps {
  teamState: TeamQuestionnaireStateWithQuestionnaire
  teamId: string
  exerciseId: string
  onClose?: () => void
}

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 getDefaultValue = (
  questions: Question[],
  answers: QuestionnaireAnswer[],
  teamStateId: string
) => {
  const storedValue = localStorage.getItem(
    Keys.getQuestionnaireAnswersKey(teamStateId)
  )
  const storedAnswers: Answer[] = storedValue ? JSON.parse(storedValue) : []
  return questions.map(question => ({
    question,
    answer:
      answers.find(answer => answer.question.id === question.id)?.answer ||
      storedAnswers.find(answer => answer.questionId === question.id)?.value ||
      INVALID_VALUE,
  }))
}

const TraineeQuestionnaireContent: FC<TraineeQuestionnaireContentProps> = ({
  teamState,
  teamId,
  exerciseId,
  onClose,
}) => {
  const { running } = useLoopStatus()
  const { id: teamStateId, answers, status, questionnaire } = teamState
  const { id: questionnaireId, questions, content } = questionnaire

  const [questionsAndAnswers, setQuestionsAndAnswers] = useState<
    QuestionAndAnswer[]
  >(getDefaultValue(questions, answers, teamStateId))

  // reset questionsAndAnswers when teamState changes
  useEffect(() => {
    setQuestionsAndAnswers(getDefaultValue(questions, answers, teamStateId))
  }, [answers, questions, teamStateId])

  // TODO: please move storage effects into a specialized hook/class and don't rely on side-effects, this creates weird latching of the questionnaire states

  // store answers in localStorage whenever state changes
  useEffect(() => {
    if (status === 'SENT') {
      const answers: Answer[] = questionsAndAnswers.map(questionAndAnswer => ({
        questionId: questionAndAnswer.question.id,
        value: questionAndAnswer.answer,
      }))
      localStorage.setItem(
        Keys.getQuestionnaireAnswersKey(teamStateId),
        JSON.stringify(answers)
      )
    } else {
      localStorage.removeItem(Keys.getQuestionnaireAnswersKey(teamStateId))
    }
  }, [questionsAndAnswers, status, teamStateId])

  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 { title, disabled } = useMemo<ButtonProps>(() => {
    switch (status) {
      case undefined:
        return { title: '', disabled: true }
      case 'UNSENT':
        if (!running) {
          return {
            title: 'Exercise is paused/stopped',
            disabled: true,
          }
        }
        return { title: 'Questionnaire has not been sent yet', disabled: true }
      case 'SENT':
        if (!running) {
          return {
            title: 'Exercise is paused/stopped',
            disabled: true,
          }
        }
        if (
          questionsAndAnswers.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}`)
    }
  }, [questionsAndAnswers, running, status])

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

  const handleChange = useCallback(
    (value: string, questionId: string) => {
      setQuestionsAndAnswers(prev => {
        const newValue = prev
        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),
        ]
      })
    },
    [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 (!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}
      content={content}
      disabled={() => status !== 'SENT' || !running}
      onChange={questionId => value => handleChange(value, questionId)}
      actions={
        <div
          className={css`
            display: flex;
            gap: 0.5rem;
          `}
        >
          {onClose && <Button text='Close' onClick={onClose} />}
          <Done done={done}>
            <Button
              intent='primary'
              onClick={handleClick}
              disabled={disabled}
              title={title}
              loading={loading}
            >
              Submit
            </Button>
          </Done>
        </div>
      }
    />
  )
}

export default TraineeQuestionnaireContent
