import {
  InfoCircleOutlined,
  LoadingOutlined,
  PlusOutlined
} from '@ant-design/icons'
import {
  Alert,
  Button,
  Form,
  Input,
  InputNumber,
  message,
  Modal,
  Popover,
  Space,
  Spin,
  Switch,
  Tree,
  TreeDataNode,
  Typography
} from 'antd'
import { useCallback, useEffect, useMemo, useState } from 'react'

import {
  useGetApiV1DevopsBillingProxyApiV1BillingSkuGroupsCategory,
  useGetApiV1DevopsBillingProxyApiV1BillingSkuGroupsCategoryCategory,
  usePostApiV1DevopsBillingProxyApiV1BillingSkuGroupsCategory,
  usePutApiV1DevopsBillingProxyApiV1BillingSkuGroupsCategoryCategory
} from 'apiClient/services/billing'
import {
  FormsCategoryItemShow,
  FormsCategoryNode
} from 'apiClient/services/billing/interface'

import './SKUPreviewModal.css'

export interface SKUPreviewModalProps {
  open: boolean
  onClose?: () => void
}

type CategoryFormMode = 'create' | 'edit'

interface CategoryTreeNode extends TreeDataNode {
  category?: string
  depth: number
  inheritedOrderMap?: Record<string, number>
  itemKey?: string
  name?: string
  nextChildOrder?: number
  order?: number
  parentKey?: string
}

interface CategoryFormValues
  extends Omit<FormsCategoryItemShow, 'labels' | 'order_map'> {
  _current_order_key?: string
  _inherited_order_map?: string
  _next_order?: number
  _original_show_category_name?: string
  _original_show_kind_name?: string
  labels?: string
  order?: number
  order_map?: string
}

const EMPTY_FORM_VALUES: CategoryFormValues = {
  _current_order_key: '',
  _inherited_order_map: '',
  _next_order: undefined,
  _original_show_category_name: '',
  _original_show_kind_name: '',
  category: '',
  label_key: '',
  labels: '',
  measure_unit: '',
  order: undefined,
  order_map: '',
  parent_key: '',
  show_category_name: '',
  show_kind_name: '',
  show_service_prefix: '',
  use_cate_gory: true
}

