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'
import ExclamationOctogonIcon from '../../Icons/ExclamationOctagonIcon'

const { Text } = Typography

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

// eslint-disable-next-line react-refresh/only-export-components
export const normalizeAppUpdateEngine = (engine) => {
  const value = String(engine || '')
    .trim()
    .toLowerCase()

  if (value === 'chromium' || value === 'cef') return 'chromium'
  if (value === 'native') return 'native'
  return null
}

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 &&
    normalizeAppUpdateEngine(dismissed.engine) ===
      normalizeAppUpdateEngine(update.engine)
  )
}

const formatFullAppVersion = (version, buildNumber) => {
  const normalizedVersion = String(version || '')
    .trim()
    .replace(/^v/i, '')

  if (!normalizedVersion) return null

  const build = String(buildNumber || '').trim()
  const buildSuffix =
    !build || build === 'dev'
      ? 'dev'
      : build.startsWith('b')
        ? build
        : `b${build}`

  return `v${normalizedVersion}-${buildSuffix}`
}

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

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

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,
    setAppSettings,
    getAppEngine,
    startAppUpdate,
    checkAppUpdateResult,
    checkDuplicateInstallations,
    removeDuplicateInstallations,
    onDuplicateInstallationsRemoved,
    onAppUpdateProgress,
    onCheckForUpdatesRequest
  } = 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 [completedUpdate, setCompletedUpdate] = useState(null)
  const [duplicateInstall, setDuplicateInstall] = useState(null)
  const [duplicatePromptOpen, setDuplicatePromptOpen] = useState(false)
  const [removingDuplicate, setRemovingDuplicate] = useState(false)
  const [duplicateRemovalResult, setDuplicateRemovalResult] = useState(null)
  const runningCheckRef = useRef(null)
  const updateCheckDependenciesRef = useRef({})

  const [modelWidth, setModelWidth] = useState(DEFAULT_MODEL_WIDTH)

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

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

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

    const checkPromise = (async () => {
      const [branches, appSettings, runningEngine] = await Promise.all([
        fetchAppUpdateBranches(),
        getAppSettings(),
        getAppEngine()
      ])
      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 selectedEngine =
        normalizeAppUpdateEngine(appSettings?.appUpdateEngine) ||
        normalizeAppUpdateEngine(runningEngine) ||
        DEFAULT_UPDATE_ENGINE
      const currentRunningEngine =
        normalizeAppUpdateEngine(runningEngine) || DEFAULT_UPDATE_ENGINE

      const settingsUpdates = {}
      if (!appSettings?.appUpdateRunningBranch) {
        settingsUpdates.appUpdateRunningBranch = selectedBranch
      }
      if (!normalizeAppUpdateEngine(appSettings?.appUpdateEngine)) {
        settingsUpdates.appUpdateEngine = selectedEngine
      }
      if (Object.keys(settingsUpdates).length > 0) {
        await setAppSettings({
          ...appSettings,
          ...settingsUpdates
        })
      }

      const runningBranch =
        appSettings?.appUpdateRunningBranch ||
        settingsUpdates.appUpdateRunningBranch ||
        selectedBranch

      const update = await fetchAppUpdateCurrent(selectedBranch)
      if (!update) return null

      const newerVersionAvailable = isAppUpdateAvailable(
        update,
        appVersion,
        CURRENT_BUILD_NUMBER
      )
      const engineMismatch = selectedEngine !== currentRunningEngine
      const branchMismatch = selectedBranch !== runningBranch

      if (!newerVersionAvailable && !engineMismatch && !branchMismatch) {
        return null
      }

      return {
        ...update,
        branch: update.branch || selectedBranch,
        engine: selectedEngine
      }
    })()

    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)) {
          setModelWidth(DEFAULT_MODEL_WIDTH)
          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])

  const recheckForUpdates = useCallback(async () => {
    if (!isElectron) return null
    return showUpdateIfAvailable({ forcePrompt: true })
  }, [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) return undefined

    let cancelled = false

    const runStartupChecks = async () => {
      let result = null

      try {
        result = await checkAppUpdateResult?.()
        if (!cancelled && result?.updated) {
          setCompletedUpdate(result)
        }
      } catch (error) {
        console.warn('[AppUpdateContext] Startup update check failed:', error)
      }

      try {
        const installations =
          result?.duplicates || (await checkDuplicateInstallations?.())
        if (!cancelled && installations?.duplicatePath) {
          setDuplicateInstall(installations)
          setDuplicatePromptOpen(true)
        }
      } catch (error) {
        console.warn(
          '[AppUpdateContext] Duplicate installation check failed:',
          error
        )
      }
    }

    void runStartupChecks()

    return () => {
      cancelled = true
    }
  }, [isElectron, checkAppUpdateResult, checkDuplicateInstallations])

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

    return onDuplicateInstallationsRemoved((result) => {
      setRemovingDuplicate(false)
      setDuplicateRemovalResult(result || { ok: false })
    })
  }, [isElectron, onDuplicateInstallationsRemoved])

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

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

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

    return onCheckForUpdatesRequest(() => {
      void checkForUpdates()
    })
  }, [isElectron, onCheckForUpdatesRequest, checkForUpdates])

  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(580)
    setUpdateProgress({
      phase: 'preparing',
      percent: 0,
      message: 'Preparing update'
    })

    try {
      const appSettings = await getAppSettings()
      const engine =
        normalizeAppUpdateEngine(update?.engine) ||
        normalizeAppUpdateEngine(appSettings?.appUpdateEngine) ||
        DEFAULT_UPDATE_ENGINE
      const result = await startAppUpdate({
        ...update,
        engine
      })

      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 handleRemoveDuplicate = async () => {
    setRemovingDuplicate(true)
    setDuplicateRemovalResult(null)

    const started = await removeDuplicateInstallations?.()
    if (!started) {
      setRemovingDuplicate(false)
      setDuplicateRemovalResult({
        ok: false,
        error: 'Failed to start removing the duplicate installation.'
      })
    }
  }

  const closeDuplicatePrompt = () => {
    setDuplicatePromptOpen(false)
    setDuplicateRemovalResult(null)
  }

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

  return (
    <AppUpdateContext.Provider
      value={{ availableUpdate, checkForUpdates, recheckForUpdates }}
    >
      {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={
          <Flex align='center' gap='middle'>
            <SoftwareUpdateIcon style={{ fontSize: 18 }} />
            Software Update
          </Flex>
        }
        open={noUpdateOpen}
        okText='OK'
        style={{ maxWidth: 430 }}
        centered
        maskClosable
        closable={false}
        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>
      <Modal
        title={
          <Flex align='center' gap='middle'>
            <SoftwareUpdateIcon style={{ fontSize: 18 }} />
            Update Installed
          </Flex>
        }
        open={Boolean(completedUpdate)}
        style={{ maxWidth: 430 }}
        centered
        closable={false}
        onCancel={() => setCompletedUpdate(null)}
        footer={[
          <Button
            key='ok'
            type='primary'
            onClick={() => setCompletedUpdate(null)}
          >
            OK
          </Button>
        ]}
      >
        <Text>
          {completedUpdate?.message ||
            `Farm Control was successfully updated to version ${
              completedUpdate?.current?.version ||
              formatFullAppVersion(appVersion, CURRENT_BUILD_NUMBER) ||
              `v${appVersion}`
            }.`}
        </Text>
      </Modal>
      <Modal
        title={
          <Flex align='center' gap='middle'>
            <ExclamationOctogonIcon />
            Duplicate Installation Found
          </Flex>
        }
        open={Boolean(duplicatePromptOpen && duplicateInstall)}
        width={!removingDuplicate && !duplicateRemovalResult ? 560 : 430}
        centered
        closable={false}
        maskClosable={false}
        onCancel={removingDuplicate ? undefined : closeDuplicatePrompt}
        footer={
          removingDuplicate
            ? null
            : duplicateRemovalResult
              ? [
                  <Button
                    key='close'
                    type='primary'
                    onClick={closeDuplicatePrompt}
                  >
                    OK
                  </Button>
                ]
              : [
                  <Button key='no' onClick={closeDuplicatePrompt}>
                    No
                  </Button>,
                  <Button
                    key='yes'
                    type='primary'
                    onClick={handleRemoveDuplicate}
                  >
                    Yes
                  </Button>
                ]
        }
      >
        {removingDuplicate ? (
          <Space size='middle'>
            <LoadingOutlined />
            <Text>
              Removing the duplicate installation... You may be asked for an
              administrator password.
            </Text>
          </Space>
        ) : duplicateRemovalResult ? (
          <Text>
            {duplicateRemovalResult.ok
              ? 'The duplicate installation was removed.'
              : duplicateRemovalResult.error ||
                'Failed to remove the duplicate installation.'}
          </Text>
        ) : (
          <Space direction='vertical' size='small'>
            <Text>
              Farm Control is now installed in your user applications folder,
              but an older copy is still installed at:{' '}
              <Text code>{duplicateInstall?.duplicatePath}</Text>.{' '}
              <Text>Do you want to remove the duplicate installation?</Text>
            </Text>
          </Space>
        )}
      </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 }
