import {
  BadRequestException,
  ConflictException,
  NotFoundException,
} from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { AiReview } from './entities/ai-review.entity';
import { AiConversation, AiMessage } from './entities';
import type { AiReviewSectionType } from './entities/ai-review.entity';
import type {
  AiChatServiceContext,
  AiSseEmitter,
} from './ai-chat.types';
import { DEFAULT_TITLE, reviewSectionType } from './ai-chat.types';
import type { AuthenticatedUser } from '../authorization';
import type {
  ImportStageRequest,
  ImportStepKey,
  PreflightReport,
} from '../imports/imports.types';
import {
  isExcelAttachment,
  parseConfirmedMapping,
  parseNestedSettings,
} from './ai-chat.import-confirm';
import { compactImportWizard } from './ai-chat.tool-actions';

export async function resolveFormConversationId(
  context: AiChatServiceContext,
  userId: number,
  formId: string,
): Promise<number> {
  const form = await context.formService.findOwnedPending(formId, userId);
  return form.conversationId;
}

export async function resolveReviewConversationId(
  context: AiChatServiceContext,
  userId: number,
  reviewId: string,
): Promise<number> {
  const review = await context.reviewService.findOwnedPending(reviewId, userId);
  return review.conversationId;
}

export async function resolvePreflightConversationId(
  context: AiChatServiceContext,
  userId: number,
  messageId: number,
): Promise<number> {
  const message = await context.messages.findOne({
    where: { id: messageId },
    relations: { conversation: true },
  });
  if (!message) throw new NotFoundException('消息不存在');
  if (message.role !== 'assistant') {
    throw new BadRequestException('该消息不是助手消息，无法确认导入预检');
  }
  const conversation = await context.requireOwnedConversation(userId, message.conversation.id);
  return conversation.id;
}

function readPreflightCard(
  metadata: Record<string, unknown> | null | undefined,
): PreflightReport {
  const card = metadata?.a2uiImportPreflight;
  if (!card || typeof card !== 'object' || Array.isArray(card)) {
    throw new BadRequestException('预检报告不存在或已失效');
  }
  if (!isPreflightReport(card)) {
    throw new BadRequestException('预检报告格式异常');
  }
  return card;
}

function isPreflightReport(value: object): value is PreflightReport {
  const record = value as Record<string, unknown>;
  return (
    typeof record.verdict === 'string' &&
    Array.isArray(record.stages) &&
    Array.isArray(record.blocks) &&
    Array.isArray(record.questions) &&
    Array.isArray(record.nextSteps) &&
    Array.isArray(record.errorSamples)
  );
}

async function loadReviewForConfirm(
  context: AiChatServiceContext,
  user: AuthenticatedUser,
  reviewId: string,
): Promise<AiReview> {
  const review = await context.reviewService.findOwned(reviewId, user.id);
  if (review.status === 'submitted') {
    throw new ConflictException('导入已全部确认，无需重复确认');
  }
  if (review.status === 'expired') {
    throw new ConflictException('导入预览已失效，请重新生成预览');
  }
  return review;
}

async function finalizeReview(
  context: AiChatServiceContext,
  updated: AiReview,
): Promise<Record<string, unknown>> {
  await context.markReviewSubmittedOnMessage(
    updated.assistantMessageId,
    updated.conversationId,
    updated,
  );
  return context.reviewService.serialize(updated);
}

async function logImportOp(
  context: AiChatServiceContext,
  user: AuthenticatedUser,
  action: string,
  detail: string,
): Promise<void> {
  await context.opLog?.log({
    userId: user.id,
    username: user.username,
    module: '批量导入',
    action,
    detail,
    targetType: 'ai_review',
    status: 'success',
  });
}

