import { AiReview } from './entities/ai-review.entity';
import {
  IMPORT_STEP_KEYS,
  type ImportStageRequest,
  type ImportStepKey,
  type PreflightReport,
} from '../imports/imports.types';
import { permittedStepKeys } from '../imports/imports.access';
import type { AiChatServiceContext, AiSseEmitter, ModelToolCall } from './ai-chat.types';
import type { AgentToolContext } from './ai-chat.tools';
import { finishToolRun, startToolRun } from './ai-chat.tools';
import {
  isExcelAttachment,
  parseConfirmedMapping,
  parseConfirmedSettings,
} from './ai-chat.import-confirm';

function parseAttachmentArgs(
  parsedArgs: unknown,
): { parsedRecord: Record<string, unknown>; attachmentId: number } {
  const parsedRecord =
    parsedArgs && typeof parsedArgs === 'object' && !Array.isArray(parsedArgs)
      ? (parsedArgs as Record<string, unknown>)
      : {};
  if (
    typeof parsedRecord.attachmentId !== 'number' ||
    !Number.isInteger(parsedRecord.attachmentId) ||
    parsedRecord.attachmentId <= 0
  ) {
    throw new Error('缺少附件 attachmentId');
  }
  return { parsedRecord, attachmentId: parsedRecord.attachmentId };
}

async function beginImportToolRun(
  context: AiChatServiceContext,
  messageId: number,
  call: ModelToolCall,
  emit: AiSseEmitter,
  toolName: string,
) {
  return startToolRun(context, messageId, call, emit, {
    toolName,
    skillKey: null,
    argumentsData: null,
  });
}

export async function executePreflightImport(
  context: AiChatServiceContext,
  messageId: number,
  call: ModelToolCall,
  agentContext: AgentToolContext,
  emit: AiSseEmitter,
): Promise<string> {
  const { run, parsedArgs, startedAt } = await beginImportToolRun(
    context,
    messageId,
    call,
    emit,
    'preflight_import',
  );

  try {
    const assistant = await context.messages.findOne({ where: { id: messageId } });
    if (!assistant) throw new Error('assistant message missing');
    const { parsedRecord, attachmentId } = parseAttachmentArgs(parsedArgs);
    const headerRow =
      parsedRecord.headerRow === undefined ? 1 : Number(parsedRecord.headerRow);
    if (!Number.isInteger(headerRow) || headerRow < 1 || headerRow > 1000) {
      throw new Error('headerRow 必须是 1-1000 之间的整数');
    }
    const [attachment] = await context.attachmentService.requireReadyOwned(agentContext.userId, [
      attachmentId as number,
    ]);
    if (!isExcelAttachment(attachment)) {
      throw new Error('附件不是 Excel 文件，无法预检导入');
    }
    if (!context.importsService) throw new Error('导入预检服务未配置');
    const buffer = await context.attachmentService.readStoredBuffer(attachment);
    const preflight: PreflightReport = await context.importsService.preflightFile({
      originalName: attachment.originalName,
      mimeType: attachment.mimeType,
      size: attachment.size,
      buffer,
    }, headerRow);
    const permittedSteps = permittedStepKeys({
      id: agentContext.userId,
      permissions: [...agentContext.permissions],
      isSuperAdmin: agentContext.isSuperAdmin,
    });
    const preflightCard: PreflightReport = {
      ...preflight,
      attachmentId: attachment.id,
      headerRow,
      permittedSteps,
      resolved: false,
      runId: null,
    };
    assistant.metadata = {
      ...assistant.metadata,
      a2uiImportPreflight: preflightCard,
    };
    await context.messages.save(assistant);

    await finishToolRun(context, run, call, startedAt, {
      status: 'success',
      summary: `已完成导入预检：${preflight.stages
        .map((stage) => `${stage.label} ${stage.total} 行`)
        .join('、') || '未识别到可导入阶段'}`,
    }, emit);
    emit('ui.import_preflight', { messageId, preflight: preflightCard });
    return preflightModelPayload(preflight, permittedSteps);
  } catch (error) {
    const summary =
      error instanceof Error ? error.message.slice(0, 100) : '导入预检失败';
    await finishToolRun(context, run, call, startedAt, {
      status: 'failed',
      summary,
      error: summary,
    }, emit);
    return JSON.stringify({ status: 'failed', error: run.resultSummary });
  }
}

