import {
  AbstractChatProvider,
  XRequest,
  type TransformMessage,
  type XRequestOptions,
} from '@ant-design/x-sdk';
import { usePermissionStore } from '../../store/permission/permissionStore';
import { useUserStore } from '../../store/user/userStore';
import type {
  AiAttachment,
  AiChatInput,
  AiChatMessage,
  AiChartSchema,
  AiFormSchema,
  AiImportPreflight,
  AiModelRetryInfo,
  AiReviewSchema,
  AiSseChunk,
  AiToolRun,
} from './types';

interface AiSsePayload {
  messageId?: number;
  userMessageId?: number;
  assistantMessageId?: number;
  delta?: string;
  content?: string;
  reasoningContent?: string | null;
  toolCallId?: string;
  toolName?: string;
  skillKey?: string | null;
  status?: string;
  summary?: string | null;
  durationMs?: number | null;
  attachment?: AiAttachment;
  form?: AiFormSchema;
  review?: AiReviewSchema;
  chart?: AiChartSchema;
  preflight?: AiImportPreflight;
  wizard?: unknown;
  retry?: AiModelRetryInfo;
  message?:
    | string
    | {
        id?: number;
        content?: string;
        reasoningContent?: string | null;
        status?: string;
        toolRuns?: AiToolRun[];
        attachments?: AiAttachment[];
        replyToMessageId?: number | null;
        metadata?: Record<string, unknown> | null;
      };
  error?: string;
}

function emptyAssistant(): AiChatMessage {
  return {
    role: 'assistant',
    content: '',
    reasoningContent: '',
    toolRuns: [],
    attachments: [],
    forms: [],
  };
}

function mergeForms(
  current: AiFormSchema[] | undefined,
  incoming: AiFormSchema | AiFormSchema[] | undefined,
): AiFormSchema[] {
  const items = Array.isArray(incoming) ? incoming : incoming ? [incoming] : [];
  if (!items.length) return current ?? [];
  const next = [...(current ?? [])];
  for (const item of items) {
    if (item && typeof item === 'object' && !next.some((existing) => existing.id === item.id)) {
      next.push(item);
    }
  }
  return next;
}

function mergeById<T extends { id: string }>(
  current: T[] | undefined,
  incoming: T | T[] | undefined,
): T[] {
  const items = Array.isArray(incoming) ? incoming : incoming ? [incoming] : [];
  if (!items.length) return current ?? [];
  const next = [...(current ?? [])];
  for (const item of items) {
    if (!item || typeof item !== 'object') continue;
    const index = next.findIndex((existing) => existing.id === item.id);
    if (index === -1) {
      next.push(item);
    } else {
      next[index] = item;
    }
  }
  return next;
}

export function parseSsePayload(chunk?: AiSseChunk): {
  event: string;
  payload: AiSsePayload;
} {
  if (!chunk) return { event: '', payload: {} };
  const event = chunk.event?.trim() || 'message';
  if (!chunk.data || chunk.data === '[DONE]') return { event, payload: {} };
  try {
    const parsed: unknown = JSON.parse(chunk.data);
    return {
      event,
      payload: parsed && typeof parsed === 'object' ? (parsed as AiSsePayload) : {},
    };
  } catch {
    return { event, payload: { delta: chunk.data } };
  }
}

function upsertToolRun(
  toolRuns: AiToolRun[],
  payload: AiSsePayload,
  fallbackStatus: AiToolRun['status'],
): AiToolRun[] {
  const toolCallId = payload.toolCallId || `${payload.toolName || 'tool'}-${toolRuns.length}`;
  const next: AiToolRun = {
    toolCallId,
    toolName: payload.toolName || '查询工具',
    skillKey: payload.skillKey,
    status: (payload.status as AiToolRun['status']) || fallbackStatus,
    summary: payload.summary,
    resultSummary: fallbackStatus === 'running' ? undefined : payload.summary,
    argumentsSummary: fallbackStatus === 'running' ? payload.summary : undefined,
    durationMs: payload.durationMs,
  };
  const index = toolRuns.findIndex((item) => item.toolCallId === toolCallId);
  if (index === -1) return [...toolRuns, next];
  return toolRuns.map((item, itemIndex) => (itemIndex === index ? { ...item, ...next } : item));
}

function normalizeToolRuns(toolRuns: AiToolRun[] | undefined, fallback: AiToolRun[]): AiToolRun[] {
  if (!toolRuns) return fallback;
  return toolRuns.map((tool) => ({
    ...tool,
    status: tool.status === 'error' ? 'failed' : tool.status,
    summary: tool.resultSummary ?? tool.argumentsSummary ?? tool.summary,
  }));
}

