import type { AgentRuntime } from "@best-agent/engine";
import type { AskUserQuestion, ThreadDetail, ThreadId, ThreadMode } from "@best-agent/protocol";
import {
  type BridgeMessage,
  createBestAgentNativeBridge,
  resolveInitialBestAgentThread,
  type BestAgentSessionsApi,
} from "./best-agent-native-bridge.js";

type ACPMessage = Readonly<{
  content?: unknown;
  metadata?: Record<string, unknown>;
  reasoning_content?: string;
  role: "assistant" | "tool" | "user";
  tool_call_id?: string;
  tool_calls?: ReadonlyArray<{
    function: {
      arguments: string;
      name: string;
    };
    id: string;
    type: "function";
  }>;
}>;

type PendingAction = Readonly<{
  actionId: string;
  kind: "tool_approval" | "ask_user";
  questions?: readonly AskUserQuestion[];
  toolCallId?: string;
  toolName: string;
}>;

type NativeBridge = ReturnType<typeof createBestAgentNativeBridge>;
type NativeAgent = NativeBridge["agent"];
type GoatChainBridgeAgent = NativeAgent &
  Readonly<{
    receiveMessage: (
      messages: readonly unknown[],
      metadata?: Record<string, unknown>,
    ) => AsyncIterable<ACPMessage>;
  }>;

type ReceiveMetadata = Readonly<{
  abortSignal?: AbortSignal;
  resume?: boolean;
  session_id?: string;
  signal?: AbortSignal;
  toolContext?: Readonly<{
    approval?: Readonly<{
      alwaysAllowForConversation?: boolean;
      decisions?: Readonly<Record<string, Readonly<{ approved: boolean; reason?: string }>>>;
    }>;
    askUser?: Readonly<{
      answers?: Readonly<Record<string, Readonly<Record<string, string | string[]>>>>;
      rejected?: Readonly<Record<string, string>>;
    }>;
  }>;
}>;

export type GoatChainSessionsApi = BestAgentSessionsApi;

type AsyncQueueState<T> = {
  closed: boolean;
  pending: T[];
  waiters: Array<(result: IteratorResult<T>) => void>;
};

function createAsyncQueue<T>(opts?: Readonly<{ onClose?: () => void }>) {
  const state: AsyncQueueState<T> = {
    closed: false,
    pending: [],
    waiters: [],
  };
  let closedNotified = false;

  const notifyClosed = () => {
    if (closedNotified) return;
    closedNotified = true;
    opts?.onClose?.();
  };

  return {
    close(): void {
      if (state.closed) return;
      state.closed = true;
      while (state.waiters.length > 0)
        state.waiters.shift()?.({ done: true, value: undefined as never });
      notifyClosed();
    },
    push(value: T): void {
      if (state.closed) return;
      const waiter = state.waiters.shift();
      if (waiter) {
        waiter({ done: false, value });
        return;
      }
      state.pending.push(value);
    },
    [Symbol.asyncIterator](): AsyncIterator<T> {
      return {
        next(): Promise<IteratorResult<T>> {
          if (state.pending.length > 0) {
            return Promise.resolve({
              done: false,
              value: state.pending.shift() as T,
            });
          }
          if (state.closed) return Promise.resolve({ done: true, value: undefined as never });
          return new Promise<IteratorResult<T>>((resolve) => {
            state.waiters.push(resolve);
          });
        },
        return(): Promise<IteratorResult<T>> {
          if (!state.closed) {
            state.closed = true;
            while (state.waiters.length > 0)
              state.waiters.shift()?.({ done: true, value: undefined as never });
          }
          notifyClosed();
          return Promise.resolve({ done: true, value: undefined as never });
        },
      };
    },
  };
}

function normalizeText(value: unknown): string {
  return String(value ?? "").trim();
}

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

function serializeUnknown(value: unknown): string {
  if (typeof value === "string") return value;
  try {
    return JSON.stringify(value);
  } catch {
    return String(value);
  }
}

function makeDoneMessage(input: { error?: string; stopReason: string }): ACPMessage {
  return {
    content: "",
    metadata: {
      done: true,
      ...(input.error ? { error: input.error } : {}),
      stopReason: input.stopReason,
    },
    role: "assistant",
  };
}