export async function resolveImportPreflight(
  context: AiChatServiceContext,
  user: AuthenticatedUser,
  messageId: number,
  dto: {
    clientRequestId: string;
    mapping?: Record<string, unknown>;
    settings?: Record<string, unknown>;
  },
  signal: AbortSignal,
  emit: AiSseEmitter,
  onReady: () => void,
): Promise<void> {
  context.throwIfAborted(signal);
  const message = await context.messages.findOne({ where: { id: messageId } });
  if (!message) throw new NotFoundException('消息不存在');
  if (message.role !== 'assistant') {
    throw new BadRequestException('该消息不是助手消息，无法确认导入预检');
  }
  const conversation = await context.requireOwnedConversation(user.id, message.conversationId);
  const preflight = readPreflightCard(message.metadata);

  await context.acquireConversation(conversation.id);
  try {
    context.throwIfAborted(signal);
    const existingWizard = message.metadata?.a2uiImportWizard;
    if (existingWizard && typeof existingWizard === 'object' && !Array.isArray(existingWizard)) {
      onReady();
      emit('ui.import_preflight', {
        messageId,
        preflight: { ...preflight, resolved: true },
      });
      emit('ui.import_wizard', { messageId, wizard: existingWizard });
      return;
    }

    const attachmentId = preflight.attachmentId;
    const headerRow = preflight.headerRow ?? 1;
    if (!Number.isInteger(attachmentId) || (attachmentId as number) <= 0) {
      throw new BadRequestException('预检报告缺少附件信息，请重新预检');
    }
    const [attachment] = await context.attachmentService.requireReadyOwned(user.id, [
      attachmentId as number,
    ]);
    if (!isExcelAttachment(attachment)) {
      throw new BadRequestException('附件不是 Excel 文件，无法生成导入向导');
    }
    const stages: ImportStageRequest[] = preflight.stages.map((stage) => ({
      stepKey: stage.stepKey,
      sheet: stage.sheetNames[0],
      headerRow,
    }));
    if (stages.some((stage) => !stage.sheet || !String(stage.sheet).trim())) {
      throw new BadRequestException('预检报告缺少工作表信息，请重新预检');
    }
    const allowedHeadersByStep: Partial<Record<ImportStepKey, string[]>> = {};
    for (const stage of preflight.stages) {
      allowedHeadersByStep[stage.stepKey] = stage.headers ?? [];
    }
    const mapping = parseConfirmedMapping(dto.mapping, { allowedHeadersByStep });
    const settings = parseNestedSettings(dto.settings);
    if (!context.importsService) throw new BadRequestException('导入向导服务未配置');

    const buffer = await context.attachmentService.readStoredBuffer(attachment);
    const detail = await context.importsService.createRun(
      {
        id: user.id,
        permissions: [...user.permissions],
        isSuperAdmin: user.isSuperAdmin,
      },
      'ai',
      {
        originalName: attachment.originalName,
        mimeType: attachment.mimeType,
        size: attachment.size,
        buffer,
      },
      conversation.id,
      stages,
      mapping,
      settings,
    );
    const wizard = compactImportWizard(detail);
    message.metadata = {
      ...message.metadata,
      a2uiImportPreflight: { ...preflight, resolved: true, runId: detail.id },
      a2uiImportWizard: wizard,
    };
    await context.messages.save(message);

    onReady();
    emit('ui.import_preflight', {
      messageId,
      preflight: { ...preflight, resolved: true, runId: detail.id },
    });
    emit('ui.import_wizard', { messageId, wizard });
  } finally {
    context.activeConversations.delete(conversation.id);
  }
}