function applyMessagePayload(
  message: AiChatMessage,
  nested: AiSsePayload['message'],
  payload: AiSsePayload,
): void {
  if (typeof nested !== 'object' || nested === null) return;
  message.forms = mergeForms(
    message.forms,
    (nested.metadata?.a2uiForm as AiFormSchema | undefined) ?? payload.form,
  );
  message.reviews = mergeById<AiReviewSchema>(
    message.reviews,
    (nested.metadata?.a2uiReview as AiReviewSchema | undefined) ?? payload.review,
  );
  message.charts = mergeById<AiChartSchema>(
    message.charts,
    (nested.metadata?.a2uiChart as AiChartSchema | AiChartSchema[] | undefined) ?? payload.chart,
  );
  message.replyToMessageId = nested.replyToMessageId ?? message.replyToMessageId;
  message.metadata = nested.metadata ?? message.metadata;
}

export function reduceAiSseMessage(
  originMessage: AiChatMessage | undefined,
  chunk?: AiSseChunk,
): AiChatMessage {
  const message = originMessage ? { ...originMessage } : emptyAssistant();
  const { event, payload } = parseSsePayload(chunk);

  if (event === 'message.created') {
    const nested = typeof payload.message === 'object' ? payload.message : undefined;
    message.id = nested?.id ?? payload.assistantMessageId ?? payload.messageId ?? message.id;
    message.content = nested?.content ?? message.content;
    message.reasoningContent = nested?.reasoningContent ?? message.reasoningContent;
    message.toolRuns = normalizeToolRuns(nested?.toolRuns, message.toolRuns);
    message.attachments = nested?.attachments ?? message.attachments;
    applyMessagePayload(message, nested, payload);
  } else if (event === 'reasoning.delta') {
    message.retrying = null;
    message.reasoningContent += payload.delta ?? payload.reasoningContent ?? '';
  } else if (event === 'content.delta') {
    message.retrying = null;
    message.content += payload.delta ?? payload.content ?? '';
  } else if (event === 'model.retrying' && payload.retry) {
    message.retrying = payload.retry;
  } else if (event === 'ui.form' && payload.form) {
    message.forms = mergeForms(message.forms, payload.form);
  } else if (event === 'ui.review' && payload.review) {
    message.reviews = mergeById<AiReviewSchema>(message.reviews, payload.review);
  } else if (event === 'ui.chart' && payload.chart) {
    message.charts = mergeById<AiChartSchema>(message.charts, payload.chart);
  } else if (event === 'ui.import_preflight' && payload.preflight) {
    message.metadata = { ...message.metadata, a2uiImportPreflight: payload.preflight };
  } else if (event === 'ui.import_wizard' && payload.wizard) {
    message.metadata = { ...message.metadata, a2uiImportWizard: payload.wizard };
  } else if (event === 'tool.started') {
    message.toolRuns = upsertToolRun(message.toolRuns, payload, 'running');
  } else if (event === 'tool.completed') {
    message.toolRuns = upsertToolRun(message.toolRuns, payload, 'success');
  } else if (event === 'tool.failed') {
    message.toolRuns = upsertToolRun(message.toolRuns, payload, 'failed');
  } else if (event === 'attachment.processed' && payload.attachment) {
    if (!message.attachments.some((item) => item.id === payload.attachment?.id)) {
      message.attachments = [...message.attachments, payload.attachment];
    }
  } else if (event === 'message.completed') {
    const nested = typeof payload.message === 'object' ? payload.message : undefined;
    message.id = nested?.id ?? payload.messageId ?? message.id;
    message.content = nested?.content ?? payload.content ?? message.content;
    message.reasoningContent =
      nested?.reasoningContent ?? payload.reasoningContent ?? message.reasoningContent;
    message.toolRuns = normalizeToolRuns(nested?.toolRuns, message.toolRuns);
    message.attachments = nested?.attachments ?? message.attachments;
    applyMessagePayload(message, nested, payload);
    message.retrying = null;
  } else if (event === 'message.cancelled') {
    message.id = payload.messageId ?? message.id;
    message.cancelled = true;
    message.retrying = null;
  } else if (event === 'error') {
    message.retrying = null;
    message.error =
      (typeof payload.message === 'string' ? payload.message : undefined) ||
      payload.error ||
      'AI 回答生成失败';
  }
  return message;
}