export async function executeStartImportWizard(
  context: AiChatServiceContext,
  messageId: number,
  call: ModelToolCall,
  agentContext: AgentToolContext,
  emit: AiSseEmitter,
): Promise<string> {
  const { run, parsedArgs, startedAt } = await beginImportToolRun(
    context,
    messageId,
    call,
    emit,
    'start_import_wizard',
  );

  try {
    const assistant = await context.messages.findOne({ where: { id: messageId } });
    if (!assistant) throw new Error('assistant message missing');
    const { parsedRecord, attachmentId } = parseAttachmentArgs(parsedArgs);
    const [attachment] = await context.attachmentService.requireReadyOwned(agentContext.userId, [
      attachmentId as number,
    ]);
    if (!isExcelAttachment(attachment)) throw new Error('附件不是 Excel 文件，无法生成导入向导');
    const stages = Array.isArray(parsedRecord.stages)
      ? (parsedRecord.stages as ImportStageRequest[])
      : [];
    if (stages.length === 0) throw new Error('缺少 stages 参数');
    for (const stage of stages) {
      if (!IMPORT_STEP_KEYS.includes(stage.stepKey)) {
        throw new Error(`stages 包含未知业务类型：${String(stage.stepKey)}`);
      }
      if (!stage.sheet || !String(stage.sheet).trim()) {
        throw new Error(`stages 中「${stage.stepKey}」缺少工作表 sheet，请指定 Excel 中对应的 sheet 名`);
      }
      if (
        stage.headerRow !== undefined &&
        (!Number.isInteger(stage.headerRow) || stage.headerRow < 1 || stage.headerRow > 1000)
      ) {
        throw new Error(`stages 中「${stage.stepKey}」的 headerRow 必须是 1-1000 之间的整数`);
      }
    }
    const mapping = parseConfirmedMapping(parsedRecord.mapping);
    const settings = parseConfirmedSettings(parsedRecord);
    if (!context.importsService) throw new Error('导入向导服务未配置');
    const buffer = await context.attachmentService.readStoredBuffer(attachment);
    const detail = await context.importsService.createRun(
      {
        id: agentContext.userId,
        permissions: [...agentContext.permissions],
        isSuperAdmin: agentContext.isSuperAdmin,
      },
      'ai',
      {
        originalName: attachment.originalName,
        mimeType: attachment.mimeType,
        size: attachment.size,
        buffer,
      },
      assistant.conversationId,
      stages,
      mapping,
      settings,
    );
    const wizard = compactImportWizard(detail);
    const preflightMeta = assistant.metadata?.a2uiImportPreflight;
    assistant.metadata = {
      ...assistant.metadata,
      ...(preflightMeta && typeof preflightMeta === 'object' && !Array.isArray(preflightMeta)
        ? {
            a2uiImportPreflight: {
              ...(preflightMeta as Record<string, unknown>),
              resolved: true,
              runId: detail.id,
            },
          }
        : {}),
      a2uiImportWizard: wizard,
    };
    await context.messages.save(assistant);

    await finishToolRun(context, run, call, startedAt, {
      status: 'success',
      summary: `已生成导入向导：${detail.steps
        .filter((step) => step.status !== 'skipped')
        .map((step) => step.label)
        .join('、')}`,
    }, emit);
    if (preflightMeta && typeof preflightMeta === 'object' && !Array.isArray(preflightMeta)) {
      emit('ui.import_preflight', {
        messageId,
        preflight: {
          ...(preflightMeta as Record<string, unknown>),
          resolved: true,
          runId: detail.id,
        },
      });
    }
    emit('ui.import_wizard', { messageId, wizard });
    return JSON.stringify({
      status: 'success',
      runId: detail.id,
      steps: detail.steps
        .filter((step) => step.status !== 'skipped')
        .map((step) => ({ stepKey: step.stepKey, label: step.label, sheets: step.sheets })),
      permittedSteps: permittedStepKeys({
        id: agentContext.userId,
        permissions: [...agentContext.permissions],
        isSuperAdmin: agentContext.isSuperAdmin,
      }),
      message: '导入向导已生成，请提示用户打开向导，按阶段预览并确认后系统才会入库',
    });
  } catch (error) {
    const summary = error instanceof Error ? error.message.slice(0, 100) : '生成导入向导失败';
    await finishToolRun(context, run, call, startedAt, { status: 'failed', summary, error: summary }, emit);
    return JSON.stringify({ status: 'failed', error: run.resultSummary });
  }
}

