import PropTypes from 'prop-types'
import { useState, useContext, useEffect } from 'react'
import axios from 'axios'
import { useMediaQuery } from 'react-responsive'
import {
  capitalizeFirstLetter,
  timeStringToMinutes
} from '../../utils/Utils.js'
import {
  Form,
  Input,
  Button,
  message,
  Typography,
  Flex,
  Steps,
  Divider,
  Upload,
  Descriptions,
  Checkbox,
  Spin,
  InputNumber,
  Badge
} from 'antd'
import { LoadingOutlined } from '@ant-design/icons'

import { AuthContext } from '../../context/AuthContext.jsx'

import GCodeFileIcon from '../../../Icons/GCodeFileIcon'

import FilamentSelect from '../../common/FilamentSelect'

import config from '../../../../config.js'

const { Dragger } = Upload

const { Title } = Typography

const initialNewGCodeFileForm = {
  gcodeFileInfo: {},
  name: '',
  printTimeMins: 0,
  cost: 0,
  file: null,
  filament: null
}

//const chunkSize = 5000

const NewGCodeFile = ({ onOk, reset }) => {
  const [messageApi] = message.useMessage()
  const isMobile = useMediaQuery({ maxWidth: 768 })

  const [newGCodeFileLoading, setNewGCodeFileLoading] = useState(false)
  const [gcodeParsing, setGcodeParsing] = useState(false)

  const [filamentSelectFilter, setFilamentSelectFilter] = useState(null)
  const [useFilamentSelectFilter, setUseFilamentSelectFilter] = useState(true)

  const [currentStep, setCurrentStep] = useState(0)
  const [nextEnabled, setNextEnabled] = useState(false)
  const [nextLoading, setNextLoading] = useState(false)

  const [newGCodeFileForm] = Form.useForm()
  const [newGCodeFileFormValues, setNewGCodeFileFormValues] = useState(
    initialNewGCodeFileForm
  )

  const [gcodeFile, setGCodeFile] = useState(null)

  const newGCodeFileFormUpdateValues = Form.useWatch([], newGCodeFileForm)

  const { token, authenticated } = useContext(AuthContext)
  // eslint-disable-next-line
  const fetchFilamentInfo = async () => {
    if (!authenticated) {
      return
    }
    if (
      newGCodeFileFormValues.filament &&
      newGCodeFileFormValues.gcodeFileInfo
    ) {
      try {
        setNextLoading(true)
        const response = await axios.get(
          `${config.backendUrl}/filaments/${newGCodeFileFormValues.filament}`,
          {
            headers: {
              Accept: 'application/json'
            },
            withCredentials: true // Important for including cookies
          }
        )
        setNextLoading(false)

        const price =
          (response.data.price / 1000) *
          newGCodeFileFormValues.gcodeFileInfo.filament_used_g // convert kg to g and multiply

        const printTimeMins = timeStringToMinutes(
          newGCodeFileFormValues.gcodeFileInfo
            .estimated_printing_time_normal_mode
        )
        setNewGCodeFileFormValues({
          ...newGCodeFileFormValues,
          price,
          printTimeMins
        })
      } catch (error) {
        if (error.response) {
          messageApi.error(
            'Error fetching filament data:',
            error.response.status
          )
        } else {
          messageApi.error(
            'An unexpected error occurred. Please try again later.'
          )
        }
      }
    }
  }

  useEffect(() => {
    newGCodeFileForm
      .validateFields({
        validateOnly: true
      })
      .then(() => setNextEnabled(true))
      .catch(() => setNextEnabled(false))
  }, [newGCodeFileForm, newGCodeFileFormUpdateValues])

  const summaryItems = [
    {
      key: 'name',
      label: 'Name',
      children: newGCodeFileFormValues?.name
    },
    {
      key: 'filament',
      label: 'Filament',
      children:
        newGCodeFileFormValues?.filament != null ? (
          <>
            {newGCodeFileFormValues.filament}
            <Badge
              text={newGCodeFileFormValues.filament.name}
              color={newGCodeFileFormValues.filament.color}
            />
          </>
        ) : (
          'n/a'
        )
    },
    {
      key: 'cost',
      label: 'Cost',
      children: '£' + newGCodeFileFormValues?.cost
    },
    {
      key: 'sparse_infill_density',
      label: 'Infill Density',
      children: newGCodeFileFormValues?.gcodeFileInfo?.sparseInfillDensity
    },
    {
      key: 'sparse_infill_pattern',
      label: 'Infill Pattern',
      children: capitalizeFirstLetter(
        newGCodeFileFormValues?.gcodeFileInfo?.sparseInfillPattern
      )
    },
    {
      key: 'layer_height',
      label: 'Layer Height',
      children: newGCodeFileFormValues?.gcodeFileInfo?.layerHeight + 'mm'
    },
    {
      key: 'filamentType',
      label: 'Filament Material',
      children: newGCodeFileFormValues?.gcodeFileInfo?.filamentType
    },
    {
      key: 'filamentUsedG',
      label: 'Filament Used (g)',
      children: newGCodeFileFormValues?.gcodeFileInfo?.filamentUsedG + 'g'
    },
    {
      key: 'filamentVendor',
      label: 'Filament Brand',
      children: newGCodeFileFormValues?.gcodeFileInfo?.filamentVendor
    },

    {
      key: 'hotendTemperature',
      label: 'Hotend Temperature',
      children: newGCodeFileFormValues?.gcodeFileInfo?.nozzleTemperature + '°'
    },
    {
      key: 'bedTemperature',
      label: 'Bed Temperature',
      children: newGCodeFileFormValues?.gcodeFileInfo?.hotPlateTemp + '°'
    },
    {
      key: 'estimated_printing_time_normal_mode',
      label: 'Est. Print Time',
      children:
        newGCodeFileFormValues?.gcodeFileInfo?.estimatedPrintingTimeNormalMode
    }
  ]

  useEffect(() => {
    if (reset) {
      setCurrentStep(0)
      newGCodeFileForm.resetFields()
    }
  }, [reset, newGCodeFileForm])

  useEffect(() => {
    const filamentCost = newGCodeFileFormValues?.filament?.cost
    const gcodeFilamentUsed =
      newGCodeFileFormValues?.gcodeFileInfo?.filamentUsedG
    if (filamentCost && gcodeFilamentUsed) {
      const cost = (filamentCost / 1000) * gcodeFilamentUsed
      setNewGCodeFileFormValues((prev) => ({ ...prev, cost: cost.toFixed(2) }))
      newGCodeFileForm.setFieldValue('cost', cost.toFixed(2))
    }
  }, [
    newGCodeFileForm,
    newGCodeFileFormValues?.filament?.cost,
    newGCodeFileFormValues?.gcodeFileInfo?.filamentUsedG
  ])

  const handleNewGCodeFileUpload = async (id) => {
    setNewGCodeFileLoading(true)
    const formData = new FormData()
    formData.append('gcodeFile', gcodeFile)
    try {
      await axios.post(
        `${config.backendUrl}/gcodefiles/${id}/content`,
        formData,
        {
          headers: {
            'Content-Type': 'multipart/form-data',
            Authorization: `Bearer ${token}`
          }
        }
      )

      resetForm()
      onOk()
    } catch (error) {
      messageApi.error('Error creating new gcode file: ' + error.message)
    } finally {
      setNewGCodeFileLoading(false)
    }
  }

  const handleNewGCodeFile = async () => {
    setNewGCodeFileLoading(true)
    try {
      const request = await axios.post(
        `${config.backendUrl}/gcodefiles`,
        newGCodeFileFormValues,
        {
          headers: {
            Authorization: `Bearer ${token}`
          }
        }
      )
      messageApi.info('New G Code file created successfully. Uploading...')
      handleNewGCodeFileUpload(request.data._id)
    } catch (error) {
      messageApi.error('Error creating new gcode file: ' + error.message)
    } finally {
      setNewGCodeFileLoading(false)
    }
  }

  const handleGetGCodeFileInfo = async (file) => {
    try {
      setGcodeParsing(true)
      // Create a FormData object to send the file
      const formData = new FormData()
      formData.append('gcodeFile', file)

      // Call the API to extract and parse the config block
      const request = await axios.post(
        `${config.backendUrl}/gcodefiles/content`,
        formData,
        {
          withCredentials: true // Important for including cookies
        },
        {
          headers: {
            Accept: 'application/json'
          }
        }
      )

      // Parse the API response
      const parsedConfig = await request.data

      // Update state with the parsed config from API
      setNewGCodeFileFormValues({
        ...newGCodeFileFormValues,
        gcodeFileInfo: parsedConfig
      })

      // Update filter settings if filament info is available
      if (parsedConfig.filament_type && parsedConfig.filament_diameter) {
        setFilamentSelectFilter({
          type: parsedConfig.filament_type,
          diameter: parsedConfig.filament_diameter
        })
      }
      const fileName = file.name.replace(/\.[^/.]+$/, '')
      newGCodeFileForm.setFieldValue('name', fileName)
      setNewGCodeFileFormValues((prev) => ({
        ...prev,
        name: fileName
      }))
      setGCodeFile(file)
      setGcodeParsing(false)
      setCurrentStep(currentStep + 1)
    } catch (error) {
      console.error('Error getting G-code file info:', error)
    }
  }

  const resetForm = () => {
    newGCodeFileForm.setFieldsValue(initialNewGCodeFileForm)
    setNewGCodeFileFormValues(initialNewGCodeFileForm)
    setGCodeFile(null)
    setGcodeParsing(false)
    setCurrentStep(0)
  }

  const steps = [
    {
      title: 'Upload',
      key: 'upload',
      content: (
        <>
          <Form.Item
            rules={[
              {
                required: true,
                message: 'Please upload a gcode file.'
              }
            ]}
            name='file'
            style={{ height: '100%' }}
            getValueFromEvent={(e) => (Array.isArray(e) ? e : e && e.fileList)}
          >
            <Dragger
              name='G Code File'
              maxCount={1}
              showUploadList={false}
              customRequest={({ file, onSuccess }) => {
                handleGetGCodeFileInfo(file)
                setTimeout(() => {
                  onSuccess('ok')
                }, 0)
              }}
            >
              <Flex style={{ height: '100%' }} vertical>
                {gcodeParsing == true ? (
                  <Spin
                    indicator={
                      <LoadingOutlined style={{ fontSize: 24 }} spin />
                    }
                  />
                ) : (
                  <>
                    <p className='ant-upload-drag-icon'>
                      <GCodeFileIcon />
                    </p>
                    <p className='ant-upload-text'>
                      Click or drag gcode file here.
                    </p>
                    <p className='ant-upload-hint'>
                      Supported file extentions: .gcode, .gco, .g
                    </p>
                  </>
                )}
              </Flex>
            </Dragger>
          </Form.Item>
        </>
      )
    },
    {
      title: 'Details',
      key: 'details',
      content: (
        <>
          <Form.Item
            label='Name'
            name='name'
            rules={[
              {
                required: true,
                message: 'Please enter a name.'
              }
            ]}
          >
            <Input />
          </Form.Item>

          <Flex gap={'middle'}>
            <Form.Item
              label='Filament'
              name='filament'
              style={{ width: '100%' }}
              rules={[
                {
                  required: true,
                  message: 'Please provide a materal.'
                }
              ]}
            >
              <FilamentSelect
                filter={filamentSelectFilter}
                useFilter={useFilamentSelectFilter}
              />
            </Form.Item>

            <Form.Item>
              <Checkbox
                checked={useFilamentSelectFilter}
                onChange={(e) => {
                  setUseFilamentSelectFilter(e.target.checked)
                }}
              >
                Filter
              </Checkbox>
            </Form.Item>
          </Flex>
          <Form.Item
            label='Cost'
            name='cost'
            rules={[
              {
                required: true,
                message: 'Please enter a cost.'
              }
            ]}
          >
            <InputNumber
              controls={false}
              addonBefore='£'
              step={0.01}
              style={{ width: '100%' }}
              readOnly
            />
          </Form.Item>
        </>
      )
    },
    {
      title: 'Summary',
      key: 'done',
      content: (
        <>
          <Form.Item>
            <Descriptions column={1} items={summaryItems} size={'small'} />
          </Form.Item>
        </>
      )
    }
  ]

  return (
    <Flex gap={'middle'}>
      {!isMobile && (
        <div style={{ minWidth: '160px' }}>
          <Steps
            current={currentStep}
            items={steps}
            direction='vertical'
            style={{ width: 'fit-content' }}
          />
        </div>
      )}

      {!isMobile && <Divider type={'vertical'} style={{ height: 'unset' }} />}

      <Flex vertical={'true'} style={{ flexGrow: 1 }} gap='middle'>
        <Title level={2} style={{ marginTop: 0, marginBottom: 4 }}>
          New G Code File
        </Title>
        <Form
          name='basic'
          autoComplete='off'
          form={newGCodeFileForm}
          onFinish={handleNewGCodeFile}
          onValuesChange={(changedValues) =>
            setNewGCodeFileFormValues((prevValues) => ({
              ...prevValues,
              ...changedValues
            }))
          }
          initialValues={initialNewGCodeFileForm}
        >
          <div style={{ minHeight: '260px' }}>{steps[currentStep].content}</div>

          <Flex justify={'end'}>
            <Button
              style={{
                margin: '0 8px'
              }}
              onClick={() => {
                setCurrentStep(currentStep - 1)
                setNextEnabled(true)
              }}
              disabled={!(currentStep > 0)}
            >
              Previous
            </Button>
            {currentStep < steps.length - 1 && (
              <Button
                type='primary'
                disabled={!nextEnabled}
                loading={nextLoading}
                onClick={() => {
                  setCurrentStep(currentStep + 1)
                  setNextEnabled(false)
                }}
              >
                Next
              </Button>
            )}
            {currentStep === steps.length - 1 && (
              <Button
                type='primary'
                htmlType='submit'
                loading={newGCodeFileLoading}
              >
                Done
              </Button>
            )}
          </Flex>
        </Form>
      </Flex>
    </Flex>
  )
}

NewGCodeFile.propTypes = {
  reset: PropTypes.bool.isRequired,
  onOk: PropTypes.func.isRequired
}

export default NewGCodeFile