export async function authenticatedFetch(
  input: RequestInfo | URL,
  init?: RequestInit,
): Promise<Response> {
  const headers = new Headers(init?.headers);
  const token = useUserStore.getState().token;
  if (token) headers.set('Authorization', `Bearer ${token}`);
  headers.set('Accept', 'text/event-stream');
  let requestInput = input;
  let requestInit = init;
  if (typeof init?.body === 'string') {
    try {
      const body = JSON.parse(init.body) as AiChatInput;
      if (body.regenerateMessageId) {
        requestInput = `${String(input).replace(/\/stream$/, '')}/messages/${body.regenerateMessageId}/regenerate/stream`;
        requestInit = {
          ...init,
          body: JSON.stringify({
            clientRequestId: body.clientRequestId,
            reasoningEffort: body.reasoningEffort,
          }),
        };
      } else if (body.editMessageId) {
        requestInput = `${String(input).replace(/\/stream$/, '')}/messages/${body.editMessageId}/edit/stream`;
        requestInit = {
          ...init,
          body: JSON.stringify({
            content: body.message,
            clientRequestId: body.clientRequestId,
            reasoningEffort: body.reasoningEffort,
          }),
        };
      } else if (body.formSubmission) {
        requestInput = `${String(input).replace(/\/conversations\/\d+\/stream$/, '')}/forms/${body.formSubmission.formId}/submit/stream`;
        requestInit = {
          ...init,
          body: JSON.stringify({
            clientRequestId: body.clientRequestId,
            values: body.formSubmission.values,
            reasoningEffort: body.reasoningEffort,
          }),
        };
      } else if (body.reviewSubmission) {
        requestInput = `${String(input).replace(/\/conversations\/\d+\/stream$/, '')}/reviews/${body.reviewSubmission.reviewId}/submit/stream`;
        requestInit = {
          ...init,
          body: JSON.stringify({
            clientRequestId: body.clientRequestId,
            reasoningEffort: body.reasoningEffort,
          }),
        };
      } else {
        const {
          localAttachments: _localAttachments,
          reloadMessage: _reloadMessage,
          regenerateMessageId: _regenerateMessageId,
          editMessageId: _editMessageId,
          formSubmission: _formSubmission,
          reviewSubmission: _reviewSubmission,
          ...payload
        } = body;
        requestInit = { ...init, body: JSON.stringify(payload) };
      }
    } catch {
      requestInit = init;
    }
  }
  const response = await fetch(requestInput, { ...requestInit, headers });
  if (response.status === 401) {
    useUserStore.getState().logout();
    usePermissionStore.getState().clearPermissions();
    window.location.href = '/login';
  }
  return response;
}

export class GongxueAiChatProvider extends AbstractChatProvider<
  AiChatMessage,
  AiChatInput,
  AiSseChunk
> {
  /** Routes events that target another (already streamed) message. */
  onExternalReview?: (messageId: number, review: AiReviewSchema) => void;

  constructor(url: string, onSettled?: (result?: { ok: boolean; aborted?: boolean }) => void) {
    super({
      request: XRequest<AiChatInput, AiSseChunk, AiChatMessage>(url, {
        manual: true,
        fetch: authenticatedFetch,
        timeout: 15_000,
        streamTimeout: 1_800_000,
        callbacks: {
          onUpdate: () => undefined,
          onSuccess: () => onSettled?.({ ok: true }),
          onError: (error) =>
            onSettled?.({
              ok: false,
              aborted: error?.name === 'AbortError',
            }),
        },
      }),
    });
  }

  transformParams(
    requestParams: Partial<AiChatInput>,
    options: XRequestOptions<AiChatInput, AiSseChunk, AiChatMessage>,
  ): AiChatInput {
    return {
      ...options.params,
      message: requestParams.message?.trim() || '',
      attachmentIds: requestParams.attachmentIds ?? [],
      skillKey: requestParams.skillKey ?? null,
      clientRequestId: requestParams.clientRequestId || crypto.randomUUID(),
      reasoningEffort: requestParams.reasoningEffort,
      localAttachments: requestParams.localAttachments,
      formSubmission: requestParams.formSubmission,
      reviewSubmission: requestParams.reviewSubmission,
      regenerateMessageId: requestParams.regenerateMessageId,
      editMessageId: requestParams.editMessageId,
      reloadMessage: requestParams.reloadMessage,
    };
  }

  transformLocalMessage(requestParams: Partial<AiChatInput>): AiChatMessage | AiChatMessage[] {
    if (requestParams.editMessageId) {
      // 编辑消息不需要新增用户气泡，store 里已原位更新原消息。
      return [];
    }
    if (requestParams.formSubmission) {
      return {
        role: 'user',
        content: '',
        reasoningContent: '',
        toolRuns: [],
        attachments: requestParams.localAttachments ?? [],
        metadata: {
          a2uiSubmit: {
            formTitle: requestParams.formSubmission.formTitle,
          },
        },
      };
    }
    if (requestParams.reviewSubmission) {
      return {
        role: 'user',
        content: '',
        reasoningContent: '',
        toolRuns: [],
        attachments: requestParams.localAttachments ?? [],
        metadata: {
          a2uiReviewSubmit: {
            reviewTitle: requestParams.reviewSubmission.reviewTitle,
          },
        },
      };
    }
    return {
      role: 'user',
      content: requestParams.message?.trim() || '',
      reasoningContent: '',
      toolRuns: [],
      attachments: requestParams.localAttachments ?? [],
    };
  }

  transformMessage(info: TransformMessage<AiChatMessage, AiSseChunk>): AiChatMessage {
    const { event, payload } = parseSsePayload(info.chunk);
    if (
      event === 'ui.review' &&
      payload.review &&
      typeof payload.messageId === 'number' &&
      info.originMessage?.id !== payload.messageId
    ) {
      // The submitted review belongs to the original assistant message;
      // do not merge it into the message currently being streamed.
      this.onExternalReview?.(payload.messageId, payload.review);
      return info.originMessage ?? emptyAssistant();
    }
    return reduceAiSseMessage(info.originMessage, info.chunk);
  }
}
