import crypto from "node:crypto";

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";

async function fetchJson<T>(
  account: ResolvedGoogleChatAccount,
  url: string,
  init: RequestInit,
): Promise<T> {
  const token = await getGoogleChatAccessToken(account);
  const res = await fetch(url, {
    ...init,
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
      ...(init.headers ?? {}),
    },
  });
  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new Error(`Google Chat API ${res.status}: ${text || res.statusText}`);
  }
  return (await res.json()) as T;
}

async function fetchOk(
  account: ResolvedGoogleChatAccount,
  url: string,
  init: RequestInit,
): Promise<void> {
  const token = await getGoogleChatAccessToken(account);
  const res = await fetch(url, {
    ...init,
    headers: {
      Authorization: `Bearer ${token}`,
      ...(init.headers ?? {}),
    },
  });
  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new Error(`Google Chat API ${res.status}: ${text || res.statusText}`);
  }
}

async function fetchBuffer(
  account: ResolvedGoogleChatAccount,
  url: string,
  init?: RequestInit,
  options?: { maxBytes?: number },
): Promise<{ buffer: Buffer; contentType?: string }> {
  const token = await getGoogleChatAccessToken(account);
  const res = await fetch(url, {
    ...init,
    headers: {
      Authorization: `Bearer ${token}`,
      ...(init?.headers ?? {}),
    },
  });
  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new Error(`Google Chat API ${res.status}: ${text || res.statusText}`);
  }
  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 url = `${CHAT_API_BASE}/${space}/messages`;
  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 = `clawdbot-${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 token = await getGoogleChatAccessToken(account);
  const url = `${CHAT_UPLOAD_BASE}/${space}/attachments:upload?uploadType=multipart`;
  const res = await fetch(url, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": `multipart/related; boundary=${boundary}`,
    },
    body,
  });
  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new Error(`Google Chat upload ${res.status}: ${text || res.statusText}`);
  }
  const payload = (await res.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) };
  }
}
