import { useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import type { Dispatch, SetStateAction } from 'react';

import { parseMaterialsFromExcel } from '@/lib/materialImportParser';
import {
  createMaterials,
  deleteMaterialById,
  deleteMaterialsByIds,
  updateMaterial,
} from '@/services/materialService';
import { recognizeMaterialsFromDocument } from '@/services/documentExtractionService';
import {
  deleteSourceFileById,
  updateSourceFileExtractionState,
  uploadSourceFile,
} from '@/services/sourceFileService';
import type {
  AuthUser,
  ProcurementMaterial,
  SourceFileRecord,
} from '@/types';

import { UPLOAD_SUCCESS_TOAST_OPTIONS } from '../lib/constants';
import {
  buildUploadedMaterialInput,
  isLocalDraftMaterial,
  isSpreadsheetFile,
  matchesMaterialDeletionTarget,
  renderUploadError,
} from '../lib/materialHelpers';

export interface FailedDocumentRecognition {
  fileName: string;
  error: string;
  code?: string;
  retryable?: boolean;
}

export interface UploadMaterialMutationResult {
  batchId: string;
  records: SourceFileRecord[];
  materials: ProcurementMaterial[];
  spreadsheetFileCount: number;
  recognizedDocumentCount: number;
  unrecognizedAttachmentCount: number;
  failedDocumentCount: number;
  failedDocuments: FailedDocumentRecognition[];
  failedFileCount: number;
  attachmentCount: number;
}

export interface UseMaterialMutationsOptions {
  /** 当前选择的归属批次 ID（来自 uploadBatchId state） */
  getUploadBatchId: () => string;
  /** 当前登录用户（用于填充 created_by / uploaded_by 字段） */
  getUser: () => AuthUser | null;
  /** 本地 draft 列表 + setter；删除流程会读写它 */
  uploadedMaterials: ProcurementMaterial[];
  setUploadedMaterials: Dispatch<SetStateAction<ProcurementMaterial[]>>;
  /** 所有可见物料（用于 batch delete 区分 draft vs 落表） */
  getMaterials: () => ProcurementMaterial[];
  /** 删除/批删后清理展开行 */
  setExpandedIds: Dispatch<SetStateAction<Set<string>>>;
  /** 删除/上传/批删后清理选中行 */
  setSelectedKeys: Dispatch<SetStateAction<Set<string>>>;
  /** 编辑保存成功后关闭弹窗 */
  setEditingMaterial: Dispatch<SetStateAction<ProcurementMaterial | null>>;
  /** 上传成功后由 page 决定如何重置筛选/分页等 */
  onUploadSuccess?: (batchId: string) => void;
}

/**
 * 把 Materials 页的 4 个 mutation（上传 / 单删 / 批删 / 编辑保存）封装成一个 hook。
 *
 * 设计取舍：
 *   - mutation 需要读 page 层的若干 state（uploadBatchId / user / materials），
 *     用 getter 函数传入而不是直传值，避免 hook 内部 stale closure。
 *   - 副作用按归属切分：通用副作用（query 缓存失效、toast）留在 hook；
 *     页面状态副作用（搜索框重置、关闭编辑弹窗）通过 setter / 回调暴露给调用方。
 *   - upload 失败时尝试回滚：删除已上传 source_file 与已写入 materials；
 *     这部分行为不变，封装在 mutationFn 里。
 */
export function useMaterialMutations(opts: UseMaterialMutationsOptions) {
  const queryClient = useQueryClient();
  const {
    getUploadBatchId,
    getUser,
    uploadedMaterials,
    setUploadedMaterials,
    getMaterials,
    setExpandedIds,
    setSelectedKeys,
    setEditingMaterial,
    onUploadSuccess,
  } = opts;

  const uploadMutation = useMutation<UploadMaterialMutationResult, Error, File[]>({
    mutationFn: async (files: File[]) => {
      const batchId = getUploadBatchId();
      if (!batchId) {
        throw new Error('请先选择归属批次');
      }
      const user = getUser();
      const uploadedBy =
        user?.displayName?.trim() || user?.username?.trim() || user?.email?.trim() || '当前用户';
      const createdByUserId = user?.id?.trim() || undefined;

      const records: SourceFileRecord[] = [];
      const materials: ProcurementMaterial[] = [];
      let spreadsheetFileCount = 0;
      let recognizedDocumentCount = 0;
      let unrecognizedAttachmentCount = 0;
      let failedDocumentCount = 0;
      const failedDocuments: FailedDocumentRecognition[] = [];
      let failedFileCount = 0;

      for (const file of files) {
        let record: SourceFileRecord | null = null;
        try {
          record = await uploadSourceFile({ batchId, file, uploadedBy });
          records.push(record);
          const sourceFileId = record.id;
          const sourceFileName = record.file_name;
          if (isSpreadsheetFile(file.name)) {
            const parsedRows = await parseMaterialsFromExcel(file);
            const materialInputs = parsedRows.map(parsedRow =>
              buildUploadedMaterialInput(record, createdByUserId, parsedRow),
            );

            if (materialInputs.length > 0) {
              const insertedMaterials = await createMaterials(materialInputs);
              materials.push(...insertedMaterials);
            }
            record = await updateSourceFileExtractionState(record.id, {
              extraction_status: 'local_parsed',
              extracted_material_count: materialInputs.length,
              extraction_error: null,
              extraction_model: null,
              extraction_summary: `Excel 本地解析成功：${materialInputs.length} 项物料`,
            });
            records[records.length - 1] = record;
            spreadsheetFileCount += 1;
          } else {
            // ── 非 Excel 文件：交给后端文档提取阶段 LLM 识别并落库 ──
            try {
              record = await updateSourceFileExtractionState(record.id, {
                extraction_status: 'llm_processing',
                extraction_model: null,
                extraction_error: null,
              });
              records[records.length - 1] = record;
              const recognitionResult = await recognizeMaterialsFromDocument(file, {
                batchId,
                sourceFileId,
                sourceFileName,
                createdBy: createdByUserId,
              });
              record = await updateSourceFileExtractionState(record.id, {
                extraction_status: 'llm_succeeded',
                extraction_model: recognitionResult.extractionModel || null,
                extraction_summary: recognitionResult.summary,
                extraction_error: null,
                extracted_material_count: recognitionResult.materials.length,
              });
              records[records.length - 1] = record;
              if (recognitionResult.materials.length > 0) {
                materials.push(...recognitionResult.materials);
                recognizedDocumentCount += 1;
              } else {
                unrecognizedAttachmentCount += 1;
                toast.info(
                  `文件 ${file.name} 未识别到采购物料，已作为附件保留。识别摘要: ${recognitionResult.summary}`,
                );
              }
            } catch (extractError) {
              // 识别失败不阻断整体上传，文件已作为附件保留
              failedDocumentCount += 1;
              const errMsg = extractError instanceof Error ? extractError.message : String(extractError);
              const errorMeta = extractError as {
                code?: unknown;
                retryable?: unknown;
                sourceFileName?: unknown;
              };
              failedDocuments.push({
                fileName: typeof errorMeta.sourceFileName === 'string' ? errorMeta.sourceFileName : file.name,
                error: errMsg,
                code: typeof errorMeta.code === 'string' ? errorMeta.code : undefined,
                retryable: typeof errorMeta.retryable === 'boolean' ? errorMeta.retryable : undefined,
              });
              try {
                record = await updateSourceFileExtractionState(record.id, {
                  extraction_status: 'llm_failed',
                  extraction_model: null,
                  extraction_error: `文档识别失败: ${errMsg}`,
                  extracted_material_count: 0,
                });
                records[records.length - 1] = record;
              } catch (statusError) {
                console.error(`[文档识别] ${file.name} 状态更新失败:`, statusError);
              }
              console.error(`[文档识别] ${file.name} 失败:`, errMsg);
              toast.error(
                `文件 ${file.name} 物料识别失败: ${errMsg}\n附件已保留，请检查后端日志。`,
              );
            }
          }
        } catch (error) {
          failedFileCount += 1;
          if (record) {
            const recordIndex = records.findIndex(item => item.id === record?.id);
            if (recordIndex >= 0) {
              records.splice(recordIndex, 1);
            }
          }
          try {
            if (record) {
              await deleteSourceFileById(record.id);
            }
          } catch {
            // Best-effort rollback only.
          }
          toast.error(
            `文件 ${file.name} 上传或解析失败: ${error instanceof Error ? error.message : String(error)}`,
          );
        }
      }

      if (records.length === 0 && materials.length === 0 && failedFileCount > 0) {
        throw new Error('所有文件上传或解析失败。');
      }

      return {
        batchId,
        records,
        materials,
        spreadsheetFileCount,
        recognizedDocumentCount,
        unrecognizedAttachmentCount,
        failedDocumentCount,
        failedDocuments,
        failedFileCount,
        attachmentCount: records.length - spreadsheetFileCount - recognizedDocumentCount,
      };
    },
    onSuccess: ({
      batchId,
      materials,
      spreadsheetFileCount,
      recognizedDocumentCount,
      unrecognizedAttachmentCount,
      failedDocumentCount,
      failedDocuments,
    }) => {
      if (materials.length > 0) {
        queryClient.setQueryData<ProcurementMaterial[]>(['materials-supabase'], prev => [
          ...(prev ?? []),
          ...materials,
        ]);
      }
      setSelectedKeys(new Set());
      queryClient.invalidateQueries({ queryKey: ['materials-source-files'] });
      queryClient.invalidateQueries({ queryKey: ['materials-supabase'] });
      onUploadSuccess?.(batchId);
      if (failedDocumentCount > 0) {
        const hasRateLimit = failedDocuments.some(item => item.code === 'LLM_RATE_LIMIT');
        const retryHint = hasRateLimit
          ? 'LLM 调用达到速率限制。附件已保留，请稍后重试。'
          : '附件已保留，请检查文件或后端日志。';
        toast.error(
          `${failedDocumentCount} 个文件识别失败：${retryHint}`,
          { duration: 12000 },
        );
      } else if (spreadsheetFileCount > 0 && recognizedDocumentCount > 0) {
        toast.success(
          `文件处理成功：${spreadsheetFileCount} 个 Excel 已本地解析，${recognizedDocumentCount} 个非 Excel 已完成 GLM 识别。`,
          UPLOAD_SUCCESS_TOAST_OPTIONS,
        );
      } else if (spreadsheetFileCount > 0) {
        toast.success('Excel 本地解析成功', UPLOAD_SUCCESS_TOAST_OPTIONS);
      } else if (recognizedDocumentCount > 0) {
        toast.success('非 Excel 文件 GLM 识别成功', UPLOAD_SUCCESS_TOAST_OPTIONS);
      } else if (unrecognizedAttachmentCount > 0 && failedDocumentCount === 0) {
        toast.info('未识别到采购物料，文件已作为附件保留。');
      } else if (failedDocumentCount === 0) {
        toast.success('文件上传成功，已作为附件保留。', UPLOAD_SUCCESS_TOAST_OPTIONS);
      }
    },
    onError: error => {
      toast.error(renderUploadError(error));
    },
  });

  const deleteMutation = useMutation<
    ProcurementMaterial,
    Error,
    ProcurementMaterial,
    {
      previousUploadedMaterials: ProcurementMaterial[];
      previousSupabaseMaterials?: ProcurementMaterial[];
      affectedLocalCount: number;
    }
  >({
    onMutate: async row => {
      const previousUploadedMaterials = uploadedMaterials;
      const previousSupabaseMaterials = queryClient.getQueryData<ProcurementMaterial[]>([
        'materials-supabase',
      ]);
      const isUploadedDraft = isLocalDraftMaterial(row);
      const affectedLocalCount = previousUploadedMaterials.filter(item =>
        matchesMaterialDeletionTarget(item, row),
      ).length;
      const affectedIds = new Set([
        ...previousUploadedMaterials
          .filter(item => matchesMaterialDeletionTarget(item, row))
          .map(item => item.id),
        ...(previousSupabaseMaterials ?? [])
          .filter(item => matchesMaterialDeletionTarget(item, row))
          .map(item => item.id),
      ]);

      await queryClient.cancelQueries({ queryKey: ['materials-supabase'] });

      if (isUploadedDraft) {
        setUploadedMaterials(prev =>
          prev.filter(item => !matchesMaterialDeletionTarget(item, row)),
        );
      } else {
        queryClient.setQueryData<ProcurementMaterial[]>(['materials-supabase'], prev => {
          if (!prev) {
            return prev;
          }
          return prev.filter(item => !matchesMaterialDeletionTarget(item, row));
        });
      }

      setExpandedIds(prev => {
        const next = new Set(prev);
        affectedIds.forEach(id => next.delete(id));
        return next;
      });
      setSelectedKeys(prev => {
        const next = new Set(prev);
        affectedIds.forEach(id => next.delete(id));
        return next;
      });

      return { previousUploadedMaterials, previousSupabaseMaterials, affectedLocalCount };
    },
    mutationFn: async row => {
      const isUploadedDraft = isLocalDraftMaterial(row);
      if (!isUploadedDraft) {
        await deleteMaterialById(row.id);
      }
      return row;
    },
    onSuccess: (_deletedRow, row, context) => {
      queryClient.invalidateQueries({ queryKey: ['materials-supabase'] });
      const removedCount = context?.affectedLocalCount ?? 0;
      toast.success(
        removedCount > 0 ? `已移除 ${removedCount} 条本地材料记录。` : `已删除材料：${row.material_name}`,
      );
    },
    onError: (error, _row, context) => {
      if (context?.previousUploadedMaterials) {
        setUploadedMaterials(context.previousUploadedMaterials);
      }
      if (context?.previousSupabaseMaterials !== undefined) {
        queryClient.setQueryData(['materials-supabase'], context.previousSupabaseMaterials);
      }
      toast.error(renderUploadError(error));
    },
  });

  const batchDeleteMutation = useMutation<void, Error, string[]>({
    mutationFn: async (ids: string[]) => {
      const allMaterials = getMaterials();
      const localDraftIds = ids.filter(id => {
        const row = allMaterials.find(m => m.id === id);
        return row && isLocalDraftMaterial(row);
      });
      const persistedIds = ids.filter(id => !localDraftIds.includes(id));

      if (localDraftIds.length > 0) {
        setUploadedMaterials(prev => prev.filter(item => !localDraftIds.includes(item.id)));
      }
      if (persistedIds.length > 0) {
        await deleteMaterialsByIds(persistedIds);
      }
    },
    onSuccess: (_data, ids) => {
      setSelectedKeys(prev => {
        const next = new Set(prev);
        ids.forEach(id => next.delete(id));
        return next;
      });
      setExpandedIds(prev => {
        const next = new Set(prev);
        ids.forEach(id => next.delete(id));
        return next;
      });
      queryClient.invalidateQueries({ queryKey: ['materials-supabase'] });
      toast.success(`已批量删除 ${ids.length} 条材料。`);
    },
    onError: error => {
      queryClient.invalidateQueries({ queryKey: ['materials-supabase'] });
      toast.error(renderUploadError(error));
    },
  });

  const updateMutation = useMutation({
    mutationFn: async (params: {
      id: string;
      updates: Partial<Omit<ProcurementMaterial, 'id' | 'created_at'>>;
    }) => {
      return updateMaterial(params.id, params.updates);
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['materials-supabase'] });
      toast.success('材料已更新');
      setEditingMaterial(null);
    },
    onError: error => {
      toast.error(`更新失败：${error.message}`);
    },
  });

  return {
    uploadMutation,
    deleteMutation,
    batchDeleteMutation,
    updateMutation,
  };
}
