/**
 * Google / Gemini runtime failure detector.
 *
 * Mirrors {@link sdk-failure-classifier} for Cursor, but categories and
 * suggested actions are provider-specific (`google_*` prefix).
 */

import type { SdkFailurePayloadFields } from "./sdk-failure-classifier.ts";

export type GoogleFailureCategory =
  | "google_genai_api_error"
  | "google_tool_error"
  | "google_session_settled_failed"
  | "google_settled_no_detail"
  | "transient_network"
  | "rate_limited";

export interface GoogleFailureDetail {
  sdk_error_message?: string;
  sdk_error_code?: string;
  sdk_error_name?: string;
  sdk_error_type?: string;
  provider_status?: string;
  provider_code?: string;
  sdk_no_detail?: boolean;
  sdk_no_detail_note?: string;
  raw_keys?: string[];
  raw_error_summary?: string;
  failed_tool_names?: string[];
  hit_max_rounds?: boolean;
}

const RATE_LIMIT_RE =
  /429|rate.?limit|quota|resource.?exhausted|too many requests|enhance.?your.?calm/i;
const TRANSIENT_RE =
  /econnreset|etimedout|econnrefused|socket hang up|fetch failed|network|timeout|503|502|504|unavailable/i;
const TOOL_FAIL_RE = /tool call failed|write_report|tool_error|fcop.*error/i;

function asRecord(v: unknown): Record<string, unknown> | null {
  if (v && typeof v === "object" && !Array.isArray(v)) {
    return v as Record<string, unknown>;
  }
  return null;
}

function pickString(...candidates: unknown[]): string | undefined {
  for (const c of candidates) {
    if (typeof c === "string" && c.trim()) return c.trim();
  }
  return undefined;
}

function summarizeRaw(raw: unknown): string | undefined {
  const r = asRecord(raw);
  if (!r) return undefined;
  try {
    const s = JSON.stringify(r);
    return s.length > 400 ? `${s.slice(0, 400)}…` : s;
  } catch {
    return undefined;
  }
}

/** True when raw is only `{ status: "error" | "failed" }` with no other fields. */
export function isGoogleResultNoDetail(raw: unknown): boolean {
  const r = asRecord(raw);
  if (!r) return false;
  const st = String(r.status ?? "").toLowerCase();
  if (st !== "error" && st !== "failed") return false;
  const keys = Object.keys(r).filter((k) => k !== "status");
  if (keys.length === 0) return true;
  return keys.every((k) => {
    const v = r[k];
    return v === undefined || v === null || v === "";
  });
}

export function extractGoogleFailureDetails(
  raw: unknown,
  opts?: { error_message?: string; failed_tool_names?: string[] },
): GoogleFailureDetail {
  const r = asRecord(raw);
  const nested = r ? asRecord(r.error) ?? asRecord(r.details) : null;

  const msg = pickString(
    opts?.error_message,
    r?.message,
    r?.error,
    nested?.message,
    typeof r?.error === "string" ? r.error : undefined,
  );

  const code = pickString(
    r?.code,
    r?.errorCode,
    r?.error_code,
    nested?.code,
    nested?.errorCode,
  );

  const name = pickString(r?.name, nested?.name);
  const type = pickString(r?.type, nested?.type);
  const providerStatus = pickString(r?.status, nested?.status);
  const providerCode = pickString(r?.provider_code, r?.providerCode);

  const noDetail = isGoogleResultNoDetail(raw);
  const rawKeys = r ? Object.keys(r) : undefined;

  const out: GoogleFailureDetail = {
    ...(msg ? { sdk_error_message: msg } : {}),
    ...(code ? { sdk_error_code: code } : {}),
    ...(name ? { sdk_error_name: name } : {}),
    ...(type ? { sdk_error_type: type } : {}),
    ...(providerStatus ? { provider_status: providerStatus } : {}),
    ...(providerCode ? { provider_code: providerCode } : {}),
    ...(opts?.failed_tool_names?.length
      ? { failed_tool_names: opts.failed_tool_names }
      : {}),
    ...(noDetail
      ? {
          sdk_no_detail: true,
          sdk_no_detail_note: "Gemini 会话结束但未暴露详细 API 错误",
          raw_keys: rawKeys,
          raw_error_summary: summarizeRaw(raw),
        }
      : rawKeys?.length
        ? { raw_keys: rawKeys, raw_error_summary: summarizeRaw(raw) }
        : {}),
  };

  return out;
}