function preflightModelPayload(
  report: PreflightReport,
  permittedSteps: ImportStepKey[],
): string {
  const guidance =
    '预检报告已以卡片展示：请引导用户在卡内确认列映射与策略并点击「生成导入向导」；' +
    '仅当用户在聊天文本中显式给出确认时才调用 start_import_wizard';
  const fullPayload = JSON.stringify({
    status: 'success',
    report,
    permittedSteps,
    message: guidance,
  });
  if (fullPayload.length <= 32 * 1024) return fullPayload;
  return JSON.stringify({
    status: 'success',
    truncated: true,
    report: {
      verdict: report.verdict,
      stages: report.stages.map((stage) => ({
        stepKey: stage.stepKey,
        label: stage.label,
        sheetNames: stage.sheetNames,
        total: stage.total,
        create: stage.create,
        update: stage.update,
        error: stage.error,
        skip: stage.skip,
        mapping: stage.mapping,
        missingRequired: stage.missingRequired,
      })),
      questions: report.questions,
      errorSamples: report.errorSamples.slice(0, 10),
      nextSteps: report.nextSteps,
    },
    permittedSteps,
    message: guidance,
  });
}

export function compactImportWizard(detail: any): {
  runId: string;
  fileName: string;
  sheets: Array<{
    name: string;
    suggestedStepKey: string | null;
    headers: string[];
    rowCount: number;
  }>;
  steps: Array<{ stepKey: string; label: string; sheets: string[]; status: string }>;
} {
  return {
    runId: detail.id,
    fileName: detail.fileName,
    sheets: detail.sheets.map((sheet: any) => ({
      name: sheet.name,
      suggestedStepKey: sheet.suggestedStepKey,
      headers: sheet.headers,
      rowCount: sheet.rowCount,
    })),
    steps: detail.steps.map((step: any) => ({
      stepKey: step.stepKey,
      label: step.label,
      sheets: step.sheets,
      status: step.status,
    })),
  };
}

export async function executeRenderForm(
  context: AiChatServiceContext,
  messageId: number,
  call: ModelToolCall,
  userId: number,
  emit: AiSseEmitter,
): Promise<string> {
  const { run, parsedArgs, startedAt } = await startToolRun(context, messageId, call, emit, {
    toolName: 'render_form',
    skillKey: null,
  });

  try {
    const assistant = await context.messages.findOne({ where: { id: messageId } });
    if (!assistant) throw new Error('assistant message missing');
    const form = await context.formService.createForm(
      { userId, conversationId: assistant.conversationId, assistantMessageId: messageId },
      parsedArgs,
    );
    assistant.metadata = {
      ...assistant.metadata,
      a2uiForm: context.formService.serialize(form),
    };
    await context.messages.save(assistant);

    await finishToolRun(context, run, call, startedAt, { status: 'success', summary: '已生成表单，等待用户填写' }, emit);
    emit('ui.form', {
      messageId,
      form: context.formService.serialize(form),
    });
    return JSON.stringify({
      status: 'success',
      formId: form.id,
      message: '表单已显示给用户，请提示用户填写并提交',
    });
  } catch {
    await finishToolRun(context, run, call, startedAt, { status: 'failed', summary: '表单参数无效', error: '表单参数无效' }, emit);
    return JSON.stringify({ status: 'failed', error: '表单参数无效' });
  }
}

