import yup from 'yup'
import {saveResponsesForQuestion} from '../../server/db/response'
import {getRetrospectiveSurveyForPlayer} from '../../server/db/survey'
import {getProjectById} from '../../server/db/project'
import {getQuestionById} from '../../server/db/question'
import {graphQLFetcher} from '../../server/util'
import {BadInputError} from '../../server/errors'

export default async function saveRetrospectiveCLISurveyResponseForPlayer(respondentId, {questionNumber, responseParams}) {
  try {
    const questionIndex = questionNumber - 1
    const survey = await getRetrospectiveSurveyForPlayer(respondentId)
    const {questionId, subject} = survey.questionRefs[questionIndex]
    const question = await getQuestionById(questionId)

    const defaultResponseAttrs = {
      questionId,
      respondentId,
      surveyId: survey.id,
    }

    const responses = await parseAndValidateResponseParams(responseParams, question, subject, survey)
      .then(responses => responses.map(response => Object.assign({}, defaultResponseAttrs, response)))

    const createdIds = await saveResponsesForQuestion(responses)

    return createdIds
  } catch (e) {
    throw (e)
  }
}

async function parseAndValidateResponseParams(responseParams, question, subject, survey) {
  try {
    const rawResponses = await parseResponseParams(responseParams, subject, question.subjectType, survey)
    const responses = parseResponses(rawResponses, question.responseType)

    await validateResponses(responses, subject, question.responseType)

    return responses
  } catch (e) {
    throw (e)
  }
}

const responseParamParsers = {
  team: async (responseParams, subject, survey) => {
    const valuesByHandle = responseParams.reduce((prev, param) => {
      const [handle, value] = param.split(':')
      return Object.assign(prev, {[handle]: value})
    }, {})

    const handles = Object.keys(valuesByHandle)

    try {
      const project = await getProjectById(survey.projectId)
      const idsByHandle = await getHandlesForPlayerIds(project.cycleTeams[survey.cycleId].playerIds)
      assertPlayerHandlesAreValid(handles, Object.keys(idsByHandle))

      return handles.map(handle => ({
        subject: idsByHandle[handle],
        value: valuesByHandle[handle],
      }))
    } catch (e) {
      throw (e)
    }
  },
  player: async (responseParams, subject) => {
    return [{subject, value: responseParams[0]}]
  },
}

const responseValueParsers = {
  relativeContribution: str => yup.number().cast(str),
  text: str => yup.string().trim().cast(str),
}

const multipartValidators = {
  relativeContribution: responseParts => {
    const values = responseParts.map(({value}) => value)
    const sum = values.reduce((sum, value) => sum + value, 0)
    if (sum !== 100) {
      throw new BadInputError(`Percentages must add up to 100%, got ${sum}`)
    }
  }
}

function parseResponseParams(responseParams, subject, subjectType, survey) {
  const parser = responseParamParsers[subjectType]

  if (!parser) {
    throw new Error(`Missing param parser for subject type: ${subjectType}!`)
  }

  return parser(responseParams, subject, survey)
}

function parseResponses(unparsedValues, responseType) {
  return unparsedValues.map(({subject, value}) => ({
    subject,
    value: parseValue(value, responseType)
  }))
}

function parseValue(value, type) {
  const parser = responseValueParsers[type]

  if (!parser) {
    throw new Error(`Missing response value parser for response type: ${type}!`)
  }

  return parser(value)
}

async function validateResponses(responses, subject, responseType) {
  try {
    await assertValidResponseValues(responses.map(r => r.value), responseType)
    assertCorrectNumberOfResponses(responses, subject)
    if (responses.length > 1) {
      assertValidMultipartResponse(responses, responseType)
    }
  } catch (e) {
    throw (e)
  }
}

const responseValueValidators = {
  relativeContribution: value => yup.number().positive().max(100).validate(value, {strict: true}),
  text: value => yup.string().min(1).max(10000).validate(value, {strict: true}),
}

function assertValidResponseValues(values, type) {
  const validator = responseValueValidators[type]

  if (!validator) {
    return Promise.reject(Error(`Missing validator for response type: ${type}!`))
  }

  return Promise.all(
    values.map(value => validator(value))
  ).catch(e => {
    throw new BadInputError(`Invalid ${type} response. ${e}`)
  })
}

function assertPlayerHandlesAreValid(responseHandles, teamPlayerHandles) {
  const invalidHandles = []
  responseHandles.forEach(handle => {
    if (teamPlayerHandles.indexOf(handle) < 0) {
      invalidHandles.push(handle)
    }
  })
  if (invalidHandles.length > 0) {
    throw new BadInputError(`Whoops! These players are not on your team: ${invalidHandles.join(' ')}

Your team was: ${teamPlayerHandles.join(' ')}`)
  }
}

function assertCorrectNumberOfResponses(responses, subject) {
  const subjectPartCount = Array.isArray(subject) ? subject.length : 1
  if (responses.length !== subjectPartCount) {
    throw new BadInputError(`Expected responses for all ${subjectPartCount} team members, but you only provided ${responses.length}`)
  }
}

function getHandlesForPlayerIds(ids) {
  return graphQLFetcher(process.env.IDM_BASE_URL)({
    query: 'query ($ids: [ID]!) { getUsersByIds(ids: $ids) { id handle } }',
    variables: {ids},
  })
  .then(json => json.data.getUsersByIds)
  .then(users => users.reduce(
    (prev, u) => Object.assign(prev, {[u.handle]: u.id}),
    {}
  ))
}

function assertValidMultipartResponse(responseParts, type) {
  if (multipartValidators[type]) {
    multipartValidators[type](responseParts)
  }
}

@@ -156,63 +156,8 @@
 ey'%0A
-import %7BgetProjectById%7D from '../../server/db/project'%0A
 impo
@@ -881,24 +881,16 @@
  subject
-, survey
 )%0A      
@@ -1191,24 +1191,16 @@
  subject
-, survey
 ) %7B%0A  tr
@@ -1299,24 +1299,16 @@
 jectType
-, survey
 )%0A    co
@@ -1575,24 +1575,16 @@
  subject
-, survey
 ) =%3E %7B%0A 
@@ -1836,156 +1836,58 @@
 nst 
-project = await getProjectById(survey.projectId)%0A      const idsByHandle = await getHandlesForPlayerIds(project.cycleTeams%5Bsurvey.cycleId%5D.playerIds
+idsByHandle = await getHandlesForPlayerIds(subject
 )%0A  
@@ -2733,24 +2733,16 @@
 jectType
-, survey
 ) %7B%0A  co
@@ -2932,16 +2932,8 @@
 ject
-, survey
 )%0A%7D%0A
@@ -4027,16 +4027,20 @@
 .reject(
+new 
 Error(%60M

use the already-provided subject to validate team handles