import { isEmpty } from 'lodash'
import { validateExpression } from '../utils'
import { db } from './db'
import type { MarkdownContent } from './types'
import {
  InjectType,
  LearningActivityType,
  MilestoneEventType,
  type ContentFile,
  type EmailAddressInfo,
  type EmailInject,
  type EmailTemplate,
  type InformationInject,
  type InjectControl,
  type InjectInfo,
  type LearningActivityInfo,
  type LearningObjectiveInfo,
  type Milestone,
  type Overlay,
  type Questionnaire,
  type QuestionnaireQuestion,
  type ToolInfo,
  type ToolResponse,
} from './types'

export const clearDb = async () =>
  await db.tables.forEach(async table => {
    await table.clear()
  })

export const isDbEmpty = async () => {
  const results = await Promise.all(
    db.tables.map(async table => (await table.count()) === 0)
  )
  return results.every(Boolean)
}

// learning objectives operations
export const addLearningObjective = async (
  objective: Omit<LearningObjectiveInfo, 'id'>
) =>
  await db.transaction('rw', db.learningObjectives, async () => {
    const id = await db.learningObjectives.add(objective)
    return id
  })

export const updateLearningObjective = async (
  objective: LearningObjectiveInfo
) => await db.learningObjectives.put(objective)

export const deleteLearningObjective = async (id: number) =>
  await db.transaction(
    'rw',
    [
      db.learningObjectives,
      db.learningActivities,
      db.toolResponses,
      db.emailTemplates,
      db.milestones,
    ],
    async () => {
      await db.learningObjectives.delete(id)
      await db.learningActivities
        .where({ learningObjectiveId: id })
        .each(async activity => {
          await deleteLearningActivity(activity.id)
        })
    }
  )

// learning activities operations
export const getLearningActivityById = async (id: number) =>
  await db.learningActivities.get(id)

export const getLearningActivityByName = async (name: string) =>
  await db.learningActivities.get({ name })

export const addLearningActivity = async (
  activity: Omit<LearningActivityInfo, 'id'>
) =>
  await db.transaction('rw', db.learningActivities, db.milestones, async () => {
    const id = await db.learningActivities.add(activity)
    await addMilestone({
      type: MilestoneEventType.LEARNING_ACTIVITY,
      referenceId: id,
    })
    return id
  })

export const updateLearningActivity = async (activity: LearningActivityInfo) =>
  await db.transaction(
    'rw',
    db.learningActivities,
    db.toolResponses,
    db.emailTemplates,
    async () => {
      const currentActivity = await db.learningActivities.get(activity.id)

      if (currentActivity?.type !== activity.type) {
        if (currentActivity?.type === LearningActivityType.EMAIL) {
          db.emailTemplates.where({ learningActivityId: activity.id }).delete()
        }
        if (currentActivity?.type === LearningActivityType.TOOL) {
          db.toolResponses.where({ learningActivityId: activity.id }).delete()
        }
      }

      db.learningActivities.put(activity)
    }
  )

export const deleteLearningActivity = async (id: number) =>
  await db.transaction(
    'rw',
    db.learningActivities,
    db.toolResponses,
    db.emailTemplates,
    db.milestones,
    async () => {
      await db.learningActivities.delete(id)
      await db.toolResponses.where({ learningActivityId: id }).delete()
      await db.emailTemplates.where({ learningActivityId: id }).delete()
      await db.milestones
        .where({ type: MilestoneEventType.LEARNING_ACTIVITY, referenceId: id })
        .delete()
    }
  )

const isToolActivitySpecified = async (activityId: number) => {
  const response = await getToolResponseByActivityId(activityId)
  const tool = response ? await getToolById(response?.toolId) : undefined
  return response && tool && !isEmpty(response.parameter)
}

const isEmailActivitySpecified = async (activityId: number) => {
  const template = await getEmailTemplateByActivityId(activityId)
  const sender = template
    ? await getEmailAddressById(template.emailAddressId)
    : undefined
  return template && sender && !isEmpty(template.context)
}

export const isActivitySpecified = async (activity: LearningActivityInfo) => {
  switch (activity.type) {
    case LearningActivityType.TOOL:
      return await isToolActivitySpecified(activity.id)
    case LearningActivityType.EMAIL:
      return await isEmailActivitySpecified(activity.id)
  }
}

export const areActivitiesSpecified = async () => {
  const activities = await db.learningActivities.toArray()

  const results = await Promise.all(
    activities.map(async activity => await isActivitySpecified(activity))
  )

  return results.every(Boolean)
}

