import { BadRequestException } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { AiConfigService } from '../ai-config/ai-config.service';
import { AgentToolExecutor } from '../agent-tools/agent-tool.executor';
import { AgentToolContextFactory } from '../agent-tools/agent-tool.types';
import type { AuthenticatedUser, AuthorizationService, CaslAbilityFactory } from '../authorization';
import { AiAttachmentService } from './ai-attachment.service';
import { ImportsService } from '../imports/imports.service';
import { AiChartService } from './ai-chart.service';
import { AiExcelReaderService } from './ai-excel-reader.service';
import { AiFormService } from './ai-form.service';
import { AiReviewService } from './ai-review.service';
import { AiModelStreamService } from './ai-model-stream.service';
import { OperationLogsService } from '../operation-logs/operation-logs.service';
import {
  AiConversation,
  AiMessage,
  AiReview,
  AiReviewSection,
  AiReviewSectionType,
  AiToolRun,
} from './entities';

export {
  DEFAULT_TITLE,
  MAX_GENERATED_CHARS,
  MAX_HISTORY_MESSAGES,
  MAX_SUMMARY_CHARS,
  MAX_TOOL_CALLS_PER_ROUND,
  MAX_TOOL_ROUNDS,
  A2UI_TOOL_SCHEMAS,
  SYSTEM_PROMPT,
} from './ai-chat.constants';

export interface PublicConversation {
  id: number;
  title: string;
  lockedSkillKey: string | null;
  createdAt: Date;
  updatedAt: Date;
  lastMessageAt: Date | null;
}

export interface GenerationInput {
  user: AuthenticatedUser;
  conversation: AiConversation;
  userMessage: AiMessage;
  assistant: AiMessage;
  clientRequestId: string;
  effectiveSkillKey: string | null;
  focusContent: string | ModelContentPart[];
  reasoningEffort?: string | null;
  signal: AbortSignal;
  emit: AiSseEmitter;
  onReady: () => void;
}

export function reviewSectionType(
  section: Pick<AiReviewSection, 'key' | 'type'>,
): AiReviewSectionType {
  if (
    section.type === 'students' ||
    section.type === 'rooms' ||
    section.type === 'transfers' ||
    section.type === 'checkins'
  ) {
    return section.type;
  }
  const type = section.key as AiReviewSectionType;
  if (type === 'students' || type === 'rooms' || type === 'transfers' || type === 'checkins') {
    return type;
  }
  for (const candidate of ['students', 'rooms', 'transfers', 'checkins'] as const) {
    if (section.key.startsWith(`${candidate}_`)) return candidate;
  }
  throw new BadRequestException(`分表标识无法解析业务类型: ${section.key}`);
}