export interface ClassifyGoogleFailureInput {
  status?: string;
  tool_call_count?: number;
  duration_ms?: number;
  raw?: unknown;
  error_message?: string;
  failed_tool_names?: string[];
  hit_max_rounds?: boolean;
}

export function classifyGoogleFailureCategory(
  input: ClassifyGoogleFailureInput,
): GoogleFailureCategory {
  const msg = (
    input.error_message ??
    extractGoogleFailureDetails(input.raw).sdk_error_message ??
    ""
  ).toLowerCase();

  if (RATE_LIMIT_RE.test(msg)) return "rate_limited";
  if (TRANSIENT_RE.test(msg)) return "transient_network";

  if (input.failed_tool_names && input.failed_tool_names.length > 0) {
    return "google_tool_error";
  }

  if (msg && TOOL_FAIL_RE.test(msg)) return "google_tool_error";

  const extracted = extractGoogleFailureDetails(input.raw, {
    error_message: input.error_message,
    failed_tool_names: input.failed_tool_names,
  });

  if (extracted.sdk_no_detail) return "google_settled_no_detail";

  if (
    extracted.sdk_error_message ||
    extracted.sdk_error_code ||
    extracted.sdk_error_name
  ) {
    return "google_genai_api_error";
  }

  const st = String(input.status ?? "").toLowerCase();
  if (st === "failed" || st === "error") {
    return "google_session_settled_failed";
  }

  return "google_session_settled_failed";
}

export function suggestedActionsForGoogleCategory(
  category: GoogleFailureCategory,
): string[] {
  switch (category) {
    case "google_tool_error":
      return [
        "查看 runtime jsonl 中 sdk.tool_call / sdk.status",
        "检查 FCoP 工具返回（write_report 的 status 须为 done/in_progress/blocked）",
        "查看 MCP 日志与 fcop 账本",
      ];
    case "google_genai_api_error":
      return [
        "查看 Gemini API 错误码与 message",
        "检查 GOOGLE_API_KEY / 模型配额",
        "查看 runtime jsonl 中 sdk.status raw",
      ];
    case "google_settled_no_detail":
      return [
        "查看 runtime jsonl 会话结束前的 sdk.status / sdk.tool_call",
        "检查是否有工具失败后未恢复（failed_tool_names）",
        "确认 report 是否已落盘（PM-to-ADMIN）",
      ];
    case "rate_limited":
      return ["等待后重试", "降低并发或切换模型", "检查 Google AI 配额"];
    case "transient_network":
      return ["等待后重试", "检查网络与代理", "查看 runtime jsonl"];
    case "google_session_settled_failed":
    default:
      return [
        "查看 runtime jsonl 与门铃故障区",
        "检查 FCoP 工具与 report 对账",
        "必要时重开会话",
      ];
  }
}

export interface BuildGoogleFailurePayloadContext {
  agent_id?: string;
  role?: string;
  session_id?: string;
  task_id?: string;
  chat_id?: string;
  tool_call_count?: number;
  duration_ms?: number;
  status?: string;
  raw?: unknown;
  error_message?: string;
  failed_tool_names?: string[];
  hit_max_rounds?: boolean;
}

export function buildGoogleFailurePayloadFields(
  ctx: BuildGoogleFailurePayloadContext,
): Record<string, unknown> {
  const extracted = extractGoogleFailureDetails(ctx.raw, {
    error_message: ctx.error_message,
    failed_tool_names: ctx.failed_tool_names,
  });
  const category = classifyGoogleFailureCategory({
    status: ctx.status,
    tool_call_count: ctx.tool_call_count,
    duration_ms: ctx.duration_ms,
    raw: ctx.raw,
    error_message: ctx.error_message ?? extracted.sdk_error_message,
    failed_tool_names: ctx.failed_tool_names,
    hit_max_rounds: ctx.hit_max_rounds,
  });
  const suggested_actions = suggestedActionsForGoogleCategory(category);

  return {
    runtime_provider: "google",
    failure_category: category,
    ...extracted,
    ...(ctx.hit_max_rounds ? { hit_max_rounds: true } : {}),
    ...(ctx.tool_call_count != null ? { tool_call_count: ctx.tool_call_count } : {}),
    ...(ctx.duration_ms != null ? { duration_ms: ctx.duration_ms } : {}),
    ...(ctx.agent_id ? { agent_id: ctx.agent_id } : {}),
    ...(ctx.role ? { role: ctx.role } : {}),
    ...(ctx.session_id ? { session_id: ctx.session_id } : {}),
    ...(ctx.task_id ? { task_id: ctx.task_id } : {}),
    ...(ctx.chat_id ? { chat_id: ctx.chat_id } : {}),
    suggested_actions,
  };
}

