import PropTypes from 'prop-types'
import { useEffect, useState } from 'react'
import { Button, Flex, Modal, Typography, theme, Divider } from 'antd'

import CloudIcon from '../../../Icons/CloudIcon'
import HostIcon from '../../../Icons/HostIcon'
import ReloadIcon from '../../../Icons/ReloadIcon'
import HProgress from '../../common/HProgress'

import CheckCircleIcon from '../../../Icons/CheckCircleIcon'
import XMarkCircleIcon from '../../../Icons/XMarkCircleIcon'

const { Text } = Typography

const formatBytes = (bytes) => {
  if (!Number.isFinite(bytes) || bytes <= 0) return null

  const units = ['B', 'KB', 'MB', 'GB']
  let value = bytes
  let unitIndex = 0

  while (value >= 1024 && unitIndex < units.length - 1) {
    value /= 1024
    unitIndex += 1
  }

  return `${value.toFixed(value >= 10 || unitIndex === 0 ? 0 : 1)} ${
    units[unitIndex]
  }`
}

const STAGE_CONFIG = {
  download: {
    icon: CloudIcon,
    labels: {
      pending: 'Download',
      active: 'Downloading...',
      complete: 'Downloaded',
      error: 'Download failed'
    }
  },
  install: {
    icon: HostIcon,
    labels: {
      pending: 'Install',
      active: 'Installing...',
      complete: 'Installed',
      error: 'Install failed'
    }
  },
  restart: {
    icon: ReloadIcon,
    labels: {
      pending: 'Restart',
      active: 'Restarting...',
      complete: 'Restarted',
      error: 'Restart failed'
    }
  }
}

const getStageColor = (status, token) => {
  if (status === 'complete') return token.colorSuccess
  if (status === 'active') return token.colorPrimary
  if (status === 'error') return token.colorError
  return token.colorTextQuaternary
}

const STAGE_INDEX = { download: 0, install: 1, restart: 2 }

const isCompletionMessage = (message) => {
  const normalized = String(message || '').toLowerCase()

  return (
    normalized.includes('complete') ||
    normalized.includes('successful') ||
    normalized.includes('restarting')
  )
}

// Maps a progress event to the stage that should currently be active.
// Returns null when the event carries no stage information (e.g. errors),
// so the previously reached stage is kept.
const getStageIndexFromProgress = (phase, percent, message) => {
  if (phase === 'restarting') return STAGE_INDEX.restart

  if (phase === 'installing') {
    return percent >= 100 || isCompletionMessage(message)
      ? STAGE_INDEX.restart
      : STAGE_INDEX.install
  }

  if (phase === 'downloaded') return STAGE_INDEX.install

  if (phase === 'preparing' || phase === 'downloading') {
    return phase === 'downloading' && percent >= 100
      ? STAGE_INDEX.install
      : STAGE_INDEX.download
  }

  return null
}

const getStageStatus = (stageIndex, activeIndex, isError) => {
  if (stageIndex < activeIndex) return 'complete'
  if (stageIndex > activeIndex) return 'pending'
  return isError ? 'error' : 'active'
}

const getProgressStatus = (stageStatus) => {
  if (stageStatus === 'error') return 'exception'
  if (stageStatus === 'complete') return 'success'
  return 'active'
}

const UpdateStage = ({ stage, status, percent, detail }) => {
  const { token } = theme.useToken()
  const config = STAGE_CONFIG[stage]
  const StageIcon = config.icon
  const resolvedPercent =
    typeof percent === 'number' ? Math.min(percent, 100) : undefined
  const color = getStageColor(status, token)
  const showProgress = status === 'active' && stage !== 'restart'

  const StatusIcon =
    status === 'complete'
      ? CheckCircleIcon
      : status === 'error'
        ? XMarkCircleIcon
        : StageIcon

  return (
    <Flex align='start' gap='middle' style={{ width: '100%' }}>
      <StatusIcon style={{ fontSize: 22, color, flexShrink: 0 }} />
      <Flex align='start' gap='24px' style={{ flex: 1, minWidth: 0 }}>
        <Text style={{ flexShrink: 0 }}>{config.labels[status]}</Text>
        {showProgress && (
          <Flex vertical gap={2} style={{ flex: 1, minWidth: 0 }}>
            <HProgress
              percent={resolvedPercent}
              status={getProgressStatus(status)}
              showInfo={typeof resolvedPercent === 'number'}
              style={{ flex: 1, margin: 0 }}
            />
            {detail && (
              <Text type='secondary' ellipsis style={{ minWidth: 0 }}>
                {detail}
              </Text>
            )}
          </Flex>
        )}
      </Flex>
    </Flex>
  )
}