export async function executeRenderReview(
  context: AiChatServiceContext,
  messageId: number,
  call: ModelToolCall,
  userId: number,
  emit: AiSseEmitter,
): Promise<string> {
  const { run, parsedArgs, startedAt } = await startToolRun(context, messageId, call, emit, {
    toolName: 'render_review',
    skillKey: null,
    argumentsData: null,
  });

  try {
    const existingReview = await context.reviewService.findPendingByAssistantMessage(messageId);
    if (existingReview) {
      const denial = `本回合已生成导入预览《${existingReview.title}》，请直接提示用户审阅并确认，不要再次调用 render_review；如需多个分表，应全部合并到同一张预览卡。`;
      await finishToolRun(context, run, call, startedAt, { status: 'failed', summary: denial, error: denial }, emit);
      return JSON.stringify({ status: 'failed', error: denial });
    }
    const assistant = await context.messages.findOne({ where: { id: messageId } });
    if (!assistant) throw new Error('assistant message missing');
    const parsedRecord =
      parsedArgs && typeof parsedArgs === 'object' && !Array.isArray(parsedArgs)
        ? (parsedArgs as Record<string, unknown>)
        : {};
    const attachmentId =
      typeof parsedRecord.attachmentId === 'number' ? parsedRecord.attachmentId : undefined;

    let review: AiReview;
    if (Number.isInteger(attachmentId) && (attachmentId as number) > 0) {
      const [attachment] = await context.attachmentService.requireReadyOwned(userId, [
        attachmentId as number,
      ]);
      if (
        !attachment.mimeType.includes('spreadsheetml') &&
        !attachment.mimeType.includes('excel') &&
        !attachment.mimeType.includes('csv')
      ) {
        throw new Error('附件不是 Excel 文件，无法生成导入预览');
      }
      if (!context.excelReader) throw new Error('Excel 解析器未配置');
      const buffer = await context.attachmentService.readStoredBuffer(attachment);
      const sheets = await context.excelReader.loadSheets(buffer);
      const sections = await context.reviewService.buildSectionsFromWorkbook(sheets, parsedArgs);
      review = await context.reviewService.createReview(
        { userId, conversationId: assistant.conversationId, assistantMessageId: messageId },
        { title: parsedRecord.title, summary: parsedRecord.summary ?? null, sections },
      );
    } else {
      review = await context.reviewService.createReview(
        { userId, conversationId: assistant.conversationId, assistantMessageId: messageId },
        parsedArgs,
      );
    }
    const expiredReviews = await context.reviewService.expirePreviousReviews(
      userId,
      assistant.conversationId,
      review.id,
    );
    await Promise.all(
      expiredReviews.map(async (expired) => {
        const oldAssistant = await context.messages.findOne({
          where: { id: expired.assistantMessageId, conversationId: assistant.conversationId },
        });
        const oldA2ui = oldAssistant?.metadata?.a2uiReview;
        if (oldAssistant && oldA2ui && typeof oldA2ui === 'object' && !Array.isArray(oldA2ui)) {
          oldAssistant.metadata = {
            ...oldAssistant.metadata,
            a2uiReview: context.reviewService.serialize(expired),
          };
          await context.messages.save(oldAssistant);
        }
        emit('ui.review', {
          messageId: expired.assistantMessageId,
          review: context.reviewService.serialize(expired),
        });
      }),
    );
    assistant.metadata = {
      ...assistant.metadata,
      a2uiReview: context.reviewService.serialize(review),
    };
    await context.messages.save(assistant);

    await finishToolRun(context, run, call, startedAt, { status: 'success', summary: '已生成导入预览，等待用户确认' }, emit);
    emit('ui.review', {
      messageId,
      review: context.reviewService.serialize(review),
    });
    return JSON.stringify({
      status: 'success',
      reviewId: review.id,
      message: '导入预览已显示给用户，请提示用户审阅并确认',
    });
  } catch (reason) {
    const errorMessage =
      reason instanceof Error && reason.message ? reason.message.slice(0, 120) : '导入预览参数无效';
    await finishToolRun(context, run, call, startedAt, { status: 'failed', summary: errorMessage, error: errorMessage }, emit);
    return JSON.stringify({ status: 'failed', error: errorMessage });
  }
}

export async function executeRenderChart(
  context: AiChatServiceContext,
  messageId: number,
  call: ModelToolCall,
  emit: AiSseEmitter,
): Promise<string> {
  const { run, parsedArgs, startedAt } = await startToolRun(context, messageId, call, emit, {
    toolName: 'render_chart',
    skillKey: null,
    argumentsData: null,
  });

  try {
    const assistant = await context.messages.findOne({ where: { id: messageId } });
    if (!assistant) throw new Error('assistant message missing');
    const chart = context.chartService.createChart(parsedArgs);
    const existingCharts = assistant.metadata?.a2uiChart;
    const charts = Array.isArray(existingCharts)
      ? [...existingCharts]
      : existingCharts
        ? [existingCharts]
        : [];
    charts.push(context.chartService.serialize(chart));
    assistant.metadata = {
      ...assistant.metadata,
      a2uiChart: charts,
    };
    await context.messages.save(assistant);

    await finishToolRun(context, run, call, startedAt, { status: 'success', summary: '已生成图表' }, emit);
    emit('ui.chart', {
      messageId,
      chart: context.chartService.serialize(chart),
    });
    return JSON.stringify({
      status: 'success',
      chartId: chart.id,
      message: '图表已显示给用户',
    });
  } catch {
    await finishToolRun(context, run, call, startedAt, { status: 'failed', summary: '图表参数无效', error: '图表参数无效' }, emit);
    return JSON.stringify({ status: 'failed', error: '图表参数无效' });
  }
}
