import { useEffect, useState, useContext, useRef } from 'react'
import { Flex, Tag, Card, Typography, Skeleton, Badge } from 'antd'
import PropTypes from 'prop-types'
import { getModelByName } from '../../../database/ObjectModels'
import { ApiServerContext } from '../context/ApiServerContext'
import { AuthContext } from '../context/AuthContext'
import { round } from '../utils/Utils'

const { Text } = Typography

/**
 * Maps stat names to Tag colors for visual indication
 */
const getTagColor = (statName) => {
  const name = statName.toLowerCase()

  // Success states
  if (
    name.includes('complete') ||
    name.includes('ready') ||
    name.includes('standby') ||
    name.includes('success')
  ) {
    return 'success'
  }

  // Error states
  if (
    name.includes('error') ||
    name.includes('failed') ||
    name.includes('cancelled')
  ) {
    return 'error'
  }

  // Warning states
  if (
    name.includes('queued') ||
    name.includes('pending') ||
    name.includes('waiting')
  ) {
    return 'warning'
  }

  if (name.includes('printing')) {
    return 'processing'
  }

  // Default states
  return 'default'
}

/*i*
 * Gets a nested value from an object using dot notation
 * e.g., getNestedValue(obj, 'states.ready') -> obj.states.ready
 */
const getNestedValue = (obj, path) => {
  if (!obj || !path) return undefined
  const keys = path.split('.')
  let value = obj
  for (const key of keys) {
    if (value === null || value === undefined) return undefined
    value = value[key]
  }
  return value
}

/**
 * Extracts numeric value from a rollup object
 * Handles both old format (number) and new format ({count: 0}, {sum: 100}, {avg: 5.5})
 */
const extractRollupValue = (value) => {
  if (typeof value === 'number') {
    return value
  }
  if (value && typeof value === 'object') {
    // Handle new format: {count: 0}, {sum: 100}, {avg: 5.5}
    const operationKeys = ['count', 'sum', 'avg']
    for (const key of operationKeys) {
      if (value[key] !== undefined) {
        return value[key]
      }
    }
    // Fallback: return first numeric value found
    const firstValue = Object.values(value)[0]
    return typeof firstValue === 'number' ? firstValue : 0
  }
  return 0
}

/**
 * Gets the stat value from the stats data object
 * Handles both flattened and nested structures, including dot notation paths
 * Now also handles new format with operation objects ({count: 0}, {sum: 100}, etc.)
 */
const getStatValue = (stats, statName, statDef) => {
  if (!stats) return 0

  // Handle special combined stats (support both 'combine' and 'sum' properties)
  const combineList = statDef.combine || statDef.sum
  if (combineList) {
    return combineList.reduce((sum, name) => {
      const value = getStatValue(stats, name, {})
      return sum + (value || 0)
    }, 0)
  }

  // Handle dot notation paths (e.g., "states.ready")
  if (statName.includes('.')) {
    const value = getNestedValue(stats, statName)
    return extractRollupValue(value)
  }

  // Try direct access first (flattened structure)
  if (stats[statName] !== undefined) {
    return extractRollupValue(stats[statName])
  }

  // Try nested structure (stats.states.statName)
  if (stats.states && stats.states[statName] !== undefined) {
    return extractRollupValue(stats.states[statName])
  }

  // Try with objectType prefix removed (e.g., "standbyPrinters" -> "standby")
  const simplifiedName = statName.replace(
    /^(standby|printing|error|offline|queued|complete|failed|cancelled|draft)/i,
    (match) => {
      return match.toLowerCase()
    }
  )
  if (stats[simplifiedName] !== undefined) {
    return extractRollupValue(stats[simplifiedName])
  }

  if (stats.states && stats.states[simplifiedName] !== undefined) {
    return extractRollupValue(stats.states[simplifiedName])
  }

  return 0
}

/**
 * Extracts the base stat name from model stat names
 * e.g., "standbyPrinters" -> "standby", "printingPrinters" -> "printing"
 * Preserves dot notation paths (e.g., "states.ready" -> "states.ready")
 */
const extractBaseStatName = (statName) => {
  // If it's a dot notation path, return as-is
  if (statName.includes('.')) {
    return statName
  }

  // Remove common suffixes
  const baseName = statName
    .replace(/Printers?$/i, '')
    .replace(/Jobs?$/i, '')
    .toLowerCase()

  return baseName
}