// inject info operations
export const getInjectInfoById = async (id: number) =>
  await db.injectInfos.get(id)

export const addInjectInfo = async (injectInfo: Omit<InjectInfo, 'id'>) =>
  await db.transaction('rw', db.injectInfos, db.milestones, async () => {
    const id = await db.injectInfos.add(injectInfo)
    await addMilestone({ type: MilestoneEventType.INJECT, referenceId: id })
    return id
  })

export const updateInjectInfo = async (injectInfo: InjectInfo) =>
  await db.injectInfos.put(injectInfo)

export const deleteInjectInfo = async (id: number) =>
  await db.transaction(
    'rw',
    [
      db.injectInfos,
      db.informationInjects,
      db.emailInjects,
      db.questionnaires,
      db.questionnaireQuestions,
      db.milestones,
      db.overlays,
      db.injectControls,
    ],
    async () => {
      await db.injectInfos.delete(id)
      await db.informationInjects.where({ injectInfoId: id }).delete()
      await db.emailInjects.where({ injectInfoId: id }).delete()
      const questionnaire = await getQuestionnaireByInjectInfoId(id)
      if (questionnaire) await deleteQuestionnaire(questionnaire.id)
      await db.milestones
        .where({ type: MilestoneEventType.INJECT, referenceId: id })
        .delete()
      await db.overlays.where({ injectInfoId: id }).delete()
      await db.injectControls.where({ injectInfoId: id }).delete()
    }
  )

export const isEmailInjectSpecified = async (injectId: number) => {
  const email = await getEmailInjectByInjectInfoId(injectId)
  const sender = email
    ? await getEmailAddressById(email?.emailAddressId)
    : undefined
  return email && sender && !isEmpty(email.subject)
}

export const isQuestionnaireInjectSpecified = async (injectId: number) => {
  const questionnaire = await getQuestionnaireByInjectInfoId(injectId)
  const questionsCount = questionnaire
    ? await db.questionnaireQuestions
        .where({ questionnaireId: questionnaire?.id })
        .count()
    : 0
  return questionnaire && !isEmpty(questionnaire.title) && questionsCount > 0
}

export const isInjectSpecified = async (inject: InjectInfo) => {
  switch (inject.type) {
    case InjectType.INFORMATION:
      return true
    case InjectType.EMAIL:
      return await isEmailInjectSpecified(inject.id)
    case InjectType.QUESTIONNAIRE:
      return await isQuestionnaireInjectSpecified(inject.id)
  }
}

export const areInjectsSpecified = async () => {
  const injects = await db.injectInfos.toArray()

  const results = await Promise.all(
    injects.map(async inject => await isInjectSpecified(inject))
  )

  return results.every(Boolean)
}

export const doesInjectHaveCorrectCondition = async (
  injectId: number,
  milestones: Milestone[]
) => {
  const control = await getInjectControlByInjectInfoId(injectId)

  return validateExpression(control?.milestoneCondition || [], milestones)
    .isValid
}

export const doInjectsHaveCorrectConditions = async () => {
  const injects = await db.injectInfos.toArray()
  const milestones = await db.milestones.toArray()

  const results = await Promise.all(
    injects.map(
      async inject =>
        await doesInjectHaveCorrectCondition(inject.id, milestones)
    )
  )

  return results.every(Boolean)
}

// tool operations
export const getToolById = async (id: number) => await db.tools.get(id)

export const addTool = async (tool: Omit<ToolInfo, 'id'>) =>
  await db.transaction('rw', db.tools, async () => {
    const id = await db.tools.add(tool)
    return id
  })

export const updateTool = async (tool: ToolInfo) => await db.tools.put(tool)

export const deleteTool = async (id: number) =>
  await db.transaction(
    'rw',
    db.tools,
    db.toolResponses,
    db.milestones,
    async () => {
      await db.tools.delete(id)
      await db.toolResponses.where({ toolId: id }).each(async response => {
        await deleteToolResponse(response.id)
      })
    }
  )

// tool response operations
export const getToolResponseById = async (id: number) =>
  await db.toolResponses.get(id)

export const getToolResponseByActivityId = async (learningActivityId: number) =>
  await db.toolResponses.get({ learningActivityId })

export const addToolResponse = async (response: Omit<ToolResponse, 'id'>) =>
  await db.transaction('rw', db.toolResponses, db.milestones, async () => {
    const id = await db.toolResponses.add(response)
    if (!response.learningActivityId) {
      await addMilestone({ type: MilestoneEventType.TOOL, referenceId: id })
    }
    return id
  })

export const updateToolResponse = async (response: ToolResponse) =>
  await db.toolResponses.put(response)