UpdateStage.propTypes = {
  stage: PropTypes.oneOf(['download', 'install', 'restart']).isRequired,
  status: PropTypes.oneOf(['pending', 'active', 'complete', 'error'])
    .isRequired,
  percent: PropTypes.number,
  detail: PropTypes.string
}

const AppUpdateProgress = ({ progress, update, onClose }) => {
  const phase = progress?.phase || 'preparing'
  const percent =
    typeof progress?.percent === 'number'
      ? Math.min(progress.percent, 100)
      : null
  const downloaded = formatBytes(progress?.downloadedBytes)
  const total = formatBytes(progress?.totalBytes)
  const message = progress?.message || 'Preparing update'
  const isError = phase === 'error'

  const [errorModalOpen, setErrorModalOpen] = useState(true)

  // Track the furthest stage reached so out-of-order or skipped progress
  // events can never move the steps backwards.
  const [activeStageIndex, setActiveStageIndex] = useState(STAGE_INDEX.download)

  useEffect(() => {
    const stageIndex = getStageIndexFromProgress(phase, percent, message)
    if (stageIndex !== null) {
      setActiveStageIndex((previous) => Math.max(previous, stageIndex))
    }
  }, [phase, percent, message])

  const downloadStatus = getStageStatus(
    STAGE_INDEX.download,
    activeStageIndex,
    isError
  )
  const installStatus = getStageStatus(
    STAGE_INDEX.install,
    activeStageIndex,
    isError
  )
  const restartStatus = getStageStatus(
    STAGE_INDEX.restart,
    activeStageIndex,
    isError
  )

  const downloadPercent =
    downloadStatus === 'active' && phase === 'downloading' ? percent : 0

  const installPercent =
    installStatus === 'active' && phase === 'installing' ? percent : null

  const downloadDetail =
    downloadStatus === 'active' && downloaded && total
      ? `${downloaded} of ${total}`
      : null

  const installDetail =
    installStatus === 'active' && phase === 'installing' ? message : null

  return (
    <Flex vertical gap='middle'>
      <Text>
        Updating Farm Control to version{' '}
        {update?.version ? `${update.version}` : 'unknown'} build{' '}
        {update?.buildNumber ? `${update.buildNumber}` : 'unknown'} from branch{' '}
        {update?.branch ? `${update.branch}` : 'unknown'}...
      </Text>

      <Divider style={{ margin: '4px 0' }} />
      <Flex vertical gap='middle'>
        <UpdateStage
          stage='download'
          status={downloadStatus}
          percent={downloadPercent}
          detail={downloadDetail}
        />
        <UpdateStage
          stage='install'
          status={installStatus}
          percent={installPercent}
          detail={installDetail}
        />
        <UpdateStage stage='restart' status={restartStatus} />
      </Flex>

      <Modal
        title='Update Failed'
        open={isError && errorModalOpen == true}
        centered
        closable={false}
        maskClosable={false}
        onCancel={() => {
          setErrorModalOpen(false)
          onClose()
        }}
        footer={[
          <Button
            key='close'
            onClick={() => {
              setErrorModalOpen(false)
              onClose()
            }}
          >
            Close
          </Button>
        ]}
      >
        <Text>{message}</Text>
      </Modal>
    </Flex>
  )
}

AppUpdateProgress.propTypes = {
  progress: PropTypes.shape({
    phase: PropTypes.string,
    percent: PropTypes.number,
    downloadedBytes: PropTypes.number,
    totalBytes: PropTypes.number,
    message: PropTypes.string,
    artifact: PropTypes.object
  }),
  update: PropTypes.object,
  onClose: PropTypes.func
}

export default AppUpdateProgress
