// Slack Web API client (OAuth code exchange + chat.postMessage). Pure fetch
// wrapper with no module dependencies so it stays runtime-clean and
// injectable for tests.

const SLACK_API_BASE = "https://slack.com/api";
const OAUTH_V2_ACCESS = `${SLACK_API_BASE}/oauth.v2.access`;
const CHAT_POST_MESSAGE = `${SLACK_API_BASE}/chat.postMessage`;

// Slack calls run while a SlackWorkspaceIntegration row lock may be held; an unbounded
// fetch would pin that lock for the lifetime of a hung connection.
const FETCH_TIMEOUT_MS = 10_000;

async function parseJsonResponse<T>(response: Response): Promise<T> {
  // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- runtime fetch boundary
  return (await response.json()) as T;
}

export interface SlackOauthAccessResult {
  ok: boolean;
  team?: { id: string; name?: string };
  bot_user_id?: string;
  access_token?: string;
  error?: string;
}

export interface SlackChatPostMessageResult {
  ok: boolean;
  ts?: string;
  channel?: string;
  error?: string;
  /** Populated from the `Retry-After` header on an HTTP 429 response. */
  retryAfterSeconds?: number;
}

/**
 * Exchanges a Slack OAuth v2 code for an access_token.
 * The returned `access_token` is a bot token (xoxb-...).
 */
export async function exchangeOauthCode(
  code: string,
  redirectUri: string,
  clientId: string,
  clientSecret: string,
): Promise<SlackOauthAccessResult> {
  const response = await fetch(OAUTH_V2_ACCESS, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      code,
      client_id: clientId,
      client_secret: clientSecret,
      redirect_uri: redirectUri,
    }),
    signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
  });
  if (!response.ok) {
    throw new Error(`Slack OAuth exchange failed (${response.status})`);
  }
  return parseJsonResponse<SlackOauthAccessResult>(response);
}

/**
 * Posts a message to a channel via `chat.postMessage`.
 */
export async function postChatMessage(
  botToken: string,
  channel: string,
  text: string,
  options?: { blocks?: unknown[] },
): Promise<SlackChatPostMessageResult> {
  const response = await fetch(CHAT_POST_MESSAGE, {
    method: "POST",
    headers: {
      "Content-Type": "application/json; charset=utf-8",
      Authorization: `Bearer ${botToken}`,
    },
    body: JSON.stringify({ channel, text, blocks: options?.blocks }),
    signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
  });
  if (response.status === 429) {
    // Rate limited: surface the Retry-After hint instead of throwing so the
    // caller can classify and persist it.
    const retryAfter = Number(response.headers.get("retry-after"));
    return {
      ok: false,
      error: "ratelimited",
      ...(Number.isFinite(retryAfter) ? { retryAfterSeconds: retryAfter } : {}),
    };
  }
  if (!response.ok) {
    throw new Error(`Slack chat.postMessage failed (${response.status})`);
  }
  return parseJsonResponse<SlackChatPostMessageResult>(response);
}