export const updateToolResponseMilestoneCondition = async (
  id: number,
  milestoneCondition: number[]
) => await db.toolResponses.update(id, { milestoneCondition })

export const deleteToolResponse = async (id: number) =>
  await db.transaction('rw', db.toolResponses, db.milestones, async () => {
    await db.toolResponses.delete(id)
    await db.milestones
      .where({ type: MilestoneEventType.TOOL, referenceId: id })
      .delete()
  })

export const doesToolHaveResponse = async (toolId: number) =>
  (await db.toolResponses.where({ toolId }).count()) > 0

export const doToolsHaveResponses = async () => {
  const tools = await db.tools.toArray()

  const results = await Promise.all(
    tools.map(async tool => await doesToolHaveResponse(tool.id))
  )
  return results.every(Boolean)
}

export const doesToolResponseHaveCorrectCondition = (
  response: ToolResponse,
  milestones: Milestone[]
) => validateExpression(response.milestoneCondition || [], milestones).isValid

export const doToolResponsesHaveCorrectConditions = async () => {
  const responses = await db.toolResponses.toArray()
  const milestones = await db.milestones.toArray()

  const results = responses.map(response =>
    doesToolResponseHaveCorrectCondition(response, milestones)
  )

  return results.every(Boolean)
}

export const doesLearningActivityHaveCorrectCondition = async (
  activity: LearningActivityInfo,
  milestones: Milestone[]
) => {
  if (activity.type !== LearningActivityType.TOOL) return true

  const response = await getToolResponseByActivityId(activity.id)

  return validateExpression(response?.milestoneCondition || [], milestones)
    .isValid
}

export const doLearningActivitiesHaveCorrectConditions = async () => {
  const responses = await db.toolResponses.toArray()
  const milestones = await db.milestones.toArray()

  const results = responses.map(
    response =>
      !response.learningActivityId ||
      doesToolResponseHaveCorrectCondition(response, milestones)
  )

  return results.every(Boolean)
}

// email address operations
export const getEmailAddressById = async (id: number) =>
  await db.emailAddresses.get(id)

export const getEmailAddressByName = async (address: string) =>
  await db.emailAddresses.get({ address })

export const addEmailAddress = async (address: Omit<EmailAddressInfo, 'id'>) =>
  await db.transaction('rw', db.emailAddresses, async () => {
    const id = await db.emailAddresses.add(address)
    return id
  })

export const updateEmailAddress = async (address: EmailAddressInfo) =>
  await db.emailAddresses.put(address)

export const deleteEmailAddress = async (id: number) =>
  await db.transaction(
    'rw',
    db.emailAddresses,
    db.emailTemplates,
    db.milestones,
    async () => {
      await db.emailAddresses.delete(id)
      await db.emailTemplates
        .where({ emailAddressId: id })
        .each(async template => {
          await deleteEmailTemplate(template.id)
        })
    }
  )

// email template operations
export const getEmailTemplateById = async (id: number) =>
  await db.emailTemplates.get(id)

export const getEmailTemplateByActivityId = async (
  learningActivityId: number
) => await db.emailTemplates.get({ learningActivityId })

export const addEmailTemplate = async (template: Omit<EmailTemplate, 'id'>) =>
  await db.transaction('rw', db.emailTemplates, db.milestones, async () => {
    const id = await db.emailTemplates.add(template)
    if (!template.learningActivityId) {
      await addMilestone({ type: MilestoneEventType.EMAIL, referenceId: id })
    }
    return id
  })

export const updateEmailTemplate = async (template: EmailTemplate) =>
  await db.emailTemplates.put(template)

export const deleteEmailTemplate = async (id: number) =>
  await db.transaction('rw', db.emailTemplates, db.milestones, async () => {
    await db.emailTemplates.delete(id)
    await db.milestones
      .where({ type: MilestoneEventType.EMAIL, referenceId: id })
      .delete()
  })

// email inject operations
export const getEmailInjectByInjectInfoId = async (injectInfoId: number) =>
  await db.emailInjects.get({ injectInfoId })

export const addEmailInject = async (emailInject: Omit<EmailInject, 'id'>) =>
  await db.transaction('rw', db.emailInjects, async () => {
    await db.emailInjects.add(emailInject)
  })

export const updateEmailInject = async (emailInject: EmailInject) =>
  await db.emailInjects.put(emailInject)

export const deleteEmailInject = async (id: number) =>
  await db.emailInjects.delete(id)

// information inject operations
export const getInformationInjectByInjectInfoId = async (
  injectInfoId: number
) => await db.informationInjects.get({ injectInfoId })

