import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useRef,
  useState
} from 'react'
import PropTypes from 'prop-types'
import { Button, Modal, Space, Typography, Flex } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { version as appVersion } from '../../../../package.json'
import { ApiServerContext } from './ApiServerContext'
import { AuthContext } from './AuthContext'
import { ElectronContext } from './ElectronContext'
import NewAppUpdate from '../Management/AppUpdates/NewAppUpdate'
import AppUpdateProgress from '../Management/AppUpdates/AppUpdateProgress'
import SoftwareUpdateIcon from '../../Icons/SoftwareUpdateIcon'

const { Text } = Typography

const UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1000
const DEFAULT_UPDATE_BRANCH = 'main'
const CURRENT_BUILD_NUMBER = import.meta.env.VITE_BUILD_NUMBER
const APP_UPDATE_DISMISSED_KEY = 'appUpdateDismissed'

const getDismissedUpdate = () => {
  try {
    const stored = sessionStorage.getItem(APP_UPDATE_DISMISSED_KEY)
    return stored ? JSON.parse(stored) : null
  } catch {
    return null
  }
}

const isUpdateDismissed = (update) => {
  if (!update) return false

  const dismissed = getDismissedUpdate()
  if (!dismissed) return false

  return (
    dismissed.version === update.version &&
    dismissed.buildNumber === update.buildNumber &&
    dismissed.branch === update.branch
  )
}

const saveDismissedUpdate = (update) => {
  if (!update) return

  sessionStorage.setItem(
    APP_UPDATE_DISMISSED_KEY,
    JSON.stringify({
      version: update.version,
      buildNumber: update.buildNumber,
      branch: update.branch
    })
  )
}

const AppUpdateContext = createContext()

// eslint-disable-next-line react-refresh/only-export-components
export const compareVersionNumbers = (leftVersion, rightVersion) => {
  const leftParts = String(leftVersion || '0.0.0')
    .split('.')
    .map((part) => Number.parseInt(part, 10) || 0)
  const rightParts = String(rightVersion || '0.0.0')
    .split('.')
    .map((part) => Number.parseInt(part, 10) || 0)
  const length = Math.max(leftParts.length, rightParts.length)

  for (let index = 0; index < length; index += 1) {
    const leftPart = leftParts[index] || 0
    const rightPart = rightParts[index] || 0

    if (leftPart > rightPart) return 1
    if (leftPart < rightPart) return -1
  }

  return 0
}

const normalizeBuildNumber = (buildNumber) => {
  const parsedBuildNumber = Number.parseInt(buildNumber, 10)
  return Number.isNaN(parsedBuildNumber) ? 0 : parsedBuildNumber
}

// eslint-disable-next-line react-refresh/only-export-components
export const isAppUpdateAvailable = (update, currentVersion, currentBuild) => {
  if (!update) return false

  const versionComparison = compareVersionNumbers(
    update.version,
    currentVersion
  )

  if (versionComparison > 0) return true
  if (versionComparison < 0) return false

  return (
    normalizeBuildNumber(update.buildNumber) >
    normalizeBuildNumber(currentBuild)
  )
}