/** 子模块访问 AiChatService 能力的共享上下文。 */
export interface AiChatServiceContext {
  readonly activeConversations: Set<number>;
  readonly conversations: Repository<AiConversation>;
  readonly messages: Repository<AiMessage>;
  readonly toolRuns: Repository<AiToolRun>;
  readonly dataSource: DataSource;
  readonly configService: AiConfigService;
  readonly toolExecutor: AgentToolExecutor;
  readonly modelStream: AiModelStreamService;
  readonly attachmentService: AiAttachmentService;
  readonly formService: AiFormService;
  readonly reviewService: AiReviewService;
  readonly chartService: AiChartService;
  readonly abilityFactory: CaslAbilityFactory;
  readonly authorization: AuthorizationService;
  readonly excelReader?: AiExcelReaderService;
  readonly importsService?: ImportsService;
  readonly opLog?: OperationLogsService;
  listSkills(user: AuthenticatedUser): ReturnType<AgentToolExecutor['listSkills']>;
  serializeMessage(message: AiMessage): Record<string, unknown>;
  redactText(value: string): string;
  summarize(value: unknown): string | null;
  safeStructured(value: unknown): unknown;
  parseToolArguments(value: string): unknown;
  safeToolName(name: string): string;
  throwIfAborted(signal: AbortSignal): void;
  errorCode(error: unknown): string;
  assertGeneratedLength(reasoning: string, content: string): void;
  a2uiSubmitInfo(metadata: Record<string, unknown> | null): {
    title: string;
    values: Record<string, unknown>;
  } | null;
  a2uiReviewSubmitInfo(metadata: Record<string, unknown> | null): {
    reviewId: string;
    reviewTitle: string;
    resultMessage: string;
  } | null;
  buildFormSubmitModelContent(submit: { title: string; values: Record<string, unknown> }): string;
  buildReviewSubmitModelContent(submit: {
    reviewId: string;
    reviewTitle: string;
    resultMessage: string;
  }): string;
  markFormSubmittedOnMessage(assistantMessageId: number, conversationId: number): Promise<void>;
  markReviewSubmittedOnMessage(
    assistantMessageId: number,
    conversationId: number,
    review?: AiReview,
  ): Promise<void>;
  assertReviewImportPermissions(
    user: AuthenticatedUser,
    review: AiReview,
    sectionKey?: string,
    sectionType?: AiReviewSectionType,
  ): void;
  buildContext(
    conversationId: number,
    focusUserMessageId: number,
    focusContent: string | ModelContentPart[],
    skillKey: string | null,
    supportsVision: boolean,
  ): Promise<ModelMessage[]>;
  buildUserContent(
    text: string,
    attachments: any[],
    supportsVision: boolean,
  ): Promise<string | ModelContentPart[]>;
  truncateText(value: string, max: number): string;
  metadataSkillKey(metadata: Record<string, unknown> | null): string | null;
  normalizeTitle(title?: string): string;
  titleFromMessage(message: string): string;
  assertSkillAvailable(user: AuthenticatedUser, skillKey?: string | null): void;
  requireOwnedConversation(userId: number, id: number): Promise<AiConversation>;
  acquireConversation(conversationId: number): Promise<void>;
  executeTool(
    messageId: number,
    call: ModelToolCall,
    context: ReturnType<typeof AgentToolContextFactory.fromAuthenticatedUser>,
    allowedSkillKey: string | null,
    allowWriteTools: boolean,
    reviewSubmitted: boolean,
    userId: number,
    emit: AiSseEmitter,
  ): Promise<string>;
  executeGeneration(input: GenerationInput): Promise<void>;
}

export type AiSseEventName =
  | 'message.created'
  | 'reasoning.delta'
  | 'content.delta'
  | 'model.retrying'
  | 'tool.started'
  | 'tool.completed'
  | 'tool.failed'
  | 'ui.form'
  | 'ui.review'
  | 'ui.chart'
  | 'ui.import_preflight'
  | 'ui.import_wizard'
  | 'attachment.processed'
  | 'message.completed'
  | 'message.cancelled'
  | 'error'
  | 'done';

export type AiSseEmitter = (event: AiSseEventName, data: Record<string, unknown>) => void;

export interface ModelToolCall {
  id: string;
  name: string;
  arguments: string;
}

export type ModelContentPart =
  | { type: 'text'; text: string }
  | { type: 'image_url'; image_url: { url: string } };

export type ModelMessage =
  | { role: 'system'; content: string }
  | { role: 'user'; content: string | ModelContentPart[] }
  | {
      role: 'assistant';
      content: string | null;
      tool_calls?: Array<{
        id: string;
        type: 'function';
        function: { name: string; arguments: string };
      }>;
    }
  | { role: 'tool'; tool_call_id: string; content: string };

export type ModelStreamEvent =
  | { type: 'reasoning'; delta: string }
  | { type: 'content'; delta: string }
  | {
      type: 'retrying';
      attempt: number;
      maxRetries: number;
      delayMs: number;
      reason: string;
    }
  | { type: 'complete'; toolCalls: ModelToolCall[] };
