/**
 * Provider Adapter — 基类
 *
 * 所有 Provider 适配器共享的基类逻辑：
 * - 请求超时处理
 * - 错误映射到 MetonaError
 * - 流式事件标准化
 */

import type {
  IMetonaProviderAdapter,
  AdapterConfig,
  MetonaRequest,
  MetonaResponse,
  MetonaStreamEvent,
  MetonaError,
} from '../types';
import { MetonaErrorCode } from '../types';
import type { MetonaModelInfo } from '../types/metona-adapter';

/**
 * 内容审核错误 — Provider 的安全过滤策略触发的错误
 *
 * 当 Provider（如 MiMo）的 API 返回 content_filter 错误码时抛出。
 * Engine 识别此错误后映射为 MetonaErrorCode.CONTENT_FILTERED，
 * 前端显示友好提示而非原始 JSON 错误体。
 */
export class ContentFilterError extends Error {
  /** Provider 返回的原始错误消息（如 "The request was rejected because it was considered high risk"） */
  readonly providerMessage: string;

  constructor(providerMessage: string, context: string) {
    super(`${context}: 内容被 Provider 安全审核拦截 - ${providerMessage}`);
    this.name = 'ContentFilterError';
    this.providerMessage = providerMessage;
  }
}

export abstract class BaseAdapter implements IMetonaProviderAdapter {
  // H-2 修复: provider → providerId（规范要求）
  abstract readonly providerId: string;
  abstract readonly supportedModels: string[];
  abstract readonly supportsToolCalling: boolean;
  abstract readonly supportsThinking: boolean;

  /**
   * C-2 修复: 外部注入的 AbortSignal（来自 Engine 的 abortController）
   *
   * 用户点击中断时，Engine 调用 abortController.abort()，此信号触发后，
   * 正在进行的 fetch 会被立即中断，避免资源泄漏。
   */
  private externalAbortSignal: AbortSignal | undefined;

  constructor(protected config: AdapterConfig) {}

  // H-2 修复: chat → send（规范要求）
  abstract send(request: MetonaRequest): Promise<MetonaResponse>;
  // H-2 修复: chatStream → sendStream（规范要求）
  abstract sendStream(request: MetonaRequest): AsyncIterable<MetonaStreamEvent>;

  /**
   * H-2 修复: 获取上下文窗口大小（规范要求）
   *
   * 默认实现从 config 读取 contextWindow，子类可覆盖以支持动态查询。
   * Engine 用此值估算上下文使用率，决定是否触发压缩。
   *
   * @returns 上下文窗口大小（token 数）
   */
  getContextWindow(): number {
    // 优先使用 AdapterConfig.contextWindow（如果存在）
    const ctx = (this.config as AdapterConfig & { contextWindow?: number }).contextWindow;
    if (typeof ctx === 'number' && ctx > 0) return ctx;
    // 默认 1M（保守值，子类应覆盖）
    return 1_000_000;
  }

  /**
   * C-2 修复: 注入外部 AbortSignal
   * Engine 在调用 send/sendStream 前调用此方法，关联 abortController
   */
  setAbortSignal(signal: AbortSignal | undefined): void {
    this.externalAbortSignal = signal;
  }

  /**
   * C-2 修复: 合并外部 abort signal 和 timeout signal
   *
   * 使用 AbortSignal.any() 合并两个信号，任一触发都会中断 fetch：
   * - timeout signal：防止请求挂起
   * - external abort signal：用户主动中断
   *
   * @param timeoutMs 超时时间（毫秒）
   * @returns 合并后的 AbortSignal
   * @deprecated 审查修复 M20: 使用 fetchWithTimeout 替代。
   * getFetchSignal 内部 AbortSignal.timeout() 创建的 timer 在请求成功完成后仍会存活到超时，
   * 高频调用下 timer 句柄累积；fetchWithTimeout 用 setTimeout + clearTimeout 已解决此问题。
   */
  protected getFetchSignal(timeoutMs: number): AbortSignal {
    const timeoutSignal = AbortSignal.timeout(timeoutMs);

    // 如果没有外部信号，直接使用 timeout signal
    if (!this.externalAbortSignal) {
      return timeoutSignal;
    }

    // 如果外部信号已经 abort，直接返回它
    if (this.externalAbortSignal.aborted) {
      return this.externalAbortSignal;
    }

    // 合并两个信号 — 任一触发都会 abort
    // Node.js 20+ / Electron 35+ 支持 AbortSignal.any()
    return AbortSignal.any([timeoutSignal, this.externalAbortSignal]);
  }

  /**
   * #24 修复: 封装 fetch + 超时控制，在 finally 中 clearTimeout，避免 timer 泄漏
   *
   * getFetchSignal 使用 AbortSignal.timeout() 内部创建的 timer 在请求成功完成后
   * 仍会存活到超时，高频调用下 timer 句柄累积。本方法使用 setTimeout + clearTimeout
   * 确保 fetch 完成（无论成功/失败/abort）后立即清理 timer。
   *
   * @param url 请求 URL
   * @param init fetch init（不含 signal，由本方法内部管理）
   * @param timeoutMs 超时时间（毫秒）
   */
  protected async fetchWithTimeout(url: string, init: RequestInit, timeoutMs: number): Promise<Response> {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeoutMs);

    // 审查修复 M20: 保存 listener 引用，finally 中 removeEventListener 清理，避免 listener 泄漏
    const onExternalAbort = () => controller.abort();