export const addInformationInject = async (
  informationInject: Omit<InformationInject, 'id'>
) =>
  await db.transaction('rw', db.informationInjects, async () => {
    await db.informationInjects.add(informationInject)
  })

export const updateInformationInject = async (
  informationInject: InformationInject
) => await db.informationInjects.put(informationInject)

export const deleteInformationInject = async (id: number) =>
  await db.informationInjects.delete(id)

// questionnaire operations
export const getQuestionnaireByInjectInfoId = async (injectInfoId: number) =>
  await db.questionnaires.get({ injectInfoId })

export const addQuestionnaire = async (
  questionnaire: Omit<Questionnaire, 'id'>
) =>
  await db.transaction('rw', db.questionnaires, async () => {
    const id = await db.questionnaires.add(questionnaire)
    return id
  })

export const updateQuestionnaire = async (questionnaire: Questionnaire) =>
  await db.questionnaires.put(questionnaire)

export const deleteQuestionnaire = async (id: number) =>
  await db.transaction(
    'rw',
    db.questionnaires,
    db.questionnaireQuestions,
    async () => {
      await db.questionnaires.delete(id)
      await db.questionnaireQuestions.where({ questionnaireId: id }).delete()
    }
  )

// questionnaire question operations
export const addQuestionnaireQuestion = async (
  questionnaireQuestion: Omit<QuestionnaireQuestion, 'id'>
) =>
  await db.transaction('rw', db.questionnaireQuestions, async () => {
    await db.questionnaireQuestions.add(questionnaireQuestion)
  })

export const updateQuestionnaireQuestion = async (
  questionnaireQuestion: QuestionnaireQuestion
) => await db.questionnaireQuestions.put(questionnaireQuestion)

export const deleteQuestionnaireQuestion = async (id: number) =>
  await db.questionnaireQuestions.delete(id)

// overlay operations
export const getOverlayByInjectInfoId = async (injectInfoId: number) =>
  await db.overlays.get({ injectInfoId })

export const addOverlay = async (overlay: Omit<Overlay, 'id'>) =>
  await db.transaction('rw', db.overlays, async () => {
    await db.overlays.add(overlay)
  })

export const updateOverlay = async (overlay: Overlay) =>
  await db.overlays.put(overlay)

export const deleteOverlay = async (id: number) => await db.overlays.delete(id)

// inject control operations
export const getInjectControlByInjectInfoId = async (injectInfoId: number) =>
  await db.injectControls.get({ injectInfoId })

export const addInjectControl = async (
  injectControl: Omit<InjectControl, 'id'>
) =>
  await db.transaction('rw', db.injectControls, async () => {
    await db.injectControls.add(injectControl)
  })

export const updateInjectControl = async (injectControl: InjectControl) =>
  await db.injectControls.put(injectControl)

export const deleteInjectControl = async (id: number) =>
  await db.injectControls.delete(id)

// file operations
export const getFileById = async (id: number) => await db.files.get(id)

export const getFileByName = async (name: string) =>
  await db.files.get({ name })

export const addFile = async (file: Omit<ContentFile, 'id'>) =>
  await db.transaction('rw', db.files, async () => {
    const id = await db.files.add(file)
    return id
  })

export const updateFile = async (file: ContentFile) => await db.files.put(file)

export const deleteFile = async (id: number) => await db.files.delete(id)

// milestone operations
export const getMilestoneById = async (id: number) =>
  await db.milestones.get(id)

export const getMilestoneByTypeAndReferenceId = async (
  type: MilestoneEventType,
  referenceId: number
) => await db.milestones.get({ type, referenceId })

export const addMilestone = async (milestone: Omit<Milestone, 'id'>) =>
  await db.transaction('rw', db.milestones, async () => {
    const id = await db.milestones.add(milestone)
    return id
  })

export const updateMilestone = async (milestone: Milestone) =>
  await db.milestones.put(milestone)

export const deleteMilestone = async (id: number) =>
  await db.milestones.delete(id)

// markdown content operations
export const getMarkdownContentByName = async (fileName: string) =>
  await db.markdownContents.get({ fileName })

export const addMarkdownContent = async (
  content: Omit<MarkdownContent, 'id'>
) =>
  await db.transaction('rw', db.markdownContents, async () => {
    await db.markdownContents.add(content)
  })

export const updateMarkdownContent = async (content: MarkdownContent) =>
  await db.markdownContents.put(content)

export const deleteMarkdownContent = async (id: number) =>
  await db.markdownContents.delete(id)