export const SKUPreviewModal = ({ open, onClose }: SKUPreviewModalProps) => {
  const [form] = Form.useForm<CategoryFormValues>()
  const [formMode, setFormMode] = useState<CategoryFormMode>('create')
  const [formOpen, setFormOpen] = useState(false)
  const [editingCategory, setEditingCategory] = useState('')
  const { data, refetch, isFetching } =
    useGetApiV1DevopsBillingProxyApiV1BillingSkuGroupsCategory({
      query: { enabled: false }
    })
  const {
    refetch: refetchCategory,
    isFetching: isFetchingCategory,
    remove: removeCategory
  } = useGetApiV1DevopsBillingProxyApiV1BillingSkuGroupsCategoryCategory(
    editingCategory,
    { query: { enabled: false } }
  )
  const {
    mutateAsync: createCategory,
    isLoading: isCreating,
    reset: resetCreate
  } = usePostApiV1DevopsBillingProxyApiV1BillingSkuGroupsCategory()
  const {
    mutateAsync: updateCategory,
    isLoading: isUpdating,
    reset: resetUpdate
  } = usePutApiV1DevopsBillingProxyApiV1BillingSkuGroupsCategoryCategory()
  const isSaving = isCreating || isUpdating
  const isFormLoading = isSaving || isFetchingCategory

  const openCategoryForm = useCallback(
    (mode: CategoryFormMode, node?: CategoryTreeNode) => {
      setFormMode(mode)
      resetCreate()
      resetUpdate()

      if (mode === 'create') {
        setEditingCategory('')
        removeCategory()
        form.setFieldsValue({
          ...EMPTY_FORM_VALUES,
          _inherited_order_map: toJSONValueString(
            node?.inheritedOrderMap || {}
          ),
          _next_order: node?.nextChildOrder || getNextOrder(data?.data || []),
          parent_key: node?.itemKey || ''
        })
        setFormOpen(true)

        return
      }

      if (!node?.category) {
        message.error('This node does not have a category.')

        return
      }

      setEditingCategory(node.category)
      form.setFieldsValue({
        ...EMPTY_FORM_VALUES,
        _current_order_key: node.name || '',
        _inherited_order_map: toJSONValueString(node.inheritedOrderMap || {})
      })
      setFormOpen(true)
    },
    [data, form, removeCategory, resetCreate, resetUpdate]
  )

  const treeData = useMemo(
    () =>
      data?.data?.length ? toTreeData(data.data, 0, {}, openCategoryForm) : [],
    [data, openCategoryForm]
  )
  const expandedKeys = useMemo(() => getExpandedKeys(treeData), [treeData])

  const closeCategoryForm = useCallback(() => {
    setFormOpen(false)
    setEditingCategory('')
    removeCategory()
    form.resetFields()
  }, [form, removeCategory])

  useEffect(() => {
    if (!open) {
      return
    }
    refetch()
  }, [open, refetch])

  useEffect(() => {
    if (!formOpen || formMode !== 'edit' || !editingCategory) {
      return
    }

    refetchCategory().then((res) => {
      const category = res.data?.data

      if (category) {
        form.setFieldsValue(toFormValues(category, form.getFieldsValue()))
      }
    })
  }, [editingCategory, form, formMode, formOpen, refetchCategory])

  return (
    <>
      <Modal
        title="SKU Preview"
        centered
        visible={open}
        onCancel={() => onClose?.()}
        footer={null}
        destroyOnClose
        width={720}
        zIndex={1000}
      >
        <Space direction="vertical" style={{ width: '100%' }}>
          <Button
            type="primary"
            icon={<PlusOutlined />}
            onClick={() => openCategoryForm('create')}
          >
            Add Root Node
          </Button>

          {isFetching && <Spin indicator={<LoadingOutlined spin />} />}
          {!isFetching && !!data?.data && (
            <Tree
              className="sku-preview-tree"
              key={expandedKeys.join('|')}
              defaultExpandedKeys={expandedKeys}
              treeData={treeData}
            />
          )}
        </Space>
      </Modal>

      <Modal
        title={formMode === 'create' ? 'Add Billing Item' : 'Edit Billing Item'}
        centered
        visible={formOpen}
        cancelButtonProps={{ disabled: isFormLoading }}
        closable={!isFormLoading}
        confirmLoading={isFormLoading}
        maskClosable={!isFormLoading}
        onCancel={() => {
          if (!isFormLoading) {
            closeCategoryForm()
          }
        }}
        okText="Save"
        width={720}
        zIndex={1100}
        onOk={async () => {
          try {
            const values = await form.validateFields()
            const payload = toPayload(values, formMode, data?.data || [])

            if (formMode === 'create') {
              await createCategory({ data: payload })
            } else {
              await updateCategory({
                category: editingCategory,
                data: payload
              })
            }

            message.success('Save successful!')
            await refetch()
            closeCategoryForm()
          } catch (e: any) {
            if (!e?.errorFields) {
              message.error(
                e.response?.data?.message || e.message || 'Save failed.'
              )
            }
          }
        }}
      >
        {isFetchingCategory && <Spin indicator={<LoadingOutlined spin />} />}
        <CategoryItemForm form={form} mode={formMode} />
      </Modal>
    </>
  )
}