    try {
      // 合并外部 abort signal（来自 Engine 的 abortController）
      if (this.externalAbortSignal) {
        if (this.externalAbortSignal.aborted) {
          controller.abort();
        } else {
          this.externalAbortSignal.addEventListener('abort', onExternalAbort, { once: true });
        }
      }

      return await fetch(url, { ...init, signal: controller.signal });
    } finally {
      // #24 修复: 关键 — 无论请求成功、失败还是 abort，都清理 timer
      clearTimeout(timer);
      // 审查修复 M20: 清理 externalAbortSignal 上注册的 listener
      // （即使 { once: true }，请求正常完成时 listener 仍挂在 signal 上直到 abort 或 GC，需显式移除）
      if (this.externalAbortSignal) {
        this.externalAbortSignal.removeEventListener('abort', onExternalAbort);
      }
    }
  }

  async healthCheck(): Promise<boolean> {
    try {
      await this.listModels();
      return true;
    } catch {
      return false;
    }
  }

  /**
   * H-2 修复: 返回 MetonaModelInfo[]（规范要求）
   *
   * 默认实现将 supportedModels 映射为 MetonaModelInfo[]，
   * 子类可覆盖以从 API 获取完整元信息。
   */
  async listModels(): Promise<MetonaModelInfo[]> {
    return this.supportedModels.map((id) => ({ id }));
  }

  /**
   * P1-4 修复: 构造带 HTTP status 属性的 Error 并抛出
   * engine.isRetryableError 依赖 error.status 判断是否可重试（429/5xx）
   *
   * v0.3.17: 解析 Provider 返回的 JSON 错误体，识别 content_filter 错误码，
   * 抛出 ContentFilterError 让 Engine 映射为 CONTENT_FILTERED 错误码。
   */
  protected async throwHttpError(response: Response, context: string): Promise<never> {
    let errorBody = '';
    try { errorBody = await response.text(); } catch { /* body 可能已消费或为 null */ }

    // v0.3.17: 解析 JSON 错误体，识别 content_filter
    if (errorBody) {
      try {
        const parsed = JSON.parse(errorBody);
        // 兼容 OpenAI 格式 {error: {code, message}} 和 MiMo/其他格式
        const errObj = parsed?.error ?? parsed;
        const errorCode = errObj?.code ?? errObj?.type ?? '';
        const errorMessage = errObj?.message ?? '';

        if (typeof errorCode === 'string' && errorCode.toLowerCase().includes('content_filter')) {
          const providerMsg = errorMessage || '内容触发安全过滤策略';
          const cfError = new ContentFilterError(providerMsg, context);
          (cfError as ContentFilterError & { status: number }).status = response.status;
          throw cfError;
        }
      } catch (parseErr) {
        // JSON 解析失败（非 JSON 响应体），走原逻辑
        if (parseErr instanceof ContentFilterError) throw parseErr;
      }
    }

    const error = new Error(`${context}: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ''}`);
    (error as Error & { status: number }).status = response.status;
    throw error;
  }

  /**
   * 将原生错误映射为 MetonaError
   */
  protected mapError(error: unknown): MetonaError {
    if (error instanceof Error) {
      // v0.3.17: 优先识别 ContentFilterError
      if (error instanceof ContentFilterError) {
        return {
          code: MetonaErrorCode.CONTENT_FILTERED,
          message: '内容被 Provider 安全审核拦截，请修改图片或文本后重试',
          provider: this.providerId,
          retryable: false,
        };
      }

      const msg = error.message.toLowerCase();

      if (msg.includes('timeout') || msg.includes('ETIMEDOUT')) {
        return {
          code: MetonaErrorCode.NETWORK_TIMEOUT,
          message: error.message,
          provider: this.providerId,
          retryable: true,
          retryAfterMs: 3000,
        };
      }

      if (msg.includes('ECONNREFUSED') || msg.includes('ENOTFOUND') || msg.includes('ECONNRESET')) {
        return {
          code: MetonaErrorCode.NETWORK_ERROR,
          message: error.message,
          provider: this.providerId,
          retryable: true,
          retryAfterMs: 3000,
        };
      }

      // #23 修复: 优先基于 HTTP status code 判断 401/429，避免字符串 includes 误匹配 URL 端口等数字
      // throwHttpError 已将 response.status 挂到 error.status，优先读取此字段
      const httpStatus = (error as Error & { status?: number }).status;

      // #23 修复: 401 认证失败 — 优先用 status code，'unauthorized' 是单词不会误匹配
      if (httpStatus === 401 || msg.includes('unauthorized')) {
        return {
          code: MetonaErrorCode.AUTH_INVALID,
          message: 'API key 无效或已过期',
          provider: this.providerId,
          retryable: false,
        };
      }

      // #23 修复: 429 限流 — 优先用 status code，'rate limit' 是单词不会误匹配
      if (httpStatus === 429 || msg.includes('rate limit')) {
        return {
          code: MetonaErrorCode.RATE_LIMITED,
          message: '请求过于频繁，请稍后重试',
          provider: this.providerId,
          retryable: true,
          retryAfterMs: 5000,
        };
      }
    }

    return {
      code: MetonaErrorCode.UNKNOWN,
      message: error instanceof Error ? error.message : 'Unknown error',
      provider: this.providerId,
      retryable: false,
    };
  }
}
