import {
  DeleteOutlined,
  EditOutlined,
  LeftOutlined,
  PlusOutlined,
  ReloadOutlined,
  RightOutlined,
  UploadOutlined
} from '@ant-design/icons'
import {
  Alert,
  Button,
  Calendar as AntdCalendar,
  DatePicker,
  Descriptions,
  Divider,
  Empty,
  Form,
  Input,
  message,
  Popconfirm,
  Popover,
  Radio,
  Select,
  Space,
  Spin,
  Typography,
  Upload
} from 'antd'
import { UploadFile } from 'antd/lib/upload/interface'
import { Moment } from 'moment'
import moment from 'moment-timezone'
import { useEffect, useMemo, useState } from 'react'

import {
  calendarServiceCreateCalendarConfig,
  calendarServiceDeleteCalendarConfig,
  calendarServiceUpdateCalendarConfig,
  useCalendarServiceListCalendarConfigs,
  useCalendarServiceListCalendarEvents
} from 'apiClient/services/core-server'
import ModalForm from 'components/ModalForm'
import { getErrorMessage } from 'utils/error'

import { uploadCalendarFile } from './api'
import {
  getAvailableCalendarName,
  getCalendarMonthRange,
  getCalendarWeekRange,
  groupCalendarEventsByDate
} from './utils'

import './index.less'

const maxCalendarFileSize = 3 * 1024 * 1024

interface CalendarFormValues {
  name: string
  timeZone: string
  file?: UploadFile[]
}

type CalendarView = 'week' | 'month'

const timeZones = [
  'UTC',
  'Asia/Shanghai',
  'Asia/Singapore',
  'Asia/Seoul',
  'Asia/Bangkok',
  'Asia/Kolkata',
  'Australia/Sydney',
  'Asia/Tokyo',
  'Europe/London',
  'Europe/Berlin',
  'Asia/Dubai',
  'Africa/Johannesburg',
  'America/New_York',
  'America/Chicago',
  'America/Denver',
  'America/Los_Angeles',
  'America/Mexico_City',
  'America/Bogota',
  'America/Sao_Paulo',
  'America/Santiago'
]

const timeZoneOptions = timeZones.map((timeZone) => ({
  label: timeZone,
  value: timeZone
}))

