import crypto from "node:crypto";
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/googlechat";
import type { ResolvedGoogleChatAccount } from "./accounts.js";
import { getGoogleChatAccessToken } from "./auth.js";
import type { GoogleChatReaction } from "./types.js";

const CHAT_API_BASE = "https://chat.googleapis.com/v1";
const CHAT_UPLOAD_BASE = "https://chat.googleapis.com/upload/v1";

const headersToObject = (headers?: HeadersInit): Record<string, string> =>
  headers instanceof Headers
    ? Object.fromEntries(headers.entries())
    : Array.isArray(headers)
      ? Object.fromEntries(headers)
      : headers || {};

async function withGoogleChatResponse<T>(params: {
  account: ResolvedGoogleChatAccount;
  url: string;
  init?: RequestInit;
  auditContext: string;
  errorPrefix?: string;
  handleResponse: (response: Response) => Promise<T>;
}): Promise<T> {
  const {
    account,
    url,
    init,
    auditContext,
    errorPrefix = "Google Chat API",
    handleResponse,
  } = params;
  const token = await getGoogleChatAccessToken(account);
  const { response, release } = await fetchWithSsrFGuard({
    url,
    init: {
      ...init,
      headers: {
        ...headersToObject(init?.headers),
        Authorization: `Bearer ${token}`,
      },
    },
    auditContext,
  });
  try {
    if (!response.ok) {
      const text = await response.text().catch(() => "");
      throw new Error(`${errorPrefix} ${response.status}: ${text || response.statusText}`);
    }
    return await handleResponse(response);
  } finally {
    await release();
  }
}

async function fetchJson<T>(
  account: ResolvedGoogleChatAccount,
  url: string,
  init: RequestInit,
): Promise<T> {
  return await withGoogleChatResponse({
    account,
    url,
    init: {
      ...init,
      headers: {
        ...headersToObject(init.headers),
        "Content-Type": "application/json",
      },
    },
    auditContext: "googlechat.api.json",
    handleResponse: async (response) => (await response.json()) as T,
  });
}

async function fetchOk(
  account: ResolvedGoogleChatAccount,
  url: string,
  init: RequestInit,
): Promise<void> {
  await withGoogleChatResponse({
    account,
    url,
    init,
    auditContext: "googlechat.api.ok",
    handleResponse: async () => undefined,
  });
}

async function fetchBuffer(
  account: ResolvedGoogleChatAccount,
  url: string,
  init?: RequestInit,
  options?: { maxBytes?: number },
): Promise<{ buffer: Buffer; contentType?: string }> {
  return await withGoogleChatResponse({
    account,
    url,
    init,
    auditContext: "googlechat.api.buffer",
    handleResponse: async (res) => {
      const maxBytes = options?.maxBytes;
      const lengthHeader = res.headers.get("content-length");
      if (maxBytes && lengthHeader) {
        const length = Number(lengthHeader);
        if (Number.isFinite(length) && length > maxBytes) {
          throw new Error(`Google Chat media exceeds max bytes (${maxBytes})`);
        }
      }
      if (!maxBytes || !res.body) {
        const buffer = Buffer.from(await res.arrayBuffer());
        const contentType = res.headers.get("content-type") ?? undefined;
        return { buffer, contentType };
      }
      const reader = res.body.getReader();
      const chunks: Buffer[] = [];
      let total = 0;
      while (true) {
        const { done, value } = await reader.read();
        if (done) {
          break;
        }
        if (!value) {
          continue;
        }
        total += value.length;
        if (total > maxBytes) {
          await reader.cancel();
          throw new Error(`Google Chat media exceeds max bytes (${maxBytes})`);
        }
        chunks.push(Buffer.from(value));
      }
      const buffer = Buffer.concat(chunks, total);
      const contentType = res.headers.get("content-type") ?? undefined;
      return { buffer, contentType };
    },
  });
}

export async function sendGoogleChatMessage(params: {
  account: ResolvedGoogleChatAccount;
  space: string;
  text?: string;
  thread?: string;
  attachments?: Array<{ attachmentUploadToken: string; contentName?: string }>;
}): Promise<{ messageName?: string } | null> {
  const { account, space, text, thread, attachments } = params;
  const body: Record<string, unknown> = {};
  if (text) {
    body.text = text;
  }
  if (thread) {
    body.thread = { name: thread };
  }
  if (attachments && attachments.length > 0) {
    body.attachment = attachments.map((item) => ({
      attachmentDataRef: { attachmentUploadToken: item.attachmentUploadToken },
      ...(item.contentName ? { contentName: item.contentName } : {}),
    }));
  }
  const urlObj = new URL(`${CHAT_API_BASE}/${space}/messages`);
  if (thread) {
    urlObj.searchParams.set("messageReplyOption", "REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD");
  }
  const url = urlObj.toString();
  const result = await fetchJson<{ name?: string }>(account, url, {
    method: "POST",
    body: JSON.stringify(body),
  });
  return result ? { messageName: result.name } : null;
}

