import { DeleteOutlined, PlusOutlined, ReloadOutlined, SaveOutlined } from '@ant-design/icons';
import { Button, Flex, Input, Modal, Select, Table, message } from 'antd';
import type {
  CellValueChangedEvent,
  ColDef,
  GridApi,
  ICellRendererParams,
  RowSelectionOptions,
  ValueFormatterParams,
} from 'ag-grid-community';
import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community';
import { AgGridReact } from 'ag-grid-react';
import {
  createContext,
  useDeferredValue,
  use,
  useEffect,
  useMemo,
  useRef,
  useState,
  type PropsWithChildren,
} from 'react';
import { renderModalWithEnterConfirm } from '../../../app/main/modalKeyboard';
import { SelectUI, type SelectOptionItem } from '../../../components/inputs/select';
import {
  deleteStockAlertRow,
  fetchStockAlerts,
  searchStockAlertCandidates,
  saveStockAlertRows,
  type StockAlertDraftRow,
  type StockAlertFilterValue,
  type StockAlertSearchItem,
  type StockAlertType,
} from './stockAlertApi';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-quartz.css';
import './StockAlertLayout.css';

ModuleRegistry.registerModules([AllCommunityModule]);

const FILTER_OPTIONS: SelectOptionItem[] = [
  { code: 'all', value: '전체' },
  { code: 'price', value: '현재가' },
  { code: 'top3', value: '등락폭이 큰 상위3종목' },
];

const ALERT_TYPE_LABEL_MAP = new Map<StockAlertType, string>([
  ['price', '현재가'],
  ['top3', '등락폭이 큰 상위3종목'],
]);

const ALERT_TYPE_VALUES = Array.from(ALERT_TYPE_LABEL_MAP.keys());

type StockAlertLayoutContextValue = {
  filterValue: StockAlertFilterValue;
  rows: StockAlertDraftRow[];
  isLoading: boolean;
  pendingFocusRowId: string | null;
  setFilterValue: (value: StockAlertFilterValue) => void;
  updateRow: (rowId: string, patch: Partial<StockAlertDraftRow>) => void;
  addRow: (item: StockAlertSearchItem) => boolean;
  clearPendingFocusRowId: () => void;
  refreshRows: () => Promise<void>;
  saveRows: () => Promise<void>;
  deleteRows: (rowIds: string[]) => Promise<void>;
};

const StockAlertLayoutContext = createContext<StockAlertLayoutContextValue | null>(null);

function useStockAlertLayoutContext() {
  const context = use(StockAlertLayoutContext);

  if (!context) {
    throw new Error('StockAlertLayoutProvider가 필요합니다.');
  }

  return context;
}

function getAlertTypeLabel(value: StockAlertType) {
  return ALERT_TYPE_LABEL_MAP.get(value) ?? value;
}

function toAlertTypeLabels(values: StockAlertType[]) {
  return values.map((value) => getAlertTypeLabel(value));
}

function mergeDraftRows(previousRows: StockAlertDraftRow[], nextRows: StockAlertDraftRow[]) {
  const dirtyRowMap = new Map(
    previousRows
      .filter((row) => row.isDirty)
      .map((row) => [row.persistedId ?? row.id, row]),
  );

  return nextRows.map((row) => {
    const dirtyRow = dirtyRowMap.get(row.persistedId ?? row.id);

    if (!dirtyRow) {
      return row;
    }

    return {
      ...row,
      stockCode: dirtyRow.stockCode,
      stockName: dirtyRow.stockName,
      alertTypes: dirtyRow.alertTypes,
      alertTypeLabels: toAlertTypeLabels(dirtyRow.alertTypes),
      isDirty: true,
    };
  });
}