export type GoogleFailurePayloadFields = ReturnType<
  typeof buildGoogleFailurePayloadFields
>;

export const GOOGLE_FAILURE_PAYLOAD_KEYS = [
  "runtime_provider",
  "failure_category",
  "sdk_error_message",
  "sdk_error_code",
  "sdk_error_name",
  "sdk_error_type",
  "provider_status",
  "provider_code",
  "sdk_no_detail",
  "sdk_no_detail_note",
  "raw_keys",
  "raw_error_summary",
  "failed_tool_names",
  "hit_max_rounds",
  "suggested_actions",
  "tool_call_count",
  "duration_ms",
  "agent_id",
  "role",
  "session_id",
  "task_id",
  "chat_id",
] as const;

export function pickGoogleFailureFieldsFromPayload(
  pl: Record<string, unknown>,
): Record<string, unknown> {
  const out: Record<string, unknown> = {};
  for (const k of GOOGLE_FAILURE_PAYLOAD_KEYS) {
    const v = pl[k];
    if (v !== undefined && v !== null && v !== "") out[k] = v;
  }
  return out;
}

export interface RebuildGoogleFailureForSessionEndInput {
  runDetail?: GoogleFailurePayloadFields | Record<string, unknown>;
  duration_ms: number;
  tool_call_count: number;
  agent_id?: string;
  role?: string;
  session_id?: string;
  task_id?: string;
  error_message?: string;
  status?: string;
}

export function rebuildGoogleFailureForSessionEnd(
  input: RebuildGoogleFailureForSessionEndInput,
): Record<string, unknown> {
  const taskId = String(input.task_id ?? "");
  const runDetail = input.runDetail as Record<string, unknown> | undefined;
  const errMsg =
    input.error_message ??
    (typeof runDetail?.sdk_error_message === "string"
      ? runDetail.sdk_error_message
      : undefined);

  const failedToolNames = Array.isArray(runDetail?.failed_tool_names)
    ? (runDetail.failed_tool_names as string[])
    : undefined;

  let raw: unknown = { status: "error" };
  if (runDetail?.sdk_no_detail === true) {
    raw = { status: "error" };
  } else if (errMsg || runDetail?.sdk_error_code || failedToolNames?.length) {
    raw = {
      status: "error",
      ...(errMsg ? { message: errMsg, error: errMsg } : {}),
      ...(runDetail?.sdk_error_code
        ? { code: runDetail.sdk_error_code, errorCode: runDetail.sdk_error_code }
        : {}),
      ...(failedToolNames?.length ? { failed_tool_names: failedToolNames } : {}),
      ...(runDetail?.provider_code ? { provider_code: runDetail.provider_code } : {}),
    };
  }

  return buildGoogleFailurePayloadFields({
    status: input.status ?? "error",
    tool_call_count: input.tool_call_count,
    duration_ms: input.duration_ms,
    raw,
    error_message: errMsg,
    failed_tool_names: failedToolNames,
    hit_max_rounds: runDetail?.hit_max_rounds === true,
    agent_id: input.agent_id,
    role: input.role,
    session_id: input.session_id,
    task_id: taskId || undefined,
    chat_id: taskId.startsWith("CHAT-") ? taskId : undefined,
  });
}

/** SessionRun extension for Google adapter (same field name as Cursor for SessionManager). */
export type SessionRunWithGoogleFailure = {
  sdk_failure_detail?: SdkFailurePayloadFields | GoogleFailurePayloadFields;
  sdk_error?: string;
};