const CategoryItemForm = ({
  form,
  mode
}: {
  form: ReturnType<typeof Form.useForm<CategoryFormValues>>[0]
  mode: CategoryFormMode
}) => {
  const parentKey = Form.useWatch('parent_key', form)
  const parentKeyNames = extractParentKeyNames(parentKey)
  const isRoot = !parentKey
  const showParentKey = mode === 'create' && !!parentKey
  const showCategoryName = !parentKey || parentKeyNames.length < 2

  return (
    <Form<CategoryFormValues>
      form={form}
      layout="vertical"
      initialValues={EMPTY_FORM_VALUES}
    >
      <Alert
        type="info"
        showIcon
        style={{ marginBottom: 16 }}
        message={
          isRoot
            ? 'Root node requires show_kind_name. Child node uses parent_key.'
            : 'Child node will infer show_kind_name from parent_key.'
        }
      />
      <Form.Item hidden name="parent_key">
        <Input />
      </Form.Item>
      <Form.Item hidden name="_inherited_order_map">
        <Input />
      </Form.Item>
      <Form.Item hidden name="_next_order">
        <Input />
      </Form.Item>
      <Form.Item hidden name="_current_order_key">
        <Input />
      </Form.Item>
      <Form.Item hidden name="_original_show_kind_name">
        <Input />
      </Form.Item>
      <Form.Item hidden name="_original_show_category_name">
        <Input />
      </Form.Item>
      <Form.Item
        label="Category"
        name="category"
        rules={[{ required: true, message: 'Please input category.' }]}
      >
        <Input disabled={mode === 'edit'} />
      </Form.Item>
      <Form.Item
        label="Use Category"
        name="use_cate_gory"
        valuePropName="checked"
      >
        <Switch />
      </Form.Item>
      {showParentKey && (
        <Form.Item label="Parent Key">
          <Input disabled value={parentKey} />
        </Form.Item>
      )}
      <Form.Item
        label="Show Kind Name"
        name="show_kind_name"
        rules={[
          {
            validator: (_, value) => {
              if (!parentKey && !value) {
                return Promise.reject(
                  new Error('Please input show kind name for root node.')
                )
              }

              return Promise.resolve()
            }
          }
        ]}
      >
        <Input disabled={!!parentKey} />
      </Form.Item>
      {showCategoryName && (
        <Form.Item label="Show Category Name" name="show_category_name">
          <Input />
        </Form.Item>
      )}
      {mode === 'edit' && (
        <Form.Item label="Order" name="order">
          <InputNumber min={0} precision={0} style={{ width: '100%' }} />
        </Form.Item>
      )}
      <Form.Item
        label={
          <Space size={4}>
            <span>Labels</span>
            <Popover
              content={<LabelsInfo />}
              overlayStyle={{ zIndex: 1200 }}
              placement="right"
              trigger="hover"
            >
              <InfoCircleOutlined />
            </Popover>
          </Space>
        }
        name="labels"
        extra='JSON object, for example: {"storage": "Storage"}'
      >
        <Input.TextArea autoSize={{ minRows: 2, maxRows: 6 }} />
      </Form.Item>
      <Form.Item label="Label Key" name="label_key">
        <Input />
      </Form.Item>
      <Form.Item label="Show Service Prefix" name="show_service_prefix">
        <Input />
      </Form.Item>
      <Form.Item label="Measure Unit" name="measure_unit">
        <Input />
      </Form.Item>
    </Form>
  )
}

const LabelsInfo = () => {
  return (
    <Space direction="vertical" size={8} style={{ maxWidth: 520 }}>
      <Typography.Text>
        Labels map raw label values from cost details to billing display names.
        They can also generate leaf nodes in the billing item tree.
      </Typography.Text>
      <Typography.Text strong>Cost detail example</Typography.Text>
      <Typography.Text code copyable>
        {`{"labels":{"component":"Storage Cost"}}`}
      </Typography.Text>
      <Typography.Text strong>Category config example</Typography.Text>
      <Typography.Text code copyable>
        {`{"category":"Backup Cost","label_key":"component","labels":{"Storage Cost":"Single Region Storage","Replication Cost":"Replication","default":"Single Region Storage"}}`}
      </Typography.Text>
      <Typography.Text>
        Flow: read <Typography.Text code>labels["component"]</Typography.Text>{' '}
        from the cost detail, get{' '}
        <Typography.Text code>Storage Cost</Typography.Text>, then display{' '}
        <Typography.Text code>Single Region Storage</Typography.Text> from this
        mapping.
      </Typography.Text>
      <Typography.Text>
        If the source label is missing, the display name falls back to{' '}
        <Typography.Text code>labels["default"]</Typography.Text>.
      </Typography.Text>
      <Typography.Text>
        Leave labels empty when this category does not need to split by cost
        detail labels.
      </Typography.Text>
    </Space>
  )
}

