import notEmpty from '@inject/shared/utils/notEmpty'
import type { EditorConfig } from '../useEditorStorage'
import { getExpression, getVariablesForOutput } from '../utils'
import { db } from './db'
import {
  getEmailAddressById,
  getEmailInjectByInjectInfoId,
  getEmailTemplateById,
  getFileById,
  getInformationInjectByInjectInfoId,
  getInjectControlByInjectInfoId,
  getInjectInfoById,
  getLearningActivityById,
  getOverlayByInjectInfoId,
  getQuestionnaireByInjectInfoId,
  getToolById,
  getToolResponseById,
} from './operations'
import type {
  EmailAddressInfo,
  EmailTemplate,
  InjectControl,
  InjectInfo,
  LearningObjectiveInfo,
  Milestone,
  MilestoneName,
  ToolInfo,
  ToolResponse,
} from './types'
import { InjectType, MilestoneEventType } from './types'

export const getMilestonesWithNames = async () => {
  const milestones = await db.milestones.toArray()

  const milestonesWithNames = await Promise.all(
    milestones.map(async (milestone: Milestone) => {
      switch (milestone.type) {
        case MilestoneEventType.LEARNING_ACTIVITY: {
          const activity = await getLearningActivityById(milestone.referenceId)
          return activity ? { ...milestone, name: activity.name } : undefined
        }
        case MilestoneEventType.INJECT: {
          const inject = await getInjectInfoById(milestone.referenceId)
          return inject ? { ...milestone, name: inject.name } : undefined
        }
        case MilestoneEventType.TOOL: {
          const response = await getToolResponseById(milestone.referenceId)
          if (!response) return

          const tool = await getToolById(response.toolId)
          return {
            ...milestone,
            name: `${tool ? `${tool.name} - ` : ''}${response.parameter}`,
          }
        }
        case MilestoneEventType.EMAIL: {
          const template = await getEmailTemplateById(milestone.referenceId)
          if (!template) return

          const address = await getEmailAddressById(template.emailAddressId)
          return {
            ...milestone,
            name: `${address ? `${address.address} - ` : ''}${template.context}`,
          }
        }
        default:
          return
      }
    })
  )

  return milestonesWithNames.filter(notEmpty)
}

export const getLearningObjectivesWithActivities = async () => {
  const objectives = await db.learningObjectives.toArray()

  return Promise.all(
    objectives.map(async (objective: LearningObjectiveInfo) => {
      const activities = await db.learningActivities
        .where({ learningObjectiveId: objective.id })
        .toArray()
      return { ...objective, activities }
    })
  )
}

const getMilestoneCondition = async (events: number[]) => {
  const milestones = await getMilestonesWithNames()
  const variables = getVariablesForOutput(milestones)
  return getExpression(events, variables)
}

const getInformationInject = async (injectInfoId: number) => {
  const info = await getInformationInjectByInjectInfoId(injectInfoId)
  if (!info) return

  const file = info?.fileId ? await getFileById(info?.fileId) : undefined
  const overlay = await getOverlayByInjectInfoId(injectInfoId)
  return { ...info, file, overlay }
}

export const getInformationInjects = async () => {
  const injects = await db.injectInfos
    .where({ type: InjectType.INFORMATION })
    .toArray()
  const usedMilestones = await getUsedMilestones()

  return Promise.all(
    injects.map(async (inject: InjectInfo) => {
      const info = await getInformationInject(inject.id)
      if (!info) return

      const activateMilestone = usedMilestones.find(
        (milestone: MilestoneName) =>
          milestone.type === MilestoneEventType.INJECT &&
          milestone.referenceId === inject.id
      )
      const injectControl = await getInjectControlByInjectInfoId(inject.id)
      const milestoneCondition = injectControl?.milestoneCondition
        ? await getMilestoneCondition(injectControl?.milestoneCondition)
        : undefined

      return {
        ...inject,
        info: {
          ...info,
          activateMilestone,
          milestoneCondition,
        },
        injectControl,
      }
    })
  )
}

const getEmailInject = async (injectInfoId: number) => {
  const email = await getEmailInjectByInjectInfoId(injectInfoId)
  if (!email) return

  const sender = email?.emailAddressId
    ? await getEmailAddressById(email?.emailAddressId)
    : undefined
  const file = email?.fileId ? await getFileById(email?.fileId) : undefined
  const overlay = await getOverlayByInjectInfoId(injectInfoId)
  return { ...email, sender, file, overlay }
}

export const getEmailInjects = async () => {
  const injects = await db.injectInfos
    .where({ type: InjectType.EMAIL })
    .toArray()
  const usedMilestones = await getUsedMilestones()

  return Promise.all(
    injects.map(async (inject: InjectInfo) => {
      const email = await getEmailInject(inject.id)
      if (!email) return

      const activateMilestone = usedMilestones.find(
        (milestone: MilestoneName) =>
          milestone.type === MilestoneEventType.INJECT &&
          milestone.referenceId === inject.id
      )
      const injectControl = await getInjectControlByInjectInfoId(inject.id)
      const milestoneCondition = injectControl?.milestoneCondition
        ? await getMilestoneCondition(injectControl?.milestoneCondition)
        : undefined

      return {
        ...inject,
        email: {
          ...email,
          activateMilestone,
          milestoneCondition,
        },
        injectControl,
      }
    })
  )
}