export default function Calendar() {
  const [selectedName, setSelectedName] = useState<string>()
  const [displayTimeZone, setDisplayTimeZone] = useState<string>()
  const [calendarValue, setCalendarValue] = useState<Moment>(() => moment())
  const [calendarView, setCalendarView] = useState<CalendarView>('month')
  const [createModalOpen, setCreateModalOpen] = useState(false)
  const [editModalOpen, setEditModalOpen] = useState(false)
  const [deleting, setDeleting] = useState(false)
  const defaultTimeZone = useMemo(() => {
    const detectedTimeZone = moment.tz.guess()
    return timeZones.includes(detectedTimeZone)
      ? detectedTimeZone
      : 'Asia/Shanghai'
  }, [])

  const {
    data: configsResponse,
    isLoading: configsLoading,
    isFetching: configsFetching,
    isError: configsError,
    refetch: refetchConfigs
  } = useCalendarServiceListCalendarConfigs()

  const configs = useMemo(
    () => configsResponse?.data.configs || [],
    [configsResponse]
  )
  const selectedConfig = useMemo(
    () => configs.find((config) => config.name === selectedName),
    [configs, selectedName]
  )
  const eventParams = useMemo(() => {
    if (!selectedConfig || !displayTimeZone) {
      return undefined
    }

    return {
      name: selectedConfig.name,
      ...(calendarView === 'month'
        ? getCalendarMonthRange(calendarValue, displayTimeZone)
        : getCalendarWeekRange(calendarValue, displayTimeZone))
    }
  }, [calendarValue, calendarView, displayTimeZone, selectedConfig])

  const {
    data: eventsResponse,
    isLoading: eventsLoading,
    isFetching: eventsFetching,
    isError: eventsError,
    refetch: refetchEvents
  } = useCalendarServiceListCalendarEvents(eventParams, {
    query: {
      enabled: !!eventParams
    }
  })

  const eventsByDate = useMemo(
    () =>
      selectedConfig && displayTimeZone
        ? groupCalendarEventsByDate(
            eventsResponse?.data.events || [],
            displayTimeZone
          )
        : {},
    [displayTimeZone, eventsResponse, selectedConfig]
  )

  useEffect(() => {
    setSelectedName((currentName) =>
      getAvailableCalendarName(configs, currentName)
    )
  }, [configs])

  useEffect(() => {
    setDisplayTimeZone(selectedConfig?.timeZone)
  }, [selectedConfig?.timeZone, selectedName])

  const handleRefresh = async () => {
    const result = await refetchConfigs()
    const refreshedConfigs = result.data?.data.configs || []
    const refreshedName = getAvailableCalendarName(
      refreshedConfigs,
      selectedName
    )

    if (refreshedName === selectedName && refreshedName) {
      await refetchEvents()
    } else {
      setSelectedName(refreshedName)
    }
  }

  const handleCreate = async (values: CalendarFormValues) => {
    const fileHash = await uploadFile(values.file![0])
    const response = await calendarServiceCreateCalendarConfig({
      name: values.name,
      timeZone: values.timeZone,
      fileHash
    })
    const createdName = response.data.config!.name!

    setCreateModalOpen(false)
    await refetchConfigs()
    setSelectedName(createdName)
    message.success(`Calendar ${createdName} created`)
  }

  const handleUpdate = async (values: CalendarFormValues) => {
    const name = selectedConfig!.name!
    const fileHash = values.file?.length
      ? await uploadFile(values.file[0])
      : undefined

    await calendarServiceUpdateCalendarConfig(name, {
      timeZone: values.timeZone,
      ...(fileHash ? { fileHash } : {})
    })
    setEditModalOpen(false)
    await refetchConfigs()
    await refetchEvents()
    message.success(`Calendar ${name} updated`)
  }

  const handleDelete = async () => {
    const name = selectedConfig!.name!
    setDeleting(true)
    try {
      await calendarServiceDeleteCalendarConfig(name)
      const result = await refetchConfigs()
      const refreshedConfigs = result.data?.data.configs || []
      setSelectedName(getAvailableCalendarName(refreshedConfigs))
      message.success(`Calendar ${name} deleted`)
    } catch (error) {
      message.error(`Failed to delete Calendar: ${getErrorMessage(error)}`)
    } finally {
      setDeleting(false)
    }
  }

  const dateCellRender = (date: Moment) => {
    const events = eventsByDate[date.format('YYYY-MM-DD')] || []

    return (
      <ul className="calendar-events">
        {events.map((event) => (
          <li key={event.key} className="calendar-event">
            <Popover
              overlayClassName="calendar-event-popover"
              content={
                <Descriptions column={1} size="small">
                  <Descriptions.Item label="Summary">
                    {event.summary}
                  </Descriptions.Item>
                  <Descriptions.Item
                    label={event.details.allDay ? 'Start Date' : 'Start Time'}
                  >
                    {event.details.start}
                  </Descriptions.Item>
                  <Descriptions.Item
                    label={
                      event.details.allDay ? 'End Date (Exclusive)' : 'End Time'
                    }
                  >
                    {event.details.end}
                  </Descriptions.Item>
                  {!event.details.allDay && (
                    <Descriptions.Item label="Time Zone">
                      {event.details.timeZone}
                    </Descriptions.Item>
                  )}
                </Descriptions>
              }
              trigger="click"
            >
              <span className="calendar-event-content">
                <span className="calendar-event-time">
                  {`${event.timeLabel} | `}
                </span>
                <span className="calendar-event-summary">{event.summary}</span>
              </span>
            </Popover>
          </li>
        ))}
      </ul>
    )
  }

  const renderCalendarHeader = (
    value: Moment,
    onChange: (value: Moment) => void
  ) => {
    const unit = calendarView === 'month' ? 'Month' : 'Week'

    return (
      <>
        <div className="calendar-header">
          <Space className="calendar-header-controls">
            <Button onClick={() => onChange(moment().tz(displayTimeZone!))}>
              Today
            </Button>
            <Button
              aria-label={`Previous ${unit}`}
              icon={<LeftOutlined />}
              onClick={() => onChange(value.clone().subtract(1, calendarView))}
            />
            <Button
              aria-label={`Next ${unit}`}
              icon={<RightOutlined />}
              onClick={() => onChange(value.clone().add(1, calendarView))}
            />
            <DatePicker
              key={calendarView}
              aria-label={`Calendar ${unit}`}
              allowClear={false}
              format={calendarView === 'month' ? 'YYYY-MM' : 'YYYY-wo'}
              picker={calendarView}
              value={value}
              onChange={(date) => onChange(date!)}
            />
          </Space>
          <Radio.Group
            aria-label="Calendar View"
            buttonStyle="solid"
            value={calendarView}
            onChange={(event) => setCalendarView(event.target.value)}
          >
            <Radio.Button value="week">Week</Radio.Button>
            <Radio.Button value="month">Month</Radio.Button>
          </Radio.Group>
        </div>
        <Divider className="calendar-header-divider" />
      </>
    )
  }

  const weekDays = useMemo(() => {
    if (!displayTimeZone) {
      return []
    }

    const start = moment
      .tz(
        [calendarValue.year(), calendarValue.month(), calendarValue.date()],
        displayTimeZone
      )
      .startOf('week')

    return Array.from({ length: 7 }, (_, index) =>
      start.clone().add(index, 'day')
    )
  }, [calendarValue, displayTimeZone])

  return (
    <div>
      <div className="calendar-toolbar">
        <Space wrap>
          <Select
            className="calendar-selector"
            aria-label="Calendar Name"
            showSearch
            placeholder="Select Calendar"
            loading={configsLoading || configsFetching}
            disabled={configs.length === 0}
            value={selectedName}
            optionFilterProp="label"
            options={configs.map((config) => ({
              label: config.name!,
              value: config.name!
            }))}
            onChange={setSelectedName}
          />
          <Typography.Text type="secondary">Time Zone:</Typography.Text>
          <Select
            className="calendar-time-zone-selector"
            aria-label="Display Time Zone"
            showSearch
            placeholder="Select Time Zone"
            disabled={!selectedConfig}
            value={displayTimeZone}
            optionFilterProp="label"
            options={timeZoneOptions}
            onChange={setDisplayTimeZone}
          />
        </Space>

        <Space wrap>
          <Button
            icon={<ReloadOutlined />}
            loading={configsFetching || eventsFetching}
            onClick={handleRefresh}
          >
            Refresh
          </Button>
          <Button
            icon={<EditOutlined />}
            disabled={!selectedConfig}
            onClick={() => setEditModalOpen(true)}
          >
            Edit
          </Button>
          <Popconfirm
            title={`Delete Calendar ${selectedConfig?.name || ''}?`}
            okText="Delete"
            okButtonProps={{ danger: true }}
            disabled={!selectedConfig}
            onConfirm={handleDelete}
          >
            <Button
              danger
              icon={<DeleteOutlined />}
              disabled={!selectedConfig}
              loading={deleting}
            >
              Delete
            </Button>
          </Popconfirm>
          <Button
            type="primary"
            icon={<PlusOutlined />}
            onClick={() => setCreateModalOpen(true)}
          >
            Add Calendar
          </Button>
        </Space>
      </div>
      <Divider className="calendar-toolbar-divider" />

      {configsError && (
        <Alert
          showIcon
          type="error"
          message="Failed to load Calendar configurations"
        />
      )}

      {!configsLoading && !configsError && configs.length === 0 && (
        <Empty description="No Calendar configured" />
      )}

      {selectedConfig && displayTimeZone && (
        <Spin spinning={eventsLoading || eventsFetching}>
          {eventsError && (
            <Alert
              showIcon
              type="error"
              message="Failed to load Calendar events"
            />
          )}
          {calendarView === 'month' ? (
            <AntdCalendar
              value={calendarValue}
              dateCellRender={dateCellRender}
              headerRender={({ value, onChange }) =>
                renderCalendarHeader(value, onChange)
              }
              onChange={setCalendarValue}
              onPanelChange={setCalendarValue}
            />
          ) : (
            <div className="calendar-week-view">
              {renderCalendarHeader(calendarValue, setCalendarValue)}
              <div className="calendar-week-grid" role="grid">
                <div className="calendar-week-heading" role="row">
                  {weekDays.map((date) => (
                    <div key={date.format('dddd')} role="columnheader">
                      {date.format('ddd')}
                    </div>
                  ))}
                </div>
                <div className="calendar-week-row" role="row">
                  {weekDays.map((date) => (
                    <div
                      key={date.format('YYYY-MM-DD')}
                      aria-label={date.format('YYYY-MM-DD')}
                      className="calendar-week-cell"
                      role="gridcell"
                    >
                      <div
                        className={`calendar-week-date${
                          date.isSame(moment().tz(displayTimeZone), 'day')
                            ? ' calendar-week-date-today'
                            : ''
                        }`}
                      >
                        <div className="calendar-week-date-value">
                          {date.date()}
                        </div>
                        <div className="calendar-week-date-content">
                          {dateCellRender(date)}
                        </div>
                      </div>
                    </div>
                  ))}
                </div>
              </div>
            </div>
          )}
        </Spin>
      )}

      <ModalForm<CalendarFormValues>
        visible={createModalOpen}
        name="create calendar"
        title="Add Calendar"
        initValues={{ name: '', timeZone: defaultTimeZone }}
        getFields={() => (
          <>
            <Form.Item
              name="name"
              label="Name"
              rules={[
                { required: true, message: 'Name is required' },
                { max: 128, message: 'Name must not exceed 128 characters' },
                {
                  pattern: /^[^/]+$/,
                  message: 'Name must not contain /'
                }
              ]}
            >
              <Input placeholder="Calendar name" />
            </Form.Item>
            <Form.Item
              name="timeZone"
              label="Time Zone"
              rules={[{ required: true, message: 'Time Zone is required' }]}
            >
              <Select
                showSearch
                options={timeZoneOptions}
                placeholder="Select an IANA time zone"
                optionFilterProp="label"
              />
            </Form.Item>
            <CalendarFileField required />
          </>
        )}
        onSubmit={handleCreate}
        onCancel={() => setCreateModalOpen(false)}
      />

      <ModalForm<CalendarFormValues>
        visible={editModalOpen}
        name="edit calendar"
        title="Edit Calendar"
        initValues={{
          name: selectedConfig?.name || '',
          timeZone: selectedConfig?.timeZone || defaultTimeZone
        }}
        getFields={() => (
          <>
            <Form.Item name="name" label="Name">
              <Input disabled />
            </Form.Item>
            <Form.Item
              name="timeZone"
              label="Time Zone"
              rules={[{ required: true, message: 'Time Zone is required' }]}
            >
              <Select
                showSearch
                options={timeZoneOptions}
                placeholder="Select an IANA time zone"
                optionFilterProp="label"
              />
            </Form.Item>
            <CalendarFileField />
          </>
        )}
        onSubmit={handleUpdate}
        onCancel={() => setEditModalOpen(false)}
      />
    </div>
  )
}

function CalendarFileField({ required = false }: { required?: boolean }) {
  return (
    <Form.Item
      name="file"
      label="Calendar File"
      valuePropName="fileList"
      getValueFromEvent={(event) => event.fileList}
      rules={
        required
          ? [{ required: true, message: 'Calendar file is required' }]
          : undefined
      }
      extra={
        required
          ? 'Upload an ICS/ICal file up to 3 MiB.'
          : 'Leave empty to keep the existing file.'
      }
    >
      <Upload
        accept=".ics,.ical,text/calendar"
        maxCount={1}
        beforeUpload={(file) => {
          if (file.size > maxCalendarFileSize) {
            message.error('Calendar file must not exceed 3 MiB')
            return Upload.LIST_IGNORE
          }
          return false
        }}
      >
        <Button icon={<UploadOutlined />}>Select Calendar File</Button>
      </Upload>
    </Form.Item>
  )
}

async function uploadFile(file: UploadFile) {
  const response = await uploadCalendarFile(
    (file.originFileObj || file) as File
  )
  if (!response.data.fileHash) {
    throw new Error('Calendar upload response does not contain fileHash')
  }
  return response.data.fileHash
}
