import { useNavigate } from '@/router'
import { Button, ButtonGroup, Dialog, DialogFooter } from '@blueprintjs/core'
import { notify } from '@inject/shared/notification/engine'
import { notifyNoncommmit } from '@inject/shared/notification/var/notifications'
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import useBranches from '../BranchSelector/useBranches'
import { GENERIC_CONTENT } from '../assets/generalContent'
import {
  commitFiles,
  createBranch,
  createGitlabActionStructure,
  createProject,
  validateGitLabGroupToken,
} from '../gitlabAccess'
import useEditorStorage from '../useEditorStorage'
import type { GitlabConfig } from '../useGitlabStorage'
import useGitlabStorage from '../useGitlabStorage'
import { generateFileContents } from '../zip/createDefinitionZip'
import BranchPanel from './BranchPanel'
import CommitPanel from './CommitPanel'
import ProjectPanel from './ProjectPanel'

enum STEPS {
  PROJECT = 0,
  BRANCH = 1,
  COMMIT = 2,
}

const CommitDefinition = () => {
  const [config, setConfig] = useGitlabStorage()
  const [editorConfig, setEditorConfig] = useEditorStorage()

  const [isOpen, setIsOpen] = useState(false)
  const [currentStep, setCurrentStep] = useState(0)

  const [newProject, setNewProject] = useState(config?.project === undefined)
  const [projectName, setProjectName] = useState(editorConfig?.name || '')
  const [projectDescription, setProjectDescription] = useState(
    editorConfig?.description || ''
  )

  const [newBranch, setNewBranch] = useState(false)
  const [branchName, setBranchName] = useState('')
  const [selectedBranch, setSelectedBranch] = useState(config?.branchFrom)
  const branches = useBranches(config?.project?.id)

  const [commitMessage, setCommitMessage] = useState('')

  const nav = useNavigate()

  const validateToken = useCallback(async (config: GitlabConfig) => {
    const result = await validateGitLabGroupToken(config, config.token)
    if (!result) {
      setIsOpen(false)
      nav('/editor/create/gitlab-settings')
    } else {
      setConfig(prev => ({ ...prev, groupId: result }))
    }
  }, [])

  useEffect(() => {
    if (!config || !isOpen) return
    validateToken(config)
  }, [config?.gitlabHost, config?.groupPath, config?.token, isOpen])

  useEffect(() => {
    if (!branches.length || branches.find(branch => branch === selectedBranch))
      return

    setSelectedBranch(branches[0])
  }, [branches, selectedBranch])

  const createNewProject = useCallback(async () => {
    if (!config) return
    try {
      const result = await createProject(
        config,
        projectName,
        projectDescription,
        config.token ? 'private' : 'public'
      )
      setConfig(prev => ({ ...prev, project: result }))
      setEditorConfig(prev => ({ ...prev, fileNames: undefined }))
      nextStep()
    } catch (error) {
      notify((error as Error).message, JSON.stringify(error), {
        intent: 'danger',
      })
    }
  }, [config, projectName, projectDescription, currentStep])

  const createNewBranch = useCallback(async () => {
    if (!config) return
    try {
      await createBranch(config, config.branchFrom || 'main', branchName)
      nextStep()
    } catch (error) {
      notify((error as Error).message, JSON.stringify(error), {
        intent: 'danger',
      })
    }
  }, [config, branchName])

  const createNewCommit = useCallback(async () => {
    if (!config || !editorConfig) return
    try {
      const fileMapping = await generateFileContents(editorConfig)
      const newFileNames = fileMapping.map(file => file.name)
      const actions = await createGitlabActionStructure(
        fileMapping,
        editorConfig.fileNames
      )
      await commitFiles(
        config,
        newBranch ? branchName : selectedBranch || '',
        actions,
        commitMessage || 'Update definition files'
      )
      notifyNoncommmit('Definition commited')
      setCurrentStep(STEPS.PROJECT)
      setConfig(prev => ({
        ...prev,
        branchFrom: newBranch ? branchName : selectedBranch,
      }))
      setEditorConfig(prev => ({ ...prev, fileNames: newFileNames }))
      setIsOpen(false)
    } catch (error) {
      notify((error as Error).message, JSON.stringify(error), {
        intent: 'danger',
      })
    }
  }, [
    config,
    editorConfig,
    newBranch,
    selectedBranch,
    branchName,
    commitMessage,
  ])

  const nextStep = useCallback(() => setCurrentStep(prev => prev + 1), [])
  const prevStep = useCallback(() => setCurrentStep(prev => prev - 1), [])

  const handleNextButton = useCallback(() => {
    switch (currentStep) {
      case STEPS.PROJECT:
        if (!newProject) nextStep()
        else createNewProject()
        return
      case STEPS.BRANCH:
        if (!newBranch) nextStep()
        else createNewBranch()
        return
      case STEPS.COMMIT:
        createNewCommit()
        return
    }
  }, [
    currentStep,
    newProject,
    newBranch,
    createNewProject,
    createNewBranch,
    createNewCommit,
  ])

  const nextButtonText = useMemo(
    () =>
      currentStep === STEPS.COMMIT ? 'Commit' : GENERIC_CONTENT.buttons.next,
    [currentStep]
  )
  const projectUnfilled = useMemo(
    () =>
      currentStep === STEPS.PROJECT &&
      newProject &&
      (!projectName || !projectDescription),
    [currentStep, newProject, projectName, projectDescription]
  )
  const branchUnfilled = useMemo(
    () => currentStep === STEPS.BRANCH && newBranch && !branchName,
    [currentStep, newBranch, branchName]
  )
  const nextDisabled = useMemo(
    () => projectUnfilled || branchUnfilled,
    [projectUnfilled, branchUnfilled]
  )

  const projectStep = useMemo(
    () => (
      <ProjectPanel
        newProject={newProject}
        projectName={projectName}
        projectDescription={projectDescription}
        onNewProjectChange={value => setNewProject(value)}
        onProjectNameChange={value => setProjectName(value)}
        onProjectDescriptionChange={value => setProjectDescription(value)}
      />
    ),
    [newProject, projectName, projectDescription]
  )

  const branchStep = useMemo(
    () => (
      <BranchPanel
        branches={branches}
        newBranch={newBranch}
        branchName={branchName}
        selectedBranch={selectedBranch}
        onNewBranchChange={value => setNewBranch(value)}
        onBranchNameChange={value => setBranchName(value)}
        onSelectedBranchChange={value => setSelectedBranch(value)}
      />
    ),
    [branches, newBranch, branchName, selectedBranch]
  )

  const commitStep = useMemo(
    () => (
      <CommitPanel
        commitMessage={commitMessage}
        onCommitMessageChange={value => setCommitMessage(value)}
      />
    ),
    [commitMessage]
  )

  const step = useMemo(() => {
    switch (currentStep) {
      case STEPS.PROJECT:
        return projectStep
      case STEPS.BRANCH:
        return branchStep
      default:
        return commitStep
    }
  }, [currentStep, projectStep, branchStep, commitStep])

  return (
    <>
      <Button
        text='Save to Gitlab'
        icon='git-commit'
        onClick={() => setIsOpen(true)}
      />
      <Dialog
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        title='Commit definition'
        icon='git-commit'
      >
        {step}
        <DialogFooter
          actions={
            <ButtonGroup>
              {currentStep > 0 && (
                <Button
                  onClick={() => prevStep()}
                  icon='arrow-left'
                  text={GENERIC_CONTENT.buttons.back}
                />
              )}
              <Button
                disabled={nextDisabled}
                onClick={handleNextButton}
                intent='primary'
                icon='arrow-right'
                text={nextButtonText}
              />
            </ButtonGroup>
          }
        />
      </Dialog>
    </>
  )
}
export default memo(CommitDefinition)