const getQuestionnaireInject = async (injectInfoId: number) => {
  const questionnaire = await getQuestionnaireByInjectInfoId(injectInfoId)
  if (!questionnaire) return

  const questions = await db.questionnaireQuestions
    .where({ questionnaireId: questionnaire.id })
    .toArray()
  const injectControl = await getInjectControlByInjectInfoId(injectInfoId)
  const milestoneCondition = injectControl?.milestoneCondition
    ? await getMilestoneCondition(injectControl?.milestoneCondition)
    : undefined
  const overlay = await getOverlayByInjectInfoId(injectInfoId)
  return {
    ...questionnaire,
    questions,
    injectControl,
    milestoneCondition,
    overlay,
  }
}

export const getQuestionnaires = async () => {
  const injects = await db.injectInfos
    .where({ type: InjectType.QUESTIONNAIRE })
    .toArray()
  const usedMilestones = await getUsedMilestones()

  return Promise.all(
    injects.map(async (inject: InjectInfo) => {
      const questionnaire = await getQuestionnaireInject(inject.id)
      if (!questionnaire) return

      const activateMilestone = usedMilestones.find(
        (milestone: MilestoneName) =>
          milestone.type === MilestoneEventType.INJECT &&
          milestone.referenceId === inject.id
      )
      return { ...questionnaire, activateMilestone }
    })
  )
}

const getToolResponses = async (
  toolId: number,
  usedMilestones: MilestoneName[]
) => {
  const toolResponses = await db.toolResponses.where({ toolId }).toArray()

  return Promise.all(
    toolResponses.map(async (toolResponse: ToolResponse) => {
      const activateMilestone = usedMilestones.find(milestone =>
        toolResponse.learningActivityId
          ? milestone.type === MilestoneEventType.LEARNING_ACTIVITY &&
            milestone.referenceId === toolResponse.learningActivityId
          : milestone.type === MilestoneEventType.TOOL &&
            milestone.referenceId === toolResponse.id
      )
      const file = toolResponse?.fileId
        ? await getFileById(toolResponse?.fileId)
        : undefined
      const milestoneConditionWithNames = toolResponse?.milestoneCondition
        ? await getMilestoneCondition(toolResponse?.milestoneCondition)
        : undefined
      return {
        ...toolResponse,
        activateMilestone,
        file,
        milestoneConditionWithNames,
      }
    })
  )
}

export const getTools = async () => {
  const tools = await db.tools.toArray()
  const usedMilestones = await getUsedMilestones()

  return Promise.all(
    tools.map(async (tool: ToolInfo) => {
      const toolResponses = await getToolResponses(tool.id, usedMilestones)
      return { ...tool, toolResponses }
    })
  )
}

export const getEmailTemplates = async (
  emailAddressId: number,
  usedMilestones: MilestoneName[]
) => {
  const emailTemplates = await db.emailTemplates
    .where({ emailAddressId })
    .toArray()

  return Promise.all(
    emailTemplates.map(async (emailTemplate: EmailTemplate) => {
      const activateMilestone = usedMilestones.find(milestone =>
        emailTemplate.learningActivityId
          ? milestone.type === MilestoneEventType.LEARNING_ACTIVITY &&
            milestone.referenceId === emailTemplate.learningActivityId
          : milestone.type === MilestoneEventType.EMAIL &&
            milestone.referenceId === emailTemplate.id
      )
      const file = emailTemplate?.fileId
        ? await getFileById(emailTemplate?.fileId)
        : undefined
      return { ...emailTemplate, activateMilestone, file }
    })
  )
}

export const getEmailAddresses = async () => {
  const emailAddresses = await db.emailAddresses.toArray()
  const usedMilestones = await getUsedMilestones()

  return Promise.all(
    emailAddresses.map(async (emailAddress: EmailAddressInfo) => {
      const emailTemplates = await getEmailTemplates(
        Number(emailAddress.id),
        usedMilestones
      )
      return { ...emailAddress, emailTemplates }
    })
  )
}

export const getUsedMilestones = async () => {
  const config = JSON.parse(
    localStorage.getItem('editor-config') || ''
  ) as EditorConfig

  const milestones = await getMilestonesWithNames()
  const injectControls = await db.injectControls.toArray()
  const toolControls = await db.toolResponses.toArray()
  const usedMilestones = new Set([
    ...injectControls
      .map((control: InjectControl) => control.milestoneCondition)
      .flat(1),
    ...toolControls
      .map((toolResponse: ToolResponse) => toolResponse.milestoneCondition)
      .flat(1),
  ])

  return milestones.filter(
    (milestone: MilestoneName) =>
      milestone.type === MilestoneEventType.LEARNING_ACTIVITY ||
      usedMilestones.has(milestone.id) ||
      milestone.id === config?.finalMilestone
  )
}