const StatsDisplay = ({ objectType, stats: statsProp }) => {
  const { getModelStats, connected, subscribeToModelStats } =
    useContext(ApiServerContext)
  const { token } = useContext(AuthContext)
  const [stats, setStats] = useState(statsProp || null)
  const [loading, setLoading] = useState(!statsProp)
  const [error, setError] = useState(null)
  const initializedRef = useRef(false)

  useEffect(() => {
    // If stats are provided as prop, use them and don't fetch
    if (statsProp) {
      setStats(statsProp)
      setLoading(false)
      initializedRef.current = true
      return
    }

    // Otherwise, fetch stats
    if (
      !objectType ||
      !getModelStats ||
      !token ||
      !connected ||
      initializedRef.current == true
    ) {
      return
    }

    const fetchStats = async () => {
      try {
        setLoading(true)
        setError(null)
        const fetchedStats = await getModelStats(objectType)
        if (fetchedStats) {
          setStats(fetchedStats)
        } else {
          setError('Failed to fetch stats')
        }
      } catch (err) {
        console.error('Error fetching stats:', err)
        setError('Failed to fetch stats')
      } finally {
        setLoading(false)
        initializedRef.current = true
      }
    }

    fetchStats()
  }, [objectType, getModelStats, token, connected, statsProp])

  // Subscribe to real-time stats updates
  useEffect(() => {
    // Only subscribe if stats are not provided as prop and we have the required dependencies
    if (!objectType || !connected || !subscribeToModelStats) {
      return
    }

    const statsUnsubscribe = subscribeToModelStats(
      objectType,
      (updatedStats) => {
        setStats(updatedStats)
      }
    )

    return () => {
      if (statsUnsubscribe) statsUnsubscribe()
    }
  }, [objectType, connected, subscribeToModelStats, statsProp])

  if (!objectType) {
    return null
  }

  const model = getModelByName(objectType)

  if (!model) {
    return null
  }

  const Icon = model.icon
  const modelStats = model.stats || []

  if (modelStats.length === 0) {
    return null
  }

  if (error) {
    return null
  }

  return (
    <Flex justify='flex-start' gap='middle' wrap='wrap' align='flex-start'>
      {modelStats.map((statDef) => {
        const baseStatName = extractBaseStatName(statDef.name)
        var statValue = getStatValue(stats, baseStatName, statDef)
        const tagColor = statDef.color || getTagColor(statDef.name)
        const label = statDef.label || statDef.name

        if (statDef?.roundNumber) {
          statValue = round(statValue, statDef?.roundNumber)
        }

        const statusColors = [
          'success',
          'warning',
          'error',
          'processing',
          'default'
        ]

        var badgeProps = {
          status: tagColor
        }

        if (!statusColors.includes(tagColor)) {
          badgeProps = { color: tagColor }
        }

        const content = (
          <Flex vertical gap='3px'>
            <Flex gap={'12px'} align='center'>
              <Badge {...badgeProps} />
              <Text>{label}</Text>
            </Flex>
            <Flex gap={'12px'} align='center'>
              {Icon && (
                <Text>
                  <Icon style={{ fontSize: 26 }} />
                </Text>
              )}
              {loading ? (
                <Flex justify='center' align='center' style={{ height: 44 }}>
                  <Skeleton.Button
                    active
                    size='small'
                    style={{ width: 50, height: 30 }}
                  />
                </Flex>
              ) : (
                <Text style={{ fontSize: 28, fontWeight: 600 }}>
                  {statDef?.prefix}
                  {statValue}
                  {statDef?.suffix}
                </Text>
              )}
            </Flex>
          </Flex>
        )

        if (tagColor === 'default') {
          return (
            <Card
              key={statDef.name}
              style={{ minWidth: statDef?.cardWidth || 175 }}
              styles={{ body: { padding: '16px 24px' } }}
            >
              {content}
            </Card>
          )
        }

        return (
          <Tag
            key={statDef.name}
            color={tagColor}
            style={{
              minWidth: statDef?.cardWidth || 175,
              padding: '16px 24px',
              margin: 0
            }}
          >
            {content}
          </Tag>
        )
      })}
    </Flex>
  )
}

StatsDisplay.propTypes = {
  objectType: PropTypes.string.isRequired,
  stats: PropTypes.object // Optional - if not provided, will fetch automatically
}

export default StatsDisplay
