import { ICE_CONFIG_PROTOCOL_VERSION } from "./iceTypes.ts";
import { randomId } from "../ids.ts";
import { isIceConfigGrantActive, parseIceConfigResponse } from "./iceValidation.ts";
import type { ScreenShareBinding, ScreenShareCredential } from "./signalingTypes.ts";

export interface EphemeralIceConfiguration {
  readonly configuration: RTCConfiguration;
  readonly expiresAtMs: number;
}

export interface FetchEphemeralIceConfigurationOptions {
  authenticatedFetch: typeof globalThis.fetch;
  /** Exact server-authored screen and active signaling capability. */
  binding: ScreenShareBinding;
  credential: ScreenShareCredential;
  endpoint?: string;
  now?: () => number;
  requestId?: () => string;
}

/** A validated, correlated server backoff response. No credential is retained. */
export class IceConfigurationUnavailableError extends Error {
  readonly name = "IceConfigurationUnavailableError";
  readonly retryAfterMs: number;
  readonly status: number;

  constructor(retryAfterMs: number, status: number) {
    super("ICE configuration is temporarily unavailable");
    this.retryAfterMs = retryAfterMs;
    this.status = status;
  }
}

/**
 * Fetches one short-lived WebRTC configuration after the caller has established
 * authenticated, explicit media intent. The result is never cached or stored;
 * its lifetime remains visible so the caller can discard it on expiry.
 */
export async function fetchEphemeralIceConfiguration(
  options: FetchEphemeralIceConfigurationOptions,
): Promise<EphemeralIceConfiguration> {
  if (typeof options.authenticatedFetch !== "function") {
    throw new TypeError("ICE configuration: authenticated fetch is required");
  }
  const now = options.now ?? Date.now;
  const id = (options.requestId ?? (() => `tera-ice-${randomId()}`))();
  const response = await options.authenticatedFetch(options.endpoint ?? "/api/v1/media/ice", {
    method: "POST",
    credentials: "same-origin",
    cache: "no-store",
    redirect: "error",
    headers: { Accept: "application/json", "Content-Type": "application/json" },
    body: JSON.stringify({
      type: "ice-config-request",
      protocolVersion: ICE_CONFIG_PROTOCOL_VERSION,
      requestId: id,
      binding: { ...options.binding },
      credential: { ...options.credential },
    }),
  });
  let body: unknown;
  try {
    body = await response.json() as unknown;
  } catch {
    if (!response.ok) throw new Error(`ICE configuration request failed (${response.status})`);
    throw new Error("ICE configuration response is not valid JSON");
  }
  const parsed = parseIceConfigResponse(body);
  if (!parsed.ok) {
    if (!response.ok) throw new Error(`ICE configuration request failed (${response.status})`);
    throw new Error(parsed.error);
  }
  if (parsed.value.requestId !== id) throw new Error("ICE configuration response did not match the request");
  if (parsed.value.type === "ice-config-unavailable") {
    if (response.ok || response.status === 429 || response.status === 503) {
      throw new IceConfigurationUnavailableError(parsed.value.retryAfterMs, response.status);
    }
    throw new Error(`ICE configuration request failed (${response.status})`);
  }
  if (!response.ok) throw new Error(`ICE configuration request failed (${response.status})`);
  if (!isIceConfigGrantActive(parsed.value, now())) {
    throw new Error("ICE configuration grant is not active");
  }
  return {
    configuration: {
      iceServers: parsed.value.iceServers.map((server) => "username" in server
        ? {
            urls: [...server.urls],
            username: server.username,
            credential: server.credential,
            credentialType: server.credentialType,
          }
        : { urls: [...server.urls] }),
    },
    expiresAtMs: parsed.value.expiresAtMs,
  };
}