export async function updateGoogleChatMessage(params: {
  account: ResolvedGoogleChatAccount;
  messageName: string;
  text: string;
}): Promise<{ messageName?: string }> {
  const { account, messageName, text } = params;
  const url = `${CHAT_API_BASE}/${messageName}?updateMask=text`;
  const result = await fetchJson<{ name?: string }>(account, url, {
    method: "PATCH",
    body: JSON.stringify({ text }),
  });
  return { messageName: result.name };
}

export async function deleteGoogleChatMessage(params: {
  account: ResolvedGoogleChatAccount;
  messageName: string;
}): Promise<void> {
  const { account, messageName } = params;
  const url = `${CHAT_API_BASE}/${messageName}`;
  await fetchOk(account, url, { method: "DELETE" });
}

export async function uploadGoogleChatAttachment(params: {
  account: ResolvedGoogleChatAccount;
  space: string;
  filename: string;
  buffer: Buffer;
  contentType?: string;
}): Promise<{ attachmentUploadToken?: string }> {
  const { account, space, filename, buffer, contentType } = params;
  const boundary = `openclaw-${crypto.randomUUID()}`;
  const metadata = JSON.stringify({ filename });
  const header = `--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${metadata}\r\n`;
  const mediaHeader = `--${boundary}\r\nContent-Type: ${contentType ?? "application/octet-stream"}\r\n\r\n`;
  const footer = `\r\n--${boundary}--\r\n`;
  const body = Buffer.concat([
    Buffer.from(header, "utf8"),
    Buffer.from(mediaHeader, "utf8"),
    buffer,
    Buffer.from(footer, "utf8"),
  ]);

  const url = `${CHAT_UPLOAD_BASE}/${space}/attachments:upload?uploadType=multipart`;
  const payload = await withGoogleChatResponse<{
    attachmentDataRef?: { attachmentUploadToken?: string };
  }>({
    account,
    url,
    init: {
      method: "POST",
      headers: {
        "Content-Type": `multipart/related; boundary=${boundary}`,
      },
      body,
    },
    auditContext: "googlechat.upload",
    errorPrefix: "Google Chat upload",
    handleResponse: async (response) =>
      (await response.json()) as {
        attachmentDataRef?: { attachmentUploadToken?: string };
      },
  });
  return {
    attachmentUploadToken: payload.attachmentDataRef?.attachmentUploadToken,
  };
}

export async function downloadGoogleChatMedia(params: {
  account: ResolvedGoogleChatAccount;
  resourceName: string;
  maxBytes?: number;
}): Promise<{ buffer: Buffer; contentType?: string }> {
  const { account, resourceName, maxBytes } = params;
  const url = `${CHAT_API_BASE}/media/${resourceName}?alt=media`;
  return await fetchBuffer(account, url, undefined, { maxBytes });
}

export async function createGoogleChatReaction(params: {
  account: ResolvedGoogleChatAccount;
  messageName: string;
  emoji: string;
}): Promise<GoogleChatReaction> {
  const { account, messageName, emoji } = params;
  const url = `${CHAT_API_BASE}/${messageName}/reactions`;
  return await fetchJson<GoogleChatReaction>(account, url, {
    method: "POST",
    body: JSON.stringify({ emoji: { unicode: emoji } }),
  });
}

export async function listGoogleChatReactions(params: {
  account: ResolvedGoogleChatAccount;
  messageName: string;
  limit?: number;
}): Promise<GoogleChatReaction[]> {
  const { account, messageName, limit } = params;
  const url = new URL(`${CHAT_API_BASE}/${messageName}/reactions`);
  if (limit && limit > 0) {
    url.searchParams.set("pageSize", String(limit));
  }
  const result = await fetchJson<{ reactions?: GoogleChatReaction[] }>(account, url.toString(), {
    method: "GET",
  });
  return result.reactions ?? [];
}

export async function deleteGoogleChatReaction(params: {
  account: ResolvedGoogleChatAccount;
  reactionName: string;
}): Promise<void> {
  const { account, reactionName } = params;
  const url = `${CHAT_API_BASE}/${reactionName}`;
  await fetchOk(account, url, { method: "DELETE" });
}

export async function findGoogleChatDirectMessage(params: {
  account: ResolvedGoogleChatAccount;
  userName: string;
}): Promise<{ name?: string; displayName?: string } | null> {
  const { account, userName } = params;
  const url = new URL(`${CHAT_API_BASE}/spaces:findDirectMessage`);
  url.searchParams.set("name", userName);
  return await fetchJson<{ name?: string; displayName?: string }>(account, url.toString(), {
    method: "GET",
  });
}

export async function probeGoogleChat(account: ResolvedGoogleChatAccount): Promise<{
  ok: boolean;
  status?: number;
  error?: string;
}> {
  try {
    const url = new URL(`${CHAT_API_BASE}/spaces`);
    url.searchParams.set("pageSize", "1");
    await fetchJson<Record<string, unknown>>(account, url.toString(), {
      method: "GET",
    });
    return { ok: true };
  } catch (err) {
    return {
      ok: false,
      error: err instanceof Error ? err.message : String(err),
    };
  }
}