export const AppUpdateProvider = ({ children }) => {
  const { fetchAppUpdateBranches, fetchAppUpdateCurrent } =
    useContext(ApiServerContext)
  const { token } = useContext(AuthContext)
  const { isElectron, getAppSettings, startAppUpdate, onAppUpdateProgress } =
    useContext(ElectronContext)
  const [checking, setChecking] = useState(false)
  const [noUpdateOpen, setNoUpdateOpen] = useState(false)
  const [availableUpdate, setAvailableUpdate] = useState(null)
  const [updatePromptOpen, setUpdatePromptOpen] = useState(false)
  const [installingUpdate, setInstallingUpdate] = useState(null)
  const [updateProgress, setUpdateProgress] = useState(null)
  const runningCheckRef = useRef(null)
  const updateCheckDependenciesRef = useRef({})

  const [modelWidth, setModelWidth] = useState(650)

  updateCheckDependenciesRef.current = {
    fetchAppUpdateBranches,
    fetchAppUpdateCurrent,
    getAppSettings,
    isElectron,
    token
  }

  const checkForAvailableUpdate = useCallback(async () => {
    const {
      fetchAppUpdateBranches,
      fetchAppUpdateCurrent,
      getAppSettings,
      isElectron,
      token
    } = updateCheckDependenciesRef.current

    if (!isElectron || !token) return null
    if (runningCheckRef.current) return runningCheckRef.current

    const checkPromise = (async () => {
      const [branches, appSettings] = await Promise.all([
        fetchAppUpdateBranches(),
        getAppSettings()
      ])
      const configuredBranch = appSettings?.appUpdateBranch
      const defaultBranch = branches.includes(DEFAULT_UPDATE_BRANCH)
        ? DEFAULT_UPDATE_BRANCH
        : branches[0]
      const selectedBranch = branches.includes(configuredBranch)
        ? configuredBranch
        : defaultBranch

      if (!selectedBranch) return null

      const update = await fetchAppUpdateCurrent(selectedBranch)

      return isAppUpdateAvailable(update, appVersion, CURRENT_BUILD_NUMBER)
        ? update
        : null
    })()

    runningCheckRef.current = checkPromise

    try {
      const update = await checkPromise
      return update
    } finally {
      runningCheckRef.current = null
    }
  }, [])

  const showUpdateIfAvailable = useCallback(
    async ({ forcePrompt = false } = {}) => {
      const update = await checkForAvailableUpdate()
      if (update) {
        setNoUpdateOpen(false)
        setAvailableUpdate(update)
        if (forcePrompt || !isUpdateDismissed(update)) {
          setUpdatePromptOpen(true)
        }
      }
      return update
    },
    [checkForAvailableUpdate]
  )

  const checkForUpdates = useCallback(async () => {
    if (!isElectron) return null

    setChecking(true)

    try {
      const update = await showUpdateIfAvailable({ forcePrompt: true })
      if (!update) {
        setNoUpdateOpen(true)
      }
      return update
    } finally {
      setChecking(false)
    }
  }, [isElectron, showUpdateIfAvailable])

  useEffect(() => {
    if (!isElectron) return undefined

    showUpdateIfAvailable()

    const interval = window.setInterval(() => {
      showUpdateIfAvailable()
    }, UPDATE_CHECK_INTERVAL_MS)

    return () => {
      window.clearInterval(interval)
    }
  }, [isElectron, showUpdateIfAvailable])

  useEffect(() => {
    if (!isElectron || !onAppUpdateProgress) return undefined

    return onAppUpdateProgress((progress) => {
      setUpdateProgress(progress)
    })
  }, [isElectron, onAppUpdateProgress])

  const dismissUpdatePrompt = () => {
    if (availableUpdate) {
      saveDismissedUpdate(availableUpdate)
    }
    setUpdatePromptOpen(false)
  }

  const closeUpdateModal = () => {
    setUpdatePromptOpen(false)
    setInstallingUpdate(null)
    setUpdateProgress(null)
  }

  const handleUpdate = async (update) => {
    setNoUpdateOpen(false)
    setUpdatePromptOpen(false)
    setInstallingUpdate(update)
    setModelWidth(550)
    setUpdateProgress({
      phase: 'preparing',
      percent: 0,
      message: 'Preparing update'
    })

    try {
      const result = await startAppUpdate(update)

      if (!result) {
        throw new Error(
          'Failed to start the app update. Please restart the app and try again.'
        )
      }
    } catch (error) {
      setUpdateProgress({
        phase: 'error',
        percent: null,
        message: error?.message || 'Failed to start the app update.'
      })
    }
  }

  const updateModalOpen = Boolean(updatePromptOpen || installingUpdate)
  const updateModalBusy =
    Boolean(installingUpdate) && updateProgress?.phase !== 'error'

  return (
    <AppUpdateContext.Provider value={{ availableUpdate, checkForUpdates }}>
      {children}
      <Modal
        open={checking}
        className='loading-modal'
        title={false}
        height={20}
        style={{ maxWidth: 260, top: '50%', transform: 'translateY(-50%)' }}
        closable={false}
        maskClosable={false}
        footer={false}
      >
        <Space size='middle'>
          <LoadingOutlined />
          <Text style={{ margin: 0 }}>Checking for updates...</Text>
        </Space>
      </Modal>
      <Modal
        title='Software Update'
        open={noUpdateOpen}
        okText='OK'
        style={{ maxWidth: 430 }}
        centered
        maskClosable
        onOk={() => setNoUpdateOpen(false)}
        onCancel={() => setNoUpdateOpen(false)}
        footer={[
          <Button
            key='ok'
            type='primary'
            onClick={() => setNoUpdateOpen(false)}
          >
            OK
          </Button>
        ]}
      >
        <Text>There are no new software updates available.</Text>
      </Modal>
      <Modal
        title={
          <Flex align='center' gap='middle'>
            <SoftwareUpdateIcon style={{ fontSize: 18 }} />
            {installingUpdate ? 'Software Update' : 'Software Update Available'}
          </Flex>
        }
        open={updateModalOpen}
        footer={null}
        width={modelWidth}
        centered
        closable={!updateModalBusy}
        maskClosable={!updateModalBusy}
        onCancel={
          updateModalBusy
            ? undefined
            : installingUpdate
              ? closeUpdateModal
              : dismissUpdatePrompt
        }
      >
        {installingUpdate ? (
          <AppUpdateProgress
            progress={updateProgress}
            update={installingUpdate}
            onClose={closeUpdateModal}
          />
        ) : (
          <NewAppUpdate
            update={availableUpdate}
            onCancel={dismissUpdatePrompt}
            onUpdate={handleUpdate}
          />
        )}
      </Modal>
    </AppUpdateContext.Provider>
  )
}

AppUpdateProvider.propTypes = {
  children: PropTypes.node.isRequired
}

// eslint-disable-next-line react-refresh/only-export-components
export const useAppUpdateContext = () => {
  const context = useContext(AppUpdateContext)
  if (!context) {
    throw new Error(
      'useAppUpdateContext must be used within an AppUpdateProvider'
    )
  }
  return context
}

export { AppUpdateContext }
