import {
  forwardRef,
  useImperativeHandle,
  useRef,
  useEffect,
  useState,
  useCallback,
  createElement
} from 'react'
import {
  Table,
  message,
  Skeleton,
  Card,
  Row,
  Col,
  Descriptions,
  Flex,
  Spin,
  Button,
  Input,
  Space,
  Tooltip
} from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import PropTypes from 'prop-types'
import { useMediaQuery } from 'react-responsive'
import { useContext } from 'react'
import { ApiServerContext } from '../context/ApiServerContext'
import config from '../../../config'
import loglevel from 'loglevel'
import {
  getModelProperties,
  getModelByName
} from '../../../database/ObjectModels'
import ObjectProperty from './ObjectProperty'
import XMarkIcon from '../../Icons/XMarkIcon'
import CheckIcon from '../../Icons/CheckIcon'
import { useNavigate } from 'react-router-dom'
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
import { AuthContext } from '../context/AuthContext'
import { ElectronContext } from '../context/ElectronContext'

const logger = loglevel.getLogger('DasboardTable')
logger.setLevel(config.logLevel)

const ObjectTable = forwardRef(
  (
    {
      type,
      pageSize = 25,
      scrollHeight = 'calc(var(--unit-100vh) - 270px)',
      onDataChange,
      initialPage = 1,
      cards = false,
      visibleColumns = {},
      masterFilter = {}
    },
    ref
  ) => {
    const { token } = useContext(AuthContext)
    const { isElectron } = useContext(ElectronContext)
    const {
      fetchObjects,
      connected,
      subscribeToObjectUpdates,
      subscribeToObjectTypeUpdates
    } = useContext(ApiServerContext)
    const isMobile = useMediaQuery({ maxWidth: 768 })
    const navigate = useNavigate()
    var adjustedScrollHeight = scrollHeight
    if (isMobile) {
      adjustedScrollHeight = 'calc(var(--unit-100vh) - 316px)'
    }
    if (cards) {
      adjustedScrollHeight = 'calc(var(--unit-100vh) - 280px)'
    }
    if (isElectron) {
      adjustedScrollHeight = 'calc(var(--unit-100vh) - 244px)'
    }
    if (isMobile && isElectron) {
      adjustedScrollHeight = 'calc(var(--unit-100vh) - 282px)'
    }
    if (cards && isElectron) {
      adjustedScrollHeight = 'calc(var(--unit-100vh) - 260px)'
    }
    const [, contextHolder] = message.useMessage()
    const tableRef = useRef(null)
    const model = getModelByName(type)
    const [tableFilter, setTableFilter] = useState({})
    const [tableSorter, setTableSorter] = useState({})
    const [initialized, setInitialized] = useState(false)

    // Table state
    const [pages, setPages] = useState([])
    const pagesRef = useRef(pages)
    const [hasMore, setHasMore] = useState(true)
    const [loading, setLoading] = useState(true)
    const [lazyLoading, setLazyLoading] = useState(false)

    const subscribedIdsRef = useRef([])
    // const [typeSubscribed, setTypeSubscribed] = useState(false)
    const unsubscribesRef = useRef([])
    const updateEventHandlerRef = useRef()
    const subscribeToObjectTypeUpdatesRef = useRef(null)

    const rowActions =
      model.actions?.filter((action) => action.row == true) || []

    const createSkeletonData = useCallback(() => {
      return Array(pageSize)
        .fill(null)
        .map(() => ({
          _id: `skeleton-${Math.random().toString(36).substring(2, 15)}`,
          isSkeleton: true
        }))
    }, [pageSize])

    const renderActions = (objectData) => {
      return (
        <Flex gap='small' align='center' justify='center'>
          {rowActions.map((action, index) => {
            var disabled = false
            if (action.disabled) {
              if (typeof action.disabled === 'function') {
                disabled = action.disabled(objectData)
              } else {
                disabled = action.disabled
              }
            }
            return (
              <Tooltip key={index} title={action.label} arrow={false}>
                <Button
                  icon={
                    action.icon ? (
                      createElement(action.icon)
                    ) : (
                      <QuestionCircleIcon />
                    )
                  }
                  disabled={disabled}
                  type={'text'}
                  size={'small'}
                  onClick={() => {
                    if (action.url) {
                      navigate(action.url(objectData._id))
                    }
                  }}
                />
              </Tooltip>
            )
          })}
        </Flex>
      )
    }

    const fetchData = useCallback(
      async (pageNum = 1, filter = null, sorter = null) => {
        if (filter == null) {
          filter = tableFilter
        } else {
          setTableFilter(filter)
        }
        if (sorter == null) {
          sorter = tableSorter
        } else {
          setTableSorter({
            field: sorter.field,
            order: sorter.order
          })
        }
        console.log('Fetching data...')
        try {
          const result = await fetchObjects(type, {
            page: pageNum,
            limit: pageSize,
            filter: { ...filter, ...masterFilter },
            sorter,
            onDataChange
          })

          setHasMore(result.hasMore)

          setPages((prev) => {
            const existingPageIndex = prev.findIndex(
              (p) => p.pageNum === pageNum
            )
            logger.debug(prev.map((p) => p.pageNum))
            if (existingPageIndex !== -1) {
              // Update existing page
              const newPages = [...prev]
              newPages[existingPageIndex] = { pageNum, items: result.data }
              return newPages
            }
            // If page doesn't exist, return unchanged
            return prev
          })

          setLoading(false)
          setLazyLoading(false)
          return result.data || []
        } catch (error) {
          setPages((prev) =>
            prev.map((page) => ({
              ...page,
              items: page.items.filter((item) => !item.isSkeleton)
            }))
          )
          setLoading(false)
          setLazyLoading(false)
          throw error
        }
      },
      [
        type,
        masterFilter,
        pageSize,
        tableFilter,
        tableSorter,
        onDataChange,
        fetchObjects
      ]
    )

    const loadNextPage = useCallback(() => {
      const highestPage = Math.max(...pages.map((p) => p.pageNum))
      const nextPage = highestPage + 1
      if (hasMore) {
        setPages((prev) => {
          const filteredPages = prev.map((page) => ({
            ...page,
            items: page.items.filter((item) => !item.isSkeleton)
          }))
          const minPage = Math.min(...filteredPages.map((p) => p.pageNum))
          const relevantPages = filteredPages.filter(
            (p) => p.pageNum !== minPage
          )
          return [
            ...relevantPages,
            { pageNum: nextPage, items: createSkeletonData() }
          ]
        })
        fetchData(nextPage)
      }
    }, [pages, createSkeletonData, fetchData, hasMore])

    const loadPreviousPage = useCallback(() => {
      const lowestPage = Math.min(...pages.map((p) => p.pageNum))
      const prevPage = lowestPage - 1

      if (prevPage > 0) {
        setPages((prev) => {
          const filteredPages = prev.map((page) => ({
            ...page,
            items: page.items.filter((item) => !item.isSkeleton)
          }))
          const maxPage = Math.max(...filteredPages.map((p) => p.pageNum))
          const relevantPages = filteredPages.filter(
            (p) => p.pageNum !== maxPage
          )
          return [
            { pageNum: prevPage, items: createSkeletonData() },
            ...relevantPages
          ]
        })
        fetchData(prevPage)
      }
    }, [pages, createSkeletonData, fetchData])

    const handleScroll = useCallback(
      (e) => {
        const { target } = e
        const scrollHeight = target.scrollHeight
        const scrollTop = target.scrollTop
        const clientHeight = target.clientHeight
        const lowestPage = Math.min(...pages.map((p) => p.pageNum))
        const prevPage = lowestPage - 1

        // Load more data when scrolling down
        if (scrollHeight - scrollTop - clientHeight < 100 && hasMore) {
          setTimeout(() => {
            target.scrollTop = scrollHeight / 2
          }, 0)
          setLazyLoading(true)
          logger.debug('Loading next page...')
          loadNextPage()
        }

        // Load previous data when scrolling up
        if (scrollTop < 100 && prevPage > 0) {
          setTimeout(() => {
            target.scrollTop = scrollHeight / 2
          }, 0)
          setLazyLoading(true)
          logger.debug('Loading previous page...')
          loadPreviousPage()
        }
      },
      [loadNextPage, loadPreviousPage, hasMore, pages]
    )

    const reload = useCallback(async () => {
      setLazyLoading(true)
      for (let i = 0; i < pagesRef.current.length; i++) {
        const page = pagesRef.current[i]
        await fetchData(page.pageNum)
      }
    }, [fetchData])

    // Update event handler for real-time updates
    const updateEventHandler = useCallback((id, updatedData) => {
      setPages((prevPages) =>
        prevPages.map((page) => {
          const updatedItems = page.items.map((item) => {
            if (item._id === id) {
              return { ...item, ...updatedData }
            }
            return item
          })
          return {
            ...page,
            items: updatedItems
          }
        })
      )
    }, [])

    // Store the latest updateEventHandler in a ref
    updateEventHandlerRef.current = updateEventHandler

    const newEventHandler = useCallback(() => {
      reload()
    }, [reload])

    // Subscribe to real-time updates for all items
    useEffect(() => {
      if (pages.length > 0 && connected == true) {
        // Get all non-skeleton item IDs from all pages
        const allItemIds = pages
          .flatMap((page) => page.items || [])
          .filter((item) => !item.isSkeleton)
          .map((item) => item._id)
          .filter(Boolean)

        // Find new items that need subscription
        const newItemIds = allItemIds.filter(
          (id) => !subscribedIdsRef.current.includes(id)
        )

        // Subscribe to new items only
        newItemIds.forEach((itemId) => {
          const unsubscribe = subscribeToObjectUpdates(
            itemId,
            type,
            (updateData) => {
              updateEventHandlerRef.current(itemId, updateData)
            }
          )
          subscribedIdsRef.current.push(itemId)
          if (unsubscribe) {
            unsubscribesRef.current.push(unsubscribe)
          }
        })

        // Clean up subscriptions for items that are no longer in the pages
        const currentSubscribedIds = subscribedIdsRef.current
        const itemsToUnsubscribe = currentSubscribedIds.filter(
          (id) => !allItemIds.includes(id)
        )

        itemsToUnsubscribe.forEach((itemId) => {
          const index = subscribedIdsRef.current.indexOf(itemId)
          if (index > -1) {
            subscribedIdsRef.current.splice(index, 1)
            const unsubscribe = unsubscribesRef.current[index]
            if (unsubscribe) {
              unsubscribe()
            }
            unsubscribesRef.current.splice(index, 1)
          }
        })
      }
    }, [pages, type, subscribeToObjectUpdates, connected])

    // Cleanup effect for component unmount
    useEffect(() => {
      return () => {
        if (connected == true && unsubscribesRef.current) {
          // Clean up all subscriptions when component unmounts
          unsubscribesRef.current.forEach((unsubscribe) => {
            if (unsubscribe) unsubscribe()
          })
          unsubscribesRef.current = []
          subscribedIdsRef.current = []

          // Clean up type subscription
        }
        if (connected == true && subscribeToObjectTypeUpdatesRef.current) {
          subscribeToObjectTypeUpdatesRef.current()
          subscribeToObjectTypeUpdatesRef.current = null
        }
      }
    }, [connected])

    useEffect(() => {
      if (
        connected == true &&
        subscribeToObjectTypeUpdatesRef.current == null
      ) {
        subscribeToObjectTypeUpdatesRef.current = subscribeToObjectTypeUpdates(
          type,
          newEventHandler
        )
      }
    }, [type, subscribeToObjectTypeUpdates, connected, newEventHandler])

    const updateData = useCallback(
      (_id, updatedData) => {
        updateEventHandler({ _id, ...updatedData })
      },
      [updateEventHandler]
    )

    const loadPage = useCallback(
      async (pageNum, filter = null, sorter = null) => {
        // Create initial page with skeletons
        setPages([{ pageNum: pageNum, items: createSkeletonData() }])

        const items = await fetchData(pageNum, filter, sorter)

        if (items.length >= 25) {
          setPages((prev) => {
            // Remove any existing page with the same pageNum
            const filtered = prev.filter((p) => p.pageNum !== pageNum + 1)
            return [
              ...filtered,
              { pageNum: pageNum + 1, items: createSkeletonData() }
            ]
          })
          await fetchData(pageNum + 1, filter, sorter)
        }
      },
      [createSkeletonData, fetchData]
    )

    const loadInitialPage = useCallback(async () => {
      loadPage(initialPage)
    }, [initialPage, loadPage])

    useImperativeHandle(ref, () => ({
      reload,
      setData: (newData) => {
        setPages([{ pageNum: 1, items: newData }])
      },
      updateData
    }))

    useEffect(() => {
      if (token != null && !pages.includes(initialPage) && !initialized) {
        loadInitialPage()
        setInitialized(true)
      }
    }, [token, loadInitialPage, initialPage, pages, initialized])

    const getFilterDropdown = ({
      setSelectedKeys,
      selectedKeys,
      confirm,
      clearFilters,
      propertyName
    }) => {
      return (
        <div style={{ padding: 8 }}>
          <Space.Compact>
            <Input
              placeholder={'Search ' + propertyName}
              value={selectedKeys[0]}
              onChange={(e) =>
                setSelectedKeys(e.target.value ? [e.target.value] : [])
              }
              onPressEnter={() => confirm()}
              style={{ width: 200, display: 'block' }}
            />
            <Button
              onClick={() => {
                clearFilters()
                confirm()
              }}
              icon={<XMarkIcon />}
            />
            <Button
              type='primary'
              onClick={() => confirm()}
              icon={<CheckIcon />}
            />
          </Space.Compact>
        </div>
      )
    }

    const handleTableChange = (pagination, filters, sorter) => {
      const newFilters = {}

      Object.entries(filters).forEach(([key, value]) => {
        if (value && value.length > 0) {
          newFilters[key] = value[0]
        }
      })

      setPages([])
      setLoading(true)
      loadPage(initialPage, newFilters, {
        field: sorter.field,
        order: sorter.order
      })
    }

    const modelProperties = getModelProperties(type)
    // Table columns from model properties
    const columnsWithSkeleton = [
      {
        title: lazyLoading ? <LoadingOutlined /> : cards ? model.icon : null,
        key: 'icon',
        width: 45,
        fixed: 'left',
        render: () => createElement(model.icon)
      }
    ]

    useEffect(() => {
      pagesRef.current = pages
    }, [pages])
    // Add columns in the order specified by model.columns
    model.columns.forEach((colName) => {
      const prop = modelProperties.find((p) => p.name === colName)
      if (prop) {
        // Check if column should be visible based on visibleColumns prop
        if (
          Object.keys(visibleColumns).length > 0 &&
          visibleColumns[prop.name] === false
        ) {
          return // Skip this column if it's not visible
        }

        var fixed = prop.columnFixed || undefined
        var width = 200

        switch (prop.type) {
          case 'text':
            width = 200
            break
          case 'number':
            width = 100
            break
          case 'dateTime':
            width = 200
            break
          case 'state':
            width = 200
            break
          case 'id':
            width = 180
            break
          default:
            break
        }
        // Check if this property should be filterable based on model.filters
        const isFilterable = model.filters && model.filters.includes(prop.name)

        // Check if this property should be sortable based on model.sorters
        const isSortable = model.sorters && model.sorters.includes(prop.name)

        const columnConfig = {
          sorter: isSortable,
          title: prop.label,
          dataIndex: prop.name,
          width: prop.columnWidth || width,
          fixed: fixed,
          key: prop.name,
          render: (text, record) => {
            if (record?.isSkeleton) {
              return (
                <Skeleton.Input active size='small' style={{ width: '100%' }} />
              )
            }
            return (
              <ObjectProperty
                {...prop}
                longId={false}
                objectData={record}
                isEditing={false}
              />
            )
          }
        }

        // Add filter configuration if the property is filterable and not in masterFilter
        if (isFilterable && !Object.keys(masterFilter).includes(prop.name)) {
          columnConfig.filterDropdown = ({
            setSelectedKeys,
            selectedKeys,
            confirm,
            clearFilters
          }) =>
            getFilterDropdown({
              setSelectedKeys,
              selectedKeys,
              confirm,
              clearFilters,
              propertyName: prop.label
            })
          // Remove local filtering - let the server handle it
          columnConfig.filtered = false
        }

        columnsWithSkeleton.push(columnConfig)
      }
    })

    if (rowActions.length > 0) {
      columnsWithSkeleton.push({
        title: (
          <Flex gap='small' align='center' justify='center'>
            {'Actions'}
          </Flex>
        ),
        key: 'actions',
        fixed: 'right',
        width: 80 + rowActions.length * 40, // Adjust width based on number of actions
        render: (record) => {
          return renderActions(record)
        }
      })
    }

    // Flatten pages array for table display
    const tableData = pages.flatMap((page) => page.items)

    // Card view rendering
    const cardsContainerRef = useRef(null)

    // Card view scroll handler
    useEffect(() => {
      if (!cards) return
      const container = cardsContainerRef.current
      if (!container) return

      const handleCardsScroll = (e) => {
        const { scrollTop, scrollHeight, clientHeight } = e.target
        const lowestPage = Math.min(...pages.map((p) => p.pageNum))
        const prevPage = lowestPage - 1

        // Load more data when scrolling down
        if (
          scrollHeight - scrollTop - clientHeight < 100 &&
          hasMore &&
          !lazyLoading
        ) {
          setTimeout(() => {
            e.target.scrollTop = scrollHeight / 2
          }, 0)
          setLazyLoading(true)
          loadNextPage()
        }

        // Load previous data when scrolling up
        if (scrollTop < 100 && prevPage > 0 && !lazyLoading) {
          setTimeout(() => {
            e.target.scrollTop = scrollHeight / 2
          }, 0)
          setLazyLoading(true)
          loadPreviousPage()
        }
      }

      container.addEventListener('scroll', handleCardsScroll)
      return () => container.removeEventListener('scroll', handleCardsScroll)
    }, [cards, pages, hasMore, lazyLoading, loadNextPage, loadPreviousPage])

    const renderCards = () => {
      return (
        <Row
          gutter={[16, 16]}
          style={{ overflowY: 'auto', maxHeight: adjustedScrollHeight }}
          ref={cardsContainerRef}
        >
          {tableData.map((record) => (
            <Col xs={24} sm={12} md={12} lg={8} xl={6} xxl={6} key={record._id}>
              <Card
                style={{ width: '100%', overflow: 'hidden' }}
                styles={{ body: { padding: 0 } }}
                loading={record.isSkeleton}
                variant={'borderless'}
              >
                <Flex align={'center'} vertical gap={'middle'}>
                  <Descriptions
                    column={1}
                    size='small'
                    bordered={true}
                    style={{ width: '100%', tableLayout: 'fixed' }}
                    className='objectTableDescritions'
                  >
                    {(() => {
                      const descriptionItems = []

                      // Add columns in the order specified by model.columns (same logic as table)
                      model.columns.forEach((colName) => {
                        const prop = modelProperties.find(
                          (p) => p.name === colName
                        )
                        if (prop) {
                          // Check if column should be visible based on visibleColumns prop
                          if (
                            Object.keys(visibleColumns).length > 0 &&
                            visibleColumns[prop.name] === false
                          ) {
                            return // Skip this column if it's not visible
                          }

                          descriptionItems.push(
                            <Descriptions.Item
                              label={prop.label}
                              key={prop.name}
                              colspan={2}
                            >
                              <ObjectProperty
                                {...prop}
                                longId={false}
                                objectData={record}
                                isEditing={false}
                              />
                            </Descriptions.Item>
                          )
                        }
                      })

                      // Add actions if they exist (same as table)
                      if (rowActions.length > 0) {
                        descriptionItems.push(
                          <Descriptions.Item label={'Actions'} key={'actions'}>
                            {renderActions(record)}
                          </Descriptions.Item>
                        )
                      }

                      return descriptionItems
                    })()}
                  </Descriptions>
                </Flex>
              </Card>
            </Col>
          ))}
        </Row>
      )
    }

    return (
      <>
        {contextHolder}
        <Flex gap={'middle'} vertical>
          <Table
            ref={tableRef}
            dataSource={tableData}
            columns={columnsWithSkeleton}
            className={cards ? 'dashboard-cards-header' : 'dashboard-table'}
            pagination={false}
            scroll={{ y: adjustedScrollHeight }}
            rowKey='_id'
            loading={{ spinning: loading, indicator: <LoadingOutlined spin /> }}
            onScroll={handleScroll}
            onChange={handleTableChange}
            showSorterTooltip={false}
            style={{ height: '100%' }}
            size={isElectron ? 'small' : 'middle'}
          />
          {cards ? (
            <Spin indicator={<LoadingOutlined />} spinning={loading}>
              {renderCards()}
            </Spin>
          ) : null}
        </Flex>
      </>
    )
  }
)

ObjectTable.displayName = 'ObjectTable'

ObjectTable.propTypes = {
  type: PropTypes.string.isRequired,
  pageSize: PropTypes.number,
  scrollHeight: PropTypes.string,
  onDataChange: PropTypes.func,
  initialPage: PropTypes.number,
  cards: PropTypes.bool,
  cardRenderer: PropTypes.func,
  visibleColumns: PropTypes.object,
  masterFilter: PropTypes.object
}

export default ObjectTable