const toTreeData = (
  data: FormsCategoryNode[],
  depth: number,
  inheritedOrderMap: Record<string, number>,
  openCategoryForm: (mode: CategoryFormMode, node?: CategoryTreeNode) => void
): CategoryTreeNode[] => {
  return data.map((d) => {
    const nodeKey = d.key || d.category || `${d.name}_${depth}`
    const nodeOrderMap = {
      ...inheritedOrderMap,
      ...(d.name && typeof d.order === 'number' ? { [d.name]: d.order } : {})
    }
    const children = d.children?.length
      ? toTreeData(d.children, depth + 1, nodeOrderMap, openCategoryForm)
      : []
    const isLeaf = children.length === 0
    const nextChildOrder = getNextOrder(d.children || [])
    const node: CategoryTreeNode = {
      category: d.category || d.categories?.[0],
      children,
      depth,
      inheritedOrderMap: nodeOrderMap,
      itemKey: d.key,
      key: nodeKey,
      name: d.name,
      nextChildOrder,
      order: d.order,
      parentKey: d.parent_key,
      title: (
        <div
          style={{
            alignItems: 'center',
            display: 'flex',
            gap: 8,
            justifyContent: 'space-between',
            width: '100%'
          }}
        >
          <Space size={4}>
            <Typography.Text>{d.name || d.category || d.key}</Typography.Text>
            {!!d.category && (
              <Typography.Text type="secondary">({d.category})</Typography.Text>
            )}
          </Space>
          <Space size={0}>
            {depth < 2 && (
              <Button
                size="small"
                type="link"
                onClick={(e) => {
                  e.stopPropagation()
                  openCategoryForm('create', node)
                }}
              >
                Add
              </Button>
            )}
            {isLeaf && !!(d.category || d.categories?.[0]) && (
              <Button
                size="small"
                type="link"
                onClick={(e) => {
                  e.stopPropagation()
                  openCategoryForm('edit', node)
                }}
              >
                Edit
              </Button>
            )}
          </Space>
        </div>
      )
    }

    return node
  })
}

const getExpandedKeys = (nodes: TreeDataNode[]): React.Key[] => {
  return nodes.flatMap((node) => [
    node.key,
    ...getExpandedKeys(node.children || [])
  ])
}

const toFormValues = (
  data: FormsCategoryItemShow,
  currentValues: CategoryFormValues
): CategoryFormValues => ({
  ...EMPTY_FORM_VALUES,
  ...data,
  _current_order_key: currentValues._current_order_key,
  _inherited_order_map: currentValues._inherited_order_map,
  _original_show_category_name: data.show_category_name,
  _original_show_kind_name: data.show_kind_name,
  labels: toJSONValueString(data.labels),
  order: getCurrentOrder(data, currentValues._current_order_key),
  order_map: toJSONValueString(data.order_map),
  use_cate_gory: data.use_cate_gory ?? true
})