export function assertReviewImportPermissions(
  context: AiChatServiceContext,
  user: AuthenticatedUser,
  review: AiReview,
  sectionKey?: string,
  sectionType?: AiReviewSectionType,
): void {
  const sectionPermission: Record<AiReviewSectionType, string> = {
    students: 'student:create',
    rooms: 'room:create',
    transfers: 'occupancy:transfer',
    checkins: 'occupancy:checkin',
  };
  const ability = context.abilityFactory.createForUser(user);
  const sections = context.reviewService.parseSections(review.sectionsJson);
  const types = new Set<AiReviewSectionType>();
  if (sectionType) {
    types.add(sectionType);
  } else if (sectionKey) {
    const section = sections.find((item) => item.key === sectionKey);
    if (!section) throw new NotFoundException(`分表不存在: ${sectionKey}`);
    types.add(reviewSectionType(section));
  } else {
    for (const section of sections) types.add(reviewSectionType(section));
  }
  for (const type of types) {
    context.authorization.assertPermission(ability, sectionPermission[type]);
  }
}

export async function submitForm(
  context: AiChatServiceContext,
  user: AuthenticatedUser,
  formId: string,
  dto: { values: Record<string, unknown>; clientRequestId: string; reasoningEffort?: string | null },
  signal: AbortSignal,
  emit: AiSseEmitter,
  onReady: () => void,
): Promise<void> {
  const form = await context.formService.findOwnedPending(formId, user.id);
  const conversation = await context.requireOwnedConversation(user.id, form.conversationId);
  const values = context.formService.validateValues(form, dto.values);
  const effectiveSkillKey = conversation.lockedSkillKey ?? null;
  context.assertSkillAvailable(user, effectiveSkillKey);

  await context.acquireConversation(conversation.id);
  try {
    const summary = `已提交表单「${form.title}」`;
    const saved = await context.dataSource.transaction(async (manager) =>
      persistExchange(
        context,
        manager,
        conversation,
        user.id,
        summary,
        dto.clientRequestId,
        effectiveSkillKey,
        { a2uiSubmit: { formId: form.id, formTitle: form.title, values } },
        undefined,
        conversation.title === DEFAULT_TITLE ? form.title.slice(0, 30) : undefined,
      ),
    );

    await context.formService.markSubmitted(form, values);
    await context.markFormSubmittedOnMessage(form.assistantMessageId, conversation.id);

    await runGenerationAndRelease(context, {
      user,
      conversation,
      userMessage: saved.userMessage,
      assistant: saved.assistantMessage,
      clientRequestId: dto.clientRequestId,
      effectiveSkillKey,
      focusContent: summary,
      reasoningEffort: dto.reasoningEffort ?? null,
      signal,
      emit,
      onReady,
    }, conversation.id);
  } finally {
    context.activeConversations.delete(conversation.id);
  }
}

export async function submitReview(
  context: AiChatServiceContext,
  user: AuthenticatedUser,
  reviewId: string,
  dto: { clientRequestId: string; reasoningEffort?: string | null },
  signal: AbortSignal,
  emit: AiSseEmitter,
  onReady: () => void,
): Promise<void> {
  const review = await context.reviewService.findOwnedPending(reviewId, user.id);
  const conversation = await context.requireOwnedConversation(user.id, review.conversationId);
  const effectiveSkillKey = conversation.lockedSkillKey ?? null;
  context.assertSkillAvailable(user, effectiveSkillKey);
  assertReviewImportPermissions(context, user, review);

  await context.acquireConversation(conversation.id);
  try {
    const { review: updatedReview, result } = await context.reviewService.submitAll(
      review.id,
      user.id,
    );
    const summary = `已确认导入「${review.title}」：${result.message}`;
    await context.opLog?.log({
      userId: user.id,
      username: user.username,
      module: '批量导入',
      action: '确认导入全部',
      detail: `「${review.title}」${result.message}`,
      targetType: 'ai_review',
      status: 'success',
    });
    const saved = await context.dataSource.transaction(async (manager) => {
      const exchange = await persistExchange(
        context,
        manager,
        conversation,
        user.id,
        summary,
        dto.clientRequestId,
        effectiveSkillKey,
        {
          a2uiReviewSubmit: {
            reviewId: review.id,
            reviewTitle: review.title,
            resultMessage: result.message,
          },
        },
        undefined,
        conversation.title === DEFAULT_TITLE ? review.title.slice(0, 30) : undefined,
      );
      return { ...exchange, result };
    });

    const serialized = context.reviewService.serialize(updatedReview);
    onReady();
    emit('ui.review', {
      messageId: updatedReview.assistantMessageId,
      review: serialized,
    });
    await context.markReviewSubmittedOnMessage(
      updatedReview.assistantMessageId,
      conversation.id,
      updatedReview,
    );

    await runGenerationAndRelease(context, {
      user,
      conversation,
      userMessage: saved.userMessage,
      assistant: saved.assistantMessage,
      clientRequestId: dto.clientRequestId,
      effectiveSkillKey,
      focusContent: saved.result.message,
      reasoningEffort: dto.reasoningEffort ?? null,
      signal,
      emit,
      onReady,
    }, conversation.id);
  } finally {
    context.activeConversations.delete(conversation.id);
  }
}