function makeAssistantStreamMessage(
  content: string,
  originalEventType: string,
  metadata: Record<string, unknown> = {},
): ACPMessage {
  return {
    content,
    metadata: {
      ...metadata,
      originalEventType,
    },
    role: "assistant",
  };
}

type StrictDecisionPayload =
  | Readonly<{ kind: "respond"; text: string }>
  | Readonly<{ kind: "tool"; reasoning?: string }>;

function parseStrictDecisionPayload(content: string): StrictDecisionPayload | null {
  const trimmed = content.trim();
  const direct = parseStrictDecisionObject(trimmed);
  if (direct) return direct;

  const embedded = extractEmbeddedDecisionPayload(trimmed);
  if (!embedded) return null;

  if (embedded.payload.kind !== "tool") return embedded.payload;

  const preamble = trimmed
    .slice(0, embedded.startIndex)
    .replace(/<\/?tool_call>/gi, " ")
    .trim();
  const reasoning = mergeToolReasoning(preamble, embedded.payload.reasoning);
  return reasoning ? { kind: "tool", reasoning } : embedded.payload;
}

function parseStrictDecisionObject(content: string): StrictDecisionPayload | null {
  const trimmed = content.trim();
  if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;

  let parsed: unknown;
  try {
    parsed = JSON.parse(trimmed);
  } catch {
    return null;
  }
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;

  const record = parsed as Record<string, unknown>;
  const kind = String(record.kind ?? "").trim();
  if (kind === "respond") {
    const keys = Object.keys(record);
    if (keys.length !== 2 || !keys.includes("kind") || !keys.includes("text")) return null;
    const text = typeof record.text === "string" ? record.text : "";
    return text ? { kind: "respond", text } : null;
  }
  if (kind === "tool") {
    const keys = Object.keys(record);
    const allowedKeys = new Set(["kind", "toolName", "input", "reasoning"]);
    if (keys.some((key) => !allowedKeys.has(key))) return null;
    if (typeof record.toolName !== "string" || !record.toolName.trim()) return null;
    if (!("input" in record)) return null;
    return {
      kind: "tool",
      ...(typeof record.reasoning === "string" && record.reasoning.trim()
        ? { reasoning: record.reasoning }
        : {}),
    };
  }
  return null;
}

function extractEmbeddedDecisionPayload(
  content: string,
): Readonly<{ payload: StrictDecisionPayload; startIndex: number }> | null {
  const ranges = extractJsonObjectRanges(content);
  if (ranges.length !== 1) return null;
  const range = ranges[0];
  if (!range) return null;
  const payload = parseStrictDecisionObject(content.slice(range.start, range.end));
  if (!payload) return null;
  return {
    payload,
    startIndex: range.start,
  };
}

function extractJsonObjectRanges(content: string): Array<{ end: number; start: number }> {
  const ranges: Array<{ end: number; start: number }> = [];
  let depth = 0;
  let start = -1;
  let inString = false;
  let escaped = false;

  for (let index = 0; index < content.length; index += 1) {
    const char = content[index];
    if (!char) continue;

    if (inString) {
      if (escaped) {
        escaped = false;
        continue;
      }
      if (char === "\\") {
        escaped = true;
        continue;
      }
      if (char === '"') inString = false;
      continue;
    }

    if (char === '"') {
      inString = true;
      continue;
    }

    if (char === "{") {
      if (depth === 0) start = index;
      depth += 1;
      continue;
    }

    if (char === "}") {
      if (depth === 0) continue;
      depth -= 1;
      if (depth === 0 && start >= 0) {
        ranges.push({ end: index + 1, start });
        start = -1;
      }
    }
  }

  return ranges;
}

function mergeToolReasoning(preamble: string, reasoning?: string): string | undefined {
  const normalizedPreamble = preamble.trim();
  const normalizedReasoning = typeof reasoning === "string" ? reasoning.trim() : "";
  if (!normalizedPreamble) return normalizedReasoning || undefined;
  if (!normalizedReasoning) return normalizedPreamble;
  if (normalizedPreamble === normalizedReasoning) return normalizedReasoning;
  return `${normalizedPreamble}\n\n${normalizedReasoning}`;
}