const toPayload = (
  values: CategoryFormValues,
  mode: CategoryFormMode,
  tree: FormsCategoryNode[]
): FormsCategoryItemShow => {
  const labels = parseJSONObject(values.labels, 'Labels')
  const payload: FormsCategoryItemShow = {
    category: values.category?.trim(),
    label_key: values.label_key?.trim(),
    labels,
    measure_unit: values.measure_unit?.trim(),
    order_map:
      mode === 'create'
        ? buildOrderMap(values, labels)
        : updateCurrentOrderMap(values, tree),
    parent_key: values.parent_key?.trim(),
    show_category_name: values.show_category_name?.trim(),
    show_kind_name: values.parent_key ? '' : values.show_kind_name?.trim(),
    show_service_prefix: values.show_service_prefix,
    use_cate_gory: values.use_cate_gory ?? true
  }

  Object.keys(payload).forEach((key) => {
    const value = payload[key as keyof FormsCategoryItemShow]

    if (value === undefined || value === null || value === '') {
      delete payload[key as keyof FormsCategoryItemShow]
    }
  })

  return payload
}

const getCurrentOrder = (
  data: FormsCategoryItemShow,
  currentOrderKey?: string
) => {
  const key = currentOrderKey || getCurrentOrderKey(data)

  if (!key) {
    return undefined
  }

  return data.order_map?.[key]
}

const updateCurrentOrderMap = (
  values: CategoryFormValues,
  tree: FormsCategoryNode[]
) => {
  const inheritedOrderMap =
    parseJSONObject(values._inherited_order_map, 'Inherited Order Map') || {}
  const orderMap = {
    ...inheritedOrderMap,
    ...(parseJSONObject(values.order_map, 'Order Map') || {})
  }
  const originalKey = values._current_order_key || getCurrentOrderKey(values)
  const key = getUpdatedCurrentOrderKey(values) || originalKey
  const order = Number(values.order)

  if (!key || Number.isNaN(order)) {
    return orderMap
  }

  syncRenamedOrderKey(
    orderMap,
    values._original_show_kind_name,
    values.show_kind_name,
    tree
  )
  syncRenamedOrderKey(
    orderMap,
    values._original_show_category_name,
    values.show_category_name,
    tree
  )

  if (originalKey && originalKey !== key) {
    delete orderMap[originalKey]
  }

  return {
    ...orderMap,
    [key]: shouldReorderCurrentNode(values, key, originalKey)
      ? getReorderedCurrentOrder(values, tree, key) || order
      : order
  }
}

const syncRenamedOrderKey = (
  orderMap: Record<string, number>,
  oldKey: string | undefined,
  newKey: string | undefined,
  tree: FormsCategoryNode[]
) => {
  const from = oldKey?.trim()
  const to = newKey?.trim()

  if (!from || !to || from === to) {
    return
  }

  delete orderMap[from]
  orderMap[to] = findNodeOrder(tree, to) || orderMap[to] || 1
}

const getUpdatedCurrentOrderKey = (values: CategoryFormValues) => {
  const originalKey = values._current_order_key

  if (!originalKey) {
    return getCurrentOrderKey(values)
  }

  if (originalKey === values._original_show_kind_name) {
    return values.show_kind_name?.trim() || originalKey
  }

  if (originalKey === values._original_show_category_name) {
    return values.show_category_name?.trim() || originalKey
  }

  return originalKey
}

const getReorderedCurrentOrder = (
  values: CategoryFormValues,
  tree: FormsCategoryNode[],
  currentKey: string
) => {
  const parentNames = getParentPathForOrderKey(values, currentKey, false)

  if (parentNames.length === 0) {
    return getNextOrder(tree)
  }

  const parentNode = findNodeByPath(tree, parentNames)

  if (!parentNode) {
    return undefined
  }

  return getNextOrder(parentNode.children || [])
}

const shouldReorderCurrentNode = (
  values: CategoryFormValues,
  currentKey: string,
  originalKey?: string
) => {
  const oldParentNames = getParentPathForOrderKey(
    values,
    originalKey || currentKey,
    true
  )
  const newParentNames = getParentPathForOrderKey(values, currentKey, false)

  return oldParentNames.join('/') !== newParentNames.join('/')
}