export async function confirmReviewStep(
  context: AiChatServiceContext,
  user: AuthenticatedUser,
  reviewId: string,
  sectionKey: string,
): Promise<Record<string, unknown>> {
  const review = await loadReviewForConfirm(context, user, reviewId);
  assertReviewImportPermissions(context, user, review, sectionKey);
  const { review: updated, message } = await context.reviewService.submitSection(
    review.id,
    user.id,
    sectionKey,
  );
  await logImportOp(
    context,
    user,
    '确认导入分表',
    `「${review.title}」分表「${sectionKey}」：${message}`,
  );
  return finalizeReview(context, updated);
}

export async function confirmReviewGroup(
  context: AiChatServiceContext,
  user: AuthenticatedUser,
  reviewId: string,
  type: AiReviewSectionType,
): Promise<Record<string, unknown>> {
  if (type !== 'students' && type !== 'rooms' && type !== 'transfers' && type !== 'checkins') {
    throw new BadRequestException(`业务类型不支持: ${String(type)}`);
  }
  const review = await loadReviewForConfirm(context, user, reviewId);
  assertReviewImportPermissions(context, user, review, undefined, type);
  const { review: updated } = await context.reviewService.submitGroup(review.id, user.id, type);
  const sectionTitles = context.reviewService
    .parseSections(updated.sectionsJson)
    .filter((section) => section.type === type)
    .map((section) => section.title)
    .join('、');
  await logImportOp(
    context,
    user,
    '确认导入分组',
    `「${review.title}」分组「${type}」：${sectionTitles}`,
  );
  return finalizeReview(context, updated);
}

export function a2uiSubmitInfo(
  metadata: Record<string, unknown> | null,
): { title: string; values: Record<string, unknown> } | null {
  const submit = metadata?.a2uiSubmit;
  if (!submit || typeof submit !== 'object' || Array.isArray(submit)) return null;
  const record = submit as Record<string, unknown>;
  const title = typeof record.formTitle === 'string' ? record.formTitle : '表单';
  const values =
    record.values && typeof record.values === 'object' && !Array.isArray(record.values)
      ? (record.values as Record<string, unknown>)
      : {};
  return { title, values };
}

export function buildFormSubmitModelContent(submit: {
  title: string;
  values: Record<string, unknown>;
}): string {
  let json: string;
  try {
    json = JSON.stringify(submit.values);
  } catch {
    json = '[无法序列化]';
  }
  return `【表单提交：${submit.title}】\n提交值（JSON）：${json.slice(0, 32 * 1024)}\n用户已在表单中确认，你可以执行允许的写操作工具。`;
}

export async function markFormSubmittedOnMessage(
  context: AiChatServiceContext,
  assistantMessageId: number,
  conversationId: number,
): Promise<void> {
  const assistant = await context.messages.findOne({
    where: { id: assistantMessageId, conversationId },
  });
  const a2ui = assistant?.metadata?.a2uiForm;
  if (assistant && a2ui && typeof a2ui === 'object' && !Array.isArray(a2ui)) {
    assistant.metadata = {
      ...assistant.metadata,
      a2uiForm: { ...(a2ui as Record<string, unknown>), status: 'submitted' },
    };
    await context.messages.save(assistant);
  }
}