function extractPromptText(content: unknown): string {
  if (typeof content === "string") return content.trim();
  if (!Array.isArray(content)) return "";
  return content
    .map((part) => {
      const record = asRecord(part);
      return typeof record.text === "string" ? record.text : "";
    })
    .join("")
    .trim();
}

function extractLatestUserPrompt(messages: readonly unknown[]): string {
  for (let index = messages.length - 1; index >= 0; index -= 1) {
    const message = asRecord(messages[index]);
    if (normalizeText(message.role) !== "user") continue;
    const prompt = extractPromptText(message.content);
    if (prompt) return prompt;
  }
  return "";
}

function readAbortSignal(metadata: ReceiveMetadata): AbortSignal | undefined {
  const signal = metadata.signal ?? metadata.abortSignal;
  return signal instanceof AbortSignal ? signal : undefined;
}

function serializeToolArguments(input: Record<string, unknown>): string {
  if (Object.keys(input).length === 1 && typeof input._raw === "string") {
    return input._raw;
  }
  try {
    return JSON.stringify(input);
  } catch {
    return "";
  }
}

function toolOutputFromEvent(output: unknown): {
  content: string;
  structuredContent?: Record<string, unknown>;
} {
  const record = asRecord(output);
  const structuredContent = asRecord(record.structuredContent);
  const value = "output" in record ? record.output : output;
  return {
    content: serializeUnknown(value),
    ...(Object.keys(structuredContent).length > 0 ? { structuredContent } : {}),
  };
}

function errorMessage(error: unknown): string {
  if (error instanceof Error) return error.message;
  return normalizeText(error) || "Unknown error";
}

async function openOrEnsureSession(agent: NativeAgent, sessionId: string) {
  const openSession = agent.openSession;
  if (typeof openSession === "function") {
    const existing = await openSession(sessionId);
    if (existing) return existing;
  }
  const ensureSession = agent.ensureSession;
  if (typeof ensureSession === "function") return await ensureSession({ sessionId });
  return null;
}

export async function resolveInitialGoatChainThread(
  runtime: AgentRuntime,
  options: Readonly<{
    latest?: boolean;
    resumeThreadId?: string;
    threadMode?: ThreadMode;
  }>,
): Promise<ThreadDetail> {
  return await resolveInitialBestAgentThread(runtime, options);
}