const getParentPathForOrderKey = (
  values: CategoryFormValues,
  orderKey: string,
  useOriginal: boolean
) => {
  const labels = extractParentKeyNames(values.parent_key)
  const path = [
    (useOriginal
      ? values._original_show_kind_name
      : values.show_kind_name
    )?.trim() || labels[0],
    (useOriginal
      ? values._original_show_category_name
      : values.show_category_name
    )?.trim() || labels[1]
  ].filter(Boolean) as string[]

  if (path[path.length - 1] === orderKey) {
    path.pop()
  }

  return path
}

const getCurrentOrderKey = (
  values: Pick<
    CategoryFormValues,
    'show_category_name' | 'show_kind_name' | 'parent_key' | 'category'
  >
) => {
  const labels = extractParentKeyNames(values.parent_key)

  return (
    values.show_category_name?.trim() ||
    values.show_kind_name?.trim() ||
    labels[labels.length - 1] ||
    values.category?.trim()
  )
}

const buildOrderMap = (
  values: CategoryFormValues,
  labels?: Record<string, string>
) => {
  const orderMap: Record<string, number> = {
    ...(parseJSONObject(values._inherited_order_map, 'Inherited Order Map') ||
      {})
  }
  const showKindName = values.show_kind_name?.trim()
  const showCategoryName = values.show_category_name?.trim()
  const nextOrder = getFormNextOrder(values)

  if (showKindName) {
    orderMap[showKindName] = nextOrder
  }

  if (showCategoryName) {
    orderMap[showCategoryName] = nextOrder
  }

  Object.entries(labels || {})
    .filter(([key, label]) => key !== 'default' && !!label)
    .map(([, label]) => label.trim())
    .forEach((label, index) => {
      if (!orderMap[label]) {
        orderMap[label] = nextOrder + index
      }
    })

  return Object.keys(orderMap).length ? orderMap : undefined
}

const getFormNextOrder = (values: CategoryFormValues) => {
  const nextOrder = Number(values._next_order)

  return Number.isNaN(nextOrder) ? 1 : nextOrder
}

const getNextOrder = (nodes: FormsCategoryNode[]) => {
  const maxOrder = nodes.reduce((max, node) => {
    return typeof node.order === 'number' ? Math.max(max, node.order) : max
  }, 0)

  return maxOrder + 1
}

const findNodeOrder = (
  nodes: FormsCategoryNode[],
  name: string
): number | undefined => {
  for (const node of nodes) {
    if (node.name === name) {
      return node.order
    }

    const childOrder = findNodeOrder(node.children || [], name)

    if (typeof childOrder === 'number') {
      return childOrder
    }
  }

  return undefined
}

const findNodeByPath = (
  nodes: FormsCategoryNode[],
  names: string[]
): FormsCategoryNode | undefined => {
  if (names.length === 0) {
    return undefined
  }

  const [name, ...restNames] = names
  const node = nodes.find((item) => item.name === name)

  if (!node || restNames.length === 0) {
    return node
  }

  return findNodeByPath(node.children || [], restNames)
}

const extractParentKeyNames = (parentKey?: string) => {
  const key = parentKey?.trim()

  if (!key) {
    return []
  }

  return key
    .replace(/^kind:/, '')
    .split('/')
    .filter(Boolean)
}

const toJSONValueString = (value?: Record<string, unknown>) => {
  if (!value || Object.keys(value).length === 0) {
    return ''
  }

  return JSON.stringify(value, null, 2)
}

const parseJSONObject = (value: string | undefined, field: string) => {
  const input = value?.trim()

  if (!input) {
    return undefined
  }

  try {
    const parsed = JSON.parse(input)

    if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
      throw new Error(`${field} must be a JSON object.`)
    }

    return parsed
  } catch {
    throw new Error(`${field} must be valid JSON.`)
  }
}