export function StockAlertLayoutProvider({ children }: PropsWithChildren) {
  const [messageApi, contextHolder] = message.useMessage();
  const [filterValue, setFilterValue] = useState<StockAlertFilterValue>('all');
  const [rows, setRows] = useState<StockAlertDraftRow[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [pendingFocusRowId, setPendingFocusRowId] = useState<string | null>(null);

  const refreshRows = async () => {
    setIsLoading(true);

    try {
      const nextRows = await fetchStockAlerts(filterValue);
      setRows((previousRows) => mergeDraftRows(previousRows, nextRows));
    } catch (error) {
      messageApi.error(error instanceof Error ? error.message : '종목 알림 목록을 불러오지 못했습니다.');
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    void refreshRows();
  }, [filterValue]);

  const updateRow = (rowId: string, patch: Partial<StockAlertDraftRow>) => {
    setRows((previousRows) =>
      previousRows.map((row) =>
        row.id === rowId
          ? {
              ...row,
              ...patch,
              alertTypes: (patch.alertTypes ?? row.alertTypes) as StockAlertType[],
              alertTypeLabels: toAlertTypeLabels((patch.alertTypes ?? row.alertTypes) as StockAlertType[]),
              isDirty: true,
            }
          : row,
      ),
    );
  };

  const addRow = (item: StockAlertSearchItem) => {
    const nextAlertTypes: StockAlertType[] = [filterValue === 'all' ? 'price' : filterValue];

    if (rows.some((row) => row.stockCode === item.stockCode)) {
      messageApi.warning('이미 추가된 종목입니다.');
      return false;
    }

    const nextRowId = `stock-alert-draft-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;

    setRows((previousRows) => [
      {
        id: nextRowId,
        persistedId: null,
        stockCode: item.stockCode,
        stockName: item.stockName,
        alertTypes: nextAlertTypes,
        alertTypeLabels: toAlertTypeLabels(nextAlertTypes),
        currentPrice: null,
        changeRate: null,
        volumeRate5d: null,
        quotedAt: null,
        createdAt: null,
        updatedAt: null,
        isDirty: true,
        isNew: true,
      },
      ...previousRows,
    ]);
    setPendingFocusRowId(nextRowId);
    return true;
  };

  const saveRows = async () => {
    const dirtyRows = rows.filter((row) => row.isDirty);

    if (!dirtyRows.length) {
      return;
    }

    if (dirtyRows.some((row) => !row.stockName.trim())) {
      messageApi.error('종목명을 입력한 뒤 저장해 주세요.');
      return;
    }

    if (dirtyRows.some((row) => !row.stockCode.trim())) {
      messageApi.error('종목 검색으로 추가한 뒤 저장해 주세요.');
      return;
    }

    if (dirtyRows.some((row) => !row.alertTypes.length)) {
      messageApi.error('알림유형을 하나 이상 선택해 주세요.');
      return;
    }

    setIsLoading(true);

    try {
      await saveStockAlertRows(dirtyRows);
      await refreshRows();
      messageApi.success('종목 알림을 저장했습니다.');
    } catch (error) {
      messageApi.error(error instanceof Error ? error.message : '종목 알림 저장에 실패했습니다.');
      setIsLoading(false);
    }
  };

  const deleteRows = async (rowIds: string[]) => {
    if (!rowIds.length) {
      return;
    }

    setIsLoading(true);

    try {
      const persistedIds = rowIds
        .map((rowId) => rows.find((row) => row.id === rowId)?.persistedId ?? null)
        .filter((value): value is string => Boolean(value));

      await Promise.all(persistedIds.map((id) => deleteStockAlertRow(id)));
      setRows((previousRows) => previousRows.filter((row) => !rowIds.includes(row.id)));
      await refreshRows();
      messageApi.success('선택한 종목 알림을 삭제했습니다.');
    } catch (error) {
      messageApi.error(error instanceof Error ? error.message : '종목 알림 삭제에 실패했습니다.');
      setIsLoading(false);
    }
  };

  const contextValue = useMemo<StockAlertLayoutContextValue>(
    () => ({
      filterValue,
      rows,
      isLoading,
      pendingFocusRowId,
      setFilterValue,
      updateRow,
      addRow,
      clearPendingFocusRowId: () => {
        setPendingFocusRowId(null);
      },
      refreshRows,
      saveRows,
      deleteRows,
    }),
    [filterValue, isLoading, pendingFocusRowId, rows],
  );

  return (
    <StockAlertLayoutContext.Provider value={contextValue}>
      {contextHolder}
      {children}
    </StockAlertLayoutContext.Provider>
  );
}

export function StockAlertFilterPane() {
  const { filterValue, setFilterValue } = useStockAlertLayoutContext();

  return (
    <Flex className="stock-alert-layout stock-alert-layout__filter">
      <SelectUI
        data={FILTER_OPTIONS}
        value={filterValue}
        allowClear={false}
        placeholder="알림유형"
        onChange={(nextCode) => {
          setFilterValue((nextCode as StockAlertFilterValue | undefined) ?? 'all');
        }}
      />
    </Flex>
  );
}

function formatPrice(value: number | null) {
  if (typeof value !== 'number' || Number.isNaN(value)) {
    return '';
  }

  return new Intl.NumberFormat('ko-KR').format(value);
}

function formatChangeRate(value: number | null) {
  if (typeof value !== 'number' || Number.isNaN(value)) {
    return '';
  }

  return `${value > 0 ? '+' : ''}${value.toFixed(2)}%`;
}

function formatQuotedAt(value: string | null) {
  if (!value) {
    return '';
  }

  const date = new Date(value);

  if (Number.isNaN(date.getTime())) {
    return '';
  }

  return new Intl.DateTimeFormat('ko-KR', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false,
  }).format(date);
}

function ChangeRateCellRenderer({ value }: ICellRendererParams<StockAlertDraftRow, number | null>) {
  const numericValue = typeof value === 'number' ? value : null;
  const className =
    numericValue === null
      ? 'stock-alert-layout__change-rate--flat'
      : numericValue > 0
        ? 'stock-alert-layout__change-rate--up'
        : numericValue < 0
          ? 'stock-alert-layout__change-rate--down'
          : 'stock-alert-layout__change-rate--flat';

  return <span className={className}>{formatChangeRate(numericValue)}</span>;
}

type AlertTypeCellRendererProps = ICellRendererParams<StockAlertDraftRow> & {
  isOpen?: boolean;
  onOpen?: (rowId: string) => void;
  onClose?: () => void;
};

function AlertTypeCellEditorRenderer({ data, isOpen = false, onOpen, onClose }: AlertTypeCellRendererProps) {
  const { updateRow } = useStockAlertLayoutContext();

  if (!data) {
    return null;
  }

  return (
    <div
      className={`stock-alert-layout__alert-type-editor${isOpen ? ' is-open' : ''}`}
      onClick={(event) => {
        event.stopPropagation();
        onOpen?.(data.id);
      }}
    >
      <Select<StockAlertType[]>
        mode="multiple"
        size="small"
        open={isOpen}
        autoFocus={isOpen}
        value={data.alertTypes}
        options={ALERT_TYPE_VALUES.map((value) => ({
          value,
          label: getAlertTypeLabel(value),
        }))}
        placeholder="알림유형 선택"
        maxTagCount="responsive"
        allowClear={false}
        popupMatchSelectWidth={false}
        className="stock-alert-layout__alert-type-select"
        onChange={(nextValues) => {
          updateRow(data.id, {
            alertTypes: nextValues as StockAlertType[],
          });
        }}
        onOpenChange={(nextOpen) => {
          if (nextOpen) {
            onOpen?.(data.id);
            return;
          }

          onClose?.();
        }}
      />
    </div>
  );
}

function StockSearchModal({
  open,
  onCancel,
  onSelect,
}: {
  open: boolean;
  onCancel: () => void;
  onSelect: (item: StockAlertSearchItem) => void;
}) {
  const [keyword, setKeyword] = useState('');
  const deferredKeyword = useDeferredValue(keyword);
  const [items, setItems] = useState<StockAlertSearchItem[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [messageApi, contextHolder] = message.useMessage();

  const search = async (rawValue: string) => {
    const trimmedValue = rawValue.trim();

    if (!trimmedValue) {
      setItems([]);
      return;
    }

    setIsLoading(true);

    try {
      const nextItems = await searchStockAlertCandidates(trimmedValue, 20);
      setItems(nextItems);

      if (!nextItems.length) {
        messageApi.info('조회 결과가 없습니다.');
      }
    } catch (error) {
      messageApi.error(error instanceof Error ? error.message : '종목 검색에 실패했습니다.');
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    if (!open) {
      setKeyword('');
      setItems([]);
      setIsLoading(false);
    }
  }, [open]);

  return (
    <>
      {contextHolder}
      <Modal
        open={open}
        title="종목 검색"
        onCancel={onCancel}
        footer={null}
        width={720}
        destroyOnHidden
        modalRender={renderModalWithEnterConfirm}
      >
        <div className="stock-alert-layout__search-modal">
          <Input.Search
            value={keyword}
            placeholder="종목명 또는 종목코드"
            enterButton="조회"
            allowClear
            onChange={(event) => {
              setKeyword(event.target.value);
            }}
            onSearch={(value) => {
              void search(value);
            }}
          />
          <Table<StockAlertSearchItem>
            size="small"
            rowKey={(record) => record.stockCode}
            loading={isLoading}
            pagination={false}
            dataSource={items}
            scroll={{ y: 360 }}
            locale={{
              emptyText: deferredKeyword.trim() ? '조회 결과가 없습니다.' : '종목명 또는 종목코드를 입력하세요.',
            }}
            onRow={(record) => ({
              onDoubleClick: () => {
                onSelect(record);
              },
            })}
            columns={[
              {
                title: '종목코드',
                dataIndex: 'stockCode',
                width: 140,
              },
              {
                title: '종목명',
                dataIndex: 'stockName',
              },
              {
                title: '시장구분',
                dataIndex: 'market',
                width: 140,
              },
              {
                key: 'action',
                width: 88,
                render: (_, record) => (
                  <Button
                    type="link"
                    onClick={() => {
                      onSelect(record);
                    }}
                  >
                    선택
                  </Button>
                ),
              },
            ]}
          />
        </div>
      </Modal>
    </>
  );
}

export function StockAlertGridPane() {
  const {
    rows,
    isLoading,
    pendingFocusRowId,
    updateRow,
    addRow,
    clearPendingFocusRowId,
    refreshRows,
    saveRows,
    deleteRows,
  } = useStockAlertLayoutContext();
  const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
  const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
  const [activeAlertTypeEditorRowId, setActiveAlertTypeEditorRowId] = useState<string | null>(null);
  const gridApiRef = useRef<GridApi<StockAlertDraftRow> | null>(null);

  const rowSelection = useMemo<RowSelectionOptions>(
    () => ({
      mode: 'multiRow',
      enableClickSelection: true,
      checkboxes: true,
      headerCheckbox: true,
    }),
    [],
  );

  const columnDefs = useMemo<ColDef<StockAlertDraftRow>[]>(
    () => [
      {
        field: 'stockName',
        headerName: '종목명',
        editable: false,
        minWidth: 150,
        flex: 1.05,
      },
      {
        field: 'changeRate',
        headerName: '등락률',
        editable: false,
        minWidth: 104,
        flex: 0.72,
        cellRenderer: ChangeRateCellRenderer,
      },
      {
        field: 'currentPrice',
        headerName: '현재가',
        editable: false,
        minWidth: 110,
        flex: 0.8,
        valueFormatter: (params: ValueFormatterParams<StockAlertDraftRow, number | null>) => formatPrice(params.value ?? null),
      },
      {
        field: 'quotedAt',
        headerName: '기준일시',
        editable: false,
        minWidth: 168,
        flex: 1,
        valueFormatter: (params: ValueFormatterParams<StockAlertDraftRow, string | null>) => formatQuotedAt(params.value ?? null),
      },
      {
        field: 'alertTypes',
        headerName: '알림유형',
        editable: false,
        minWidth: 180,
        flex: 0.95,
        cellRenderer: AlertTypeCellEditorRenderer,
        cellRendererParams: (params: ICellRendererParams<StockAlertDraftRow>) => ({
          isOpen: params.data?.id === activeAlertTypeEditorRowId,
          onOpen: (rowId: string) => {
            setActiveAlertTypeEditorRowId(rowId);
          },
          onClose: () => {
            setActiveAlertTypeEditorRowId((currentValue) => (currentValue === params.data?.id ? null : currentValue));
          },
        }),
        sortable: false,
        filter: false,
        valueFormatter: (params: ValueFormatterParams<StockAlertDraftRow, StockAlertType[] | null>) =>
          Array.isArray(params.value) ? toAlertTypeLabels(params.value).join(', ') : '',
      },
      {
        field: 'stockCode',
        headerName: '종목코드',
        hide: true,
      },
    ],
    [activeAlertTypeEditorRowId],
  );

  const defaultColDef = useMemo<ColDef<StockAlertDraftRow>>(
    () => ({
      sortable: true,
      filter: true,
      resizable: true,
    }),
    [],
  );

  const handleCellValueChanged = (event: CellValueChangedEvent<StockAlertDraftRow>) => {
    if (!event.data) {
      return;
    }

    if (event.colDef.field === 'stockName') {
      updateRow(event.data.id, {
        stockName: String(event.newValue ?? ''),
      });
    }
  };

  useEffect(() => {
    if (!pendingFocusRowId || !gridApiRef.current) {
      return;
    }

    const rowIndex = rows.findIndex((row) => row.id === pendingFocusRowId);

    if (rowIndex < 0) {
      return;
    }

    gridApiRef.current.ensureIndexVisible(rowIndex, 'top');
    gridApiRef.current.setFocusedCell(rowIndex, 'alertTypes');
    setActiveAlertTypeEditorRowId(pendingFocusRowId);
    clearPendingFocusRowId();
  }, [clearPendingFocusRowId, pendingFocusRowId, rows]);

  return (
    <>
      <StockSearchModal
        open={isSearchModalOpen}
        onCancel={() => {
          setIsSearchModalOpen(false);
        }}
        onSelect={(item) => {
          const added = addRow(item);

          if (added) {
            setIsSearchModalOpen(false);
          }
        }}
      />
      <div className="stock-alert-layout stock-alert-layout__grid">
        <div className="stock-alert-layout__toolbar">
          <Button
            icon={<PlusOutlined />}
            onClick={() => {
              setIsSearchModalOpen(true);
            }}
          />
          <Button icon={<DeleteOutlined />} danger disabled={!selectedRowIds.length} onClick={() => void deleteRows(selectedRowIds)} />
          <Button icon={<SaveOutlined />} type="primary" onClick={() => void saveRows()} />
          <Button icon={<ReloadOutlined />} onClick={() => void refreshRows()} />
        </div>
        <div className="stock-alert-layout__surface ag-theme-quartz">
          <AgGridReact<StockAlertDraftRow>
            loading={isLoading}
            rowData={rows}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
            rowSelection={rowSelection}
            suppressRowClickSelection={false}
            getRowId={(params) => params.data.id}
            onGridReady={(event) => {
              gridApiRef.current = event.api;
            }}
            onCellValueChanged={handleCellValueChanged}
            onCellClicked={(event) => {
              const rowId = event.data?.id ?? null;

              if (event.colDef.field === 'alertTypes' && rowId) {
                setActiveAlertTypeEditorRowId(rowId);
                return;
              }

              setActiveAlertTypeEditorRowId(null);
            }}
            onSelectionChanged={(event) => {
              setSelectedRowIds(event.api.getSelectedRows().map((row) => row.id));
            }}
          />
        </div>
      </div>
    </>
  );
}