export function createGoatChainBridge(
  options: Readonly<{
    agentProfile?: string;
    runtime: AgentRuntime;
    threadMode?: ThreadMode;
  }>,
): Readonly<{
  agent: GoatChainBridgeAgent;
  attachStore: (store: unknown) => void;
  hydrateThread: (threadId: ThreadId) => Promise<BridgeMessage[]>;
  sessions: GoatChainSessionsApi;
}> {
  const bridge = createBestAgentNativeBridge(options);
  const pendingActions = new Map<string, PendingAction>();

  const receiveMessageStream = async function* (
    messages: readonly unknown[],
    rawMetadata: Record<string, unknown> = {},
  ): AsyncGenerator<ACPMessage, void, void> {
    const metadata = rawMetadata as ReceiveMetadata;
    const sessionId = normalizeText(metadata.session_id);
    if (!sessionId) {
      yield makeDoneMessage({
        error: "Missing session_id for GoatChain chat.",
        stopReason: "failed",
      });
      return;
    }

    const session = await openOrEnsureSession(bridge.agent, sessionId);
    if (!session) {
      yield makeDoneMessage({
        error: "Session bridge is unavailable.",
        stopReason: "failed",
      });
      return;
    }

    const pending = pendingActions.get(sessionId);
    const signal = readAbortSignal(metadata);
    let stream: AsyncIterable<unknown>;

    if (pending) {
      if (pending.kind === "tool_approval") {
        const decision = pending.toolCallId
          ? metadata.toolContext?.approval?.decisions?.[pending.toolCallId]
          : undefined;
        if (!decision) {
          yield makeDoneMessage({
            error: `Missing approval decision for ${pending.toolCallId ?? pending.actionId}.`,
            stopReason: "failed",
          });
          return;
        }
        stream = session.resume(
          {
            actionId: pending.actionId,
            alwaysAllowForConversation: metadata.toolContext?.approval?.alwaysAllowForConversation,
            approved: decision.approved,
            kind: "tool_approval",
            ...(decision.reason ? { reason: decision.reason } : {}),
            ...(pending.toolCallId ? { toolCallId: pending.toolCallId } : {}),
          },
          { signal },
        );
      } else {
        const answers = pending.toolCallId
          ? metadata.toolContext?.askUser?.answers?.[pending.toolCallId]
          : undefined;
        const rejectedReason = pending.toolCallId
          ? metadata.toolContext?.askUser?.rejected?.[pending.toolCallId]
          : undefined;
        if (!answers && !rejectedReason) {
          yield makeDoneMessage({
            error: `Missing ask-user answers for ${pending.toolCallId ?? pending.actionId}.`,
            stopReason: "failed",
          });
          return;
        }
        stream = session.resume(
          {
            actionId: pending.actionId,
            answers: { ...answers },
            kind: "ask_user",
            ...(pending.toolCallId ? { toolCallId: pending.toolCallId } : {}),
            ...(rejectedReason ? { reason: rejectedReason, rejected: true } : {}),
          } as never,
          { signal },
        );
      }
    } else {
      const prompt = extractLatestUserPrompt(messages);
      const attach = metadata.resume === true;
      if (!prompt && !attach) {
        yield makeDoneMessage({
          error: "Missing user prompt.",
          stopReason: "failed",
        });
        return;
      }
      stream = session.run(
        {
          content: prompt,
          ...(attach ? { metadata: { resume: true } } : {}),
        },
        { signal },
      );
    }

    let streamedAssistantText = "";

    try {
      for await (const event of stream) {
        const record = asRecord(event);
        const eventType = normalizeText(record.type);

        if (eventType === "assistant.message") {
          const message = asRecord(record.message);
          const content = normalizeText(message.content);
          const thinking = normalizeText(message.thinking);
          const leakedDecision = content ? parseStrictDecisionPayload(content) : null;
          const leakedToolReasoning =
            leakedDecision?.kind === "tool" ? leakedDecision.reasoning : undefined;
          if (thinking || leakedToolReasoning) {
            yield {
              ...(content && leakedDecision?.kind !== "tool" ? { content } : {}),
              reasoning_content: thinking || leakedToolReasoning,
              role: "assistant",
            };
            if (!content || leakedDecision?.kind === "tool") continue;
          }
          const settledText =
            leakedDecision?.kind === "respond"
              ? leakedDecision.text
              : leakedDecision?.kind === "tool"
                ? ""
                : content;
          if (settledText) {
            if (streamedAssistantText && settledText === streamedAssistantText) {
              streamedAssistantText = settledText;
              continue;
            }
            streamedAssistantText = settledText;
            yield makeAssistantStreamMessage(settledText, "text_end", {
              settled: true,
            });
          }
          continue;
        }

        if (eventType === "assistant.delta") {
          const delta = String(record.delta ?? "");
          if (delta) {
            const leakedDecision = parseStrictDecisionPayload(delta);
            if (leakedDecision?.kind === "tool") {
              yield {
                ...(leakedDecision.reasoning
                  ? { reasoning_content: leakedDecision.reasoning }
                  : {}),
                role: "assistant",
              };
              continue;
            }
            if (leakedDecision?.kind === "respond") {
              streamedAssistantText += leakedDecision.text;
              yield makeAssistantStreamMessage(leakedDecision.text, "text_delta");
              continue;
            }
            streamedAssistantText += delta;
            yield makeAssistantStreamMessage(delta, "text_delta");
          }
          continue;
        }

        if (eventType === "thinking.started") {
          yield {
            metadata: { originalEventType: "thinking_start" },
            role: "assistant",
          };
          continue;
        }

        if (eventType === "thinking.delta") {
          const delta = String(record.delta ?? "");
          yield {
            metadata: { originalEventType: "thinking_delta" },
            reasoning_content: delta,
            role: "assistant",
          };
          continue;
        }

        if (eventType === "thinking.finished") {
          yield {
            metadata: { originalEventType: "thinking_end" },
            reasoning_content: normalizeText(record.content),
            role: "assistant",
          };
          continue;
        }

        if (eventType === "tool.started") {
          if (normalizeText(record.subagentId)) {
            continue;
          }
          const toolName = normalizeText(record.toolName) || "tool";
          const toolCallId = normalizeText(record.toolCallId) || "tool";
          const toolInput = asRecord(record.input);
          yield {
            metadata: record.originalEventType
              ? { originalEventType: record.originalEventType }
              : undefined,
            role: "assistant",
            tool_calls: [
              {
                function: {
                  arguments: serializeToolArguments(toolInput),
                  name: toolName,
                },
                id: toolCallId,
                type: "function",
              },
            ],
          };
          continue;
        }

        if (eventType === "tool.finished") {
          if (normalizeText(record.subagentId)) {
            continue;
          }
          const toolCallId = normalizeText(record.toolCallId) || "tool";
          const output = toolOutputFromEvent(record.output);
          yield {
            content: output.content,
            metadata: output.structuredContent
              ? { structuredContent: output.structuredContent }
              : undefined,
            role: "tool",
            tool_call_id: toolCallId,
          };
          continue;
        }

        if (eventType === "usage.updated") {
          yield {
            metadata: {
              ...(record.lastRequestBudget ? { lastRequestBudget: record.lastRequestBudget } : {}),
              originalEventType: "usage_refresh",
              ...(record.usage ? { usage: record.usage } : {}),
            },
            role: "assistant",
          };
          continue;
        }

        if (eventType === "action.required") {
          const kind = normalizeText(record.kind);
          if (kind === "tool_approval") {
            const toolCallId = normalizeText(record.toolCallId);
            pendingActions.set(sessionId, {
              actionId: normalizeText(record.actionId),
              kind: "tool_approval",
              toolCallId: toolCallId || undefined,
              toolName: normalizeText(record.toolName) || "tool",
            });
            yield {
              content: normalizeText(record.reason) || "Approval requested.",
              metadata: {
                approval_requested: true,
                permission: record.permission,
                requires_action: true,
                risk_level: record.riskLevel,
                tool_call_id: toolCallId,
                tool_name: record.toolName,
              },
              role: "assistant",
            };
            yield makeDoneMessage({ stopReason: "approval_required" });
            return;
          }

          if (kind === "ask_user") {
            const toolCallId = normalizeText(record.toolCallId);
            pendingActions.set(sessionId, {
              actionId: normalizeText(record.actionId),
              kind: "ask_user",
              questions: (record.questions as readonly AskUserQuestion[] | undefined) ?? [],
              toolCallId: toolCallId || undefined,
              toolName: normalizeText(record.toolName) || "ask-user",
            });
            yield {
              content: "",
              metadata: {
                kind: "ask_user",
                questions: record.questions,
                requires_action: true,
                tool_call_id: toolCallId,
                tool_name: record.toolName,
              },
              role: "assistant",
            };
            yield makeDoneMessage({ stopReason: "action_required" });
            return;
          }
          continue;
        }

        if (eventType === "run.finished") {
          pendingActions.delete(sessionId);
          yield makeDoneMessage({
            stopReason: record.status === "interrupted" ? "interrupted" : "completed",
          });
          return;
        }

        if (eventType === "run.error") {
          pendingActions.delete(sessionId);
          yield makeDoneMessage({
            error: errorMessage(record.error),
            stopReason: "failed",
          });
          return;
        }
      }
    } catch (error) {
      pendingActions.delete(sessionId);
      yield makeDoneMessage({
        error: errorMessage(error),
        stopReason: "failed",
      });
    }
  };

  const agent: GoatChainBridgeAgent = {
    ...bridge.agent,
    receiveMessage(messages: readonly unknown[], rawMetadata = {}) {
      const source = receiveMessageStream(messages, rawMetadata);
      const queue = createAsyncQueue<ACPMessage>({
        onClose: () => {
          void source.return?.();
        },
      });
      void (async () => {
        try {
          for await (const message of source) queue.push(message);
        } finally {
          queue.close();
        }
      })();
      return queue;
    },
  };

  return {
    ...bridge,
    attachStore(store) {
      bridge.attachStore(store as never);
    },
    agent,
  };
}
