import request from '@/utils/request';
import type { ApiResult } from '@/api';
import { API_BASE_URL, TOKEN_HEADER_NAME } from '@/config/setting';
import { getToken } from '@/utils/token-util';
import { getHostname, getTenantId } from '@/utils/domain';
import { getMerchantId } from '@/utils/merchant';

export interface AiModel {
  name?: string;
  id?: string;
  [k: string]: unknown;
}

export interface AiChatRequest {
  prompt: string;
}

function normalizeBaseURL(baseURL: string) {
  return baseURL.replace(/\/+$/, '');
}

// Keep consistent with `src/utils/request.ts` but usable from `fetch` for SSE.
function getRuntimeApiBaseURL(): string {
  try {
    const apiUrl = localStorage.getItem('ApiUrl');
    if (apiUrl) return normalizeBaseURL(apiUrl);
  } catch {
    // ignore
  }
  return normalizeBaseURL(API_BASE_URL || '');
}

function buildAuthHeaders(): Record<string, string> {
  const headers: Record<string, string> = {};
  const token = getToken();
  if (token) headers[TOKEN_HEADER_NAME] = token;

  // Mirror axios interceptor behavior; add fallbacks to make localhost dev usable.
  const tenantId =
    getTenantId() ?? (localStorage.getItem('TenantId') || undefined);
  if (tenantId) headers.TenantId = String(tenantId);

  const companyId = localStorage.getItem('CompanyId');
  if (companyId) headers.CompanyId = companyId;

  const merchantId = getMerchantId?.();
  if (merchantId) headers.MerchantId = String(merchantId);

  const domain = getHostname?.();
  if (domain) headers.Domain = domain;

  return headers;
}

function extractText(payload: unknown): string {
  if (payload == null) return '';
  if (typeof payload === 'string') return payload;
  if (typeof payload === 'number' || typeof payload === 'boolean') {
    return String(payload);
  }
  if (Array.isArray(payload)) return payload.map(extractText).join('');
  if (typeof payload === 'object') {
    const obj: any = payload;
    const candidates: unknown[] = [
      obj.text,
      obj.answer,
      obj.content,
      obj.data,
      obj.message?.content,
      obj.delta?.content,
      obj.choices?.[0]?.delta?.content,
      obj.choices?.[0]?.message?.content
    ];
    for (const c of candidates) {
      const t = extractText(c);
      if (t) return t;
    }
  }
  return '';
}

export function normalizeModels(payload: unknown): AiModel[] {
  if (!payload) return [];
  if (Array.isArray(payload)) {
    return payload.map((m) =>
      typeof m === 'string' ? ({ name: m } as AiModel) : (m as AiModel)
    );
  }
  if (typeof payload === 'object') {
    const obj: any = payload;
    if (Array.isArray(obj.models)) {
      return obj.models.map((m: any) =>
        typeof m === 'string' ? ({ name: m } as AiModel) : (m as AiModel)
      );
    }
  }
  return [];
}

/**
 * 后端 AI 模块：GET /api/ai/models
 * 前端 baseURL 一般已包含 `/api`，因此这里用相对路径 `/ai/models`。
 */
export async function aiListModels(): Promise<unknown> {
  const res = await request.get<ApiResult<unknown>>('/ai/models');
  if (res.data.code === 0) return res.data.data;
  return Promise.reject(new Error(res.data.message));
}

/**
 * 后端 AI 模块：POST /api/ai/chat
 */
export async function aiChat(body: AiChatRequest): Promise<string> {
  const res = await request.post<ApiResult<unknown>>('/ai/chat', body);
  if (res.data.code === 0) return extractText(res.data.data);
  return Promise.reject(new Error(res.data.message));
}

/**
 * 后端 AI 模块：POST /api/ai/chat/stream（SSE）
 * 用 fetch 以便读取 ReadableStream，并附带鉴权/租户头。
 */
export async function aiChatStream(
  body: AiChatRequest,
  opts: {
    signal?: AbortSignal;
    onDelta: (text: string) => void;
    onDone?: () => void;
  }
): Promise<void> {
  const baseURL = getRuntimeApiBaseURL();
  const url = `${baseURL}/ai/chat/stream`;

  const res = await fetch(url, {
    method: 'POST',
    headers: {
      Accept: 'text/event-stream',
      'Content-Type': 'application/json',
      ...buildAuthHeaders()
    },
    body: JSON.stringify(body),
    signal: opts.signal
  });

  if (!res.ok || !res.body) {
    const text = await res.text().catch(() => '');
    throw new Error(
      `aiChatStream failed: ${res.status} ${res.statusText}${
        text ? ` - ${text}` : ''
      }`
    );
  }

  const reader = res.body.getReader();
  const decoder = new TextDecoder('utf-8');
  let buffer = '';

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    // Some servers use CRLF; normalize to simplify parsing.
    buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');

    // SSE events are separated by blank lines.
    let idx = buffer.indexOf('\n\n');
    while (idx !== -1) {
      const rawEvent = buffer.slice(0, idx);
      buffer = buffer.slice(idx + 2);
      idx = buffer.indexOf('\n\n');

      const dataParts: string[] = [];
      for (const line of rawEvent.split('\n')) {
        const trimmed = line.trimStart();
        if (!trimmed.startsWith('data:')) continue;
        dataParts.push(trimmed.slice(5).trimStart());
      }
      if (!dataParts.length) continue;

      const data = dataParts.join('\n').trim();
      if (!data) continue;
      if (data === '[DONE]') {
        opts.onDone?.();
        return;
      }

      let delta = '';
      try {
        delta = extractText(JSON.parse(data));
      } catch {
        // Some servers stream plain text without JSON.
        delta = data;
      }

      if (delta) opts.onDelta(delta);
    }
  }

  opts.onDone?.();
}