export function a2uiReviewSubmitInfo(
  metadata: Record<string, unknown> | null,
): { reviewId: string; reviewTitle: string; resultMessage: string } | null {
  const submit = metadata?.a2uiReviewSubmit;
  if (!submit || typeof submit !== 'object' || Array.isArray(submit)) return null;
  const record = submit as Record<string, unknown>;
  if (typeof record.reviewId !== 'string') return null;
  return {
    reviewId: record.reviewId,
    reviewTitle: typeof record.reviewTitle === 'string' ? record.reviewTitle : '批量导入',
    resultMessage: typeof record.resultMessage === 'string' ? record.resultMessage : '导入已完成',
  };
}

export function buildReviewSubmitModelContent(submit: {
  reviewId: string;
  reviewTitle: string;
  resultMessage: string;
}): string {
  return `【批量导入已确认：${submit.reviewTitle}】\n${submit.resultMessage}\n数据已由系统入库，不要再次调用写入工具，直接向用户汇报导入结果即可。`;
}

export async function markReviewSubmittedOnMessage(
  context: AiChatServiceContext,
  assistantMessageId: number,
  conversationId: number,
  review?: AiReview,
): Promise<void> {
  const assistant = await context.messages.findOne({
    where: { id: assistantMessageId, conversationId },
  });
  const a2ui = assistant?.metadata?.a2uiReview;
  if (assistant && a2ui && typeof a2ui === 'object' && !Array.isArray(a2ui)) {
    assistant.metadata = {
      ...assistant.metadata,
      a2uiReview: review
        ? context.reviewService.serialize(review)
        : { ...(a2ui as Record<string, unknown>), status: 'submitted' },
    };
    await context.messages.save(assistant);
  }
}

export async function persistExchange(
  context: AiChatServiceContext,
  manager: EntityManager,
  conversation: AiConversation,
  userId: number,
  userContent: string,
  clientRequestId: string | undefined,
  skillKey: string | null,
  metadata?: Record<string, unknown>,
  attachments?: any[],
  titleUpdate?: string,
): Promise<{ userMessage: AiMessage; assistantMessage: AiMessage }> {
  const userMessage = await manager.save(
    AiMessage,
    manager.create(AiMessage, {
      conversationId: conversation.id,
      role: 'user',
      content: userContent,
      reasoningContent: null,
      status: 'completed',
      errorCode: null,
      replyToMessageId: null,
      metadata: { clientRequestId, skillKey, ...metadata },
      attachments,
    }),
  );
  const assistantMessage = await manager.save(
    AiMessage,
    manager.create(AiMessage, {
      conversationId: conversation.id,
      role: 'assistant',
      content: '',
      reasoningContent: null,
      status: 'pending',
      errorCode: null,
      replyToMessageId: userMessage.id,
      metadata: { clientRequestId, skillKey },
    }),
  );
  await manager.update(
    AiConversation,
    { id: conversation.id, userId },
    {
      lastMessageAt: new Date(),
      ...(titleUpdate ? { title: titleUpdate } : {}),
    },
  );
  return { userMessage, assistantMessage };
}

export async function runGenerationAndRelease(
  context: AiChatServiceContext,
  input: {
    user: AuthenticatedUser;
    conversation: AiConversation;
    userMessage: AiMessage;
    assistant: AiMessage;
    clientRequestId: string;
    effectiveSkillKey: string | null;
    focusContent: string | import('./ai-chat.types').ModelContentPart[];
    reasoningEffort?: string | null;
    signal: AbortSignal;
    emit: AiSseEmitter;
    onReady: () => void;
  },
  conversationId: number,
): Promise<void> {
  try {
    await context.executeGeneration(input);
  } finally {
    context.activeConversations.delete(conversationId);
  }
}
