/**
 * Typed client for the Rust management API.
 *
 * Production uses a same-origin base so the embedded Console follows the
 * address used to open it. Vite development keeps the existing local backend
 * default and relies on Wasmeld's development CORS policy.
 */

export const DEFAULT_API_BASE =
  import.meta.env.VITE_WASMELD_API_URL ?? (import.meta.env.DEV ? "http://127.0.0.1:8080" : "");

const API_BASE_STORAGE_KEY = "wasmeld-api-base";

export type BackendServiceStatus = "running" | "stopped" | "faulted";

export type BackendCapability = {
  interface: string;
  package: string;
  name: string;
  version: string;
};

export type BackendService = {
  id: string;
  revision: string;
  artifact: string;
  world: string;
  capabilities: BackendCapability[];
  status: BackendServiceStatus;
  updated_at_ms: number;
  limits: {
    memory_bytes: number;
    fuel_per_call: number;
    deadline_ms: number;
    mailbox_capacity: number;
    max_input_bytes: number;
    max_output_bytes: number;
  };
  calls: number;
  errors: number;
  last_latency_ms: number | null;
};

export type BackendEvent = {
  id: number;
  timestamp_ms: number;
  kind: "registered" | "unregistered" | "deployed" | "started" | "stopped" | "invoked" | "failed";
  service_id: string | null;
  revision: string | null;
  message: string;
};

export type BackendRuntime = {
  status: "running" | "stopped";
  uptime_ms: number;
  managed_services: number;
  registered_services: number;
  running_services: number;
};

export type BackendDeployment = {
  service_id: string;
  active_revision: string;
  status: BackendServiceStatus;
  updated_at_ms: number;
};

export type BackendWitDependency = {
  name: string;
  version: string;
};

export type BackendWitPackage = {
  schema_version: number;
  name: string;
  version: string;
  sha256: string;
  dependencies: BackendWitDependency[];
};

export type BackendSnapshot = {
  services: BackendService[];
  deployments: BackendDeployment[];
  events: BackendEvent[];
  runtime: BackendRuntime;
  witPackages: BackendWitPackage[];
};

export type InvokeResult = {
  output: Uint8Array;
  outputBytes: number;
  latencyMs: number;
};

export function getApiBase(): string {
  if (typeof window === "undefined") return DEFAULT_API_BASE;
  return window.localStorage.getItem(API_BASE_STORAGE_KEY) ?? DEFAULT_API_BASE;
}

export function displayApiBase(value: string): string {
  return value || (typeof window === "undefined" ? "same origin" : window.location.origin);
}

export function saveApiBase(value: string): string {
  const normalized = value.trim().replace(/\/+$/, "");
  if (!normalized) {
    window.localStorage.removeItem(API_BASE_STORAGE_KEY);
    return "";
  }
  const parsed = new URL(normalized);
  if (!["http:", "https:"].includes(parsed.protocol)) {
    throw new Error("API 地址必须使用 http 或 https");
  }
  window.localStorage.setItem(API_BASE_STORAGE_KEY, normalized);
  return normalized;
}

export async function fetchSnapshot(apiBase: string): Promise<BackendSnapshot> {
  const [services, deployments, events, runtime, witPackages] = await Promise.all([
    request<{ services: BackendService[] }>(apiBase, "/api/v1/services"),
    request<{ deployments: BackendDeployment[] }>(apiBase, "/api/v1/deployments"),
    request<{ events: BackendEvent[] }>(apiBase, "/api/v1/events"),
    request<BackendRuntime>(apiBase, "/api/v1/runtime"),
    request<{ packages: BackendWitPackage[] }>(apiBase, "/api/v1/wit/packages"),
  ]);
  return {
    services: services.services,
    deployments: deployments.deployments,
    events: events.events,
    runtime,
    witPackages: witPackages.packages,
  };
}

export function activateDeployment(
  apiBase: string,
  service: Pick<BackendService, "id" | "revision">,
): Promise<BackendDeployment> {
  return request(apiBase, `/api/v1/deployments/${segment(service.id)}/activate`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ revision: service.revision }),
  });
}

export function registerComponent(apiBase: string, file: File): Promise<BackendService> {
  const form = new FormData();
  form.set("package", file, file.name);
  return request(apiBase, "/api/v1/services", {
    method: "POST",
    body: form,
  });
}

export function publishWitPackage(apiBase: string, file: File): Promise<BackendWitPackage> {
  const form = new FormData();
  form.set("package", file, file.name);
  return request(apiBase, "/api/v1/wit/packages", {
    method: "POST",
    body: form,
  });
}

export function witPackageDownloadUrl(
  apiBase: string,
  packageMetadata: Pick<BackendWitPackage, "name" | "version">,
): string {
  const [namespace, name] = packageMetadata.name.split(":");
  return `${apiBase}/api/v1/wit/packages/${segment(namespace)}/${segment(name)}/${segment(packageMetadata.version)}/content`;
}

export function changeServiceState(
  apiBase: string,
  service: Pick<BackendService, "id" | "revision">,
  action: "start" | "stop" | "restart",
): Promise<BackendService> {
  return request(
    apiBase,
    `/api/v1/services/${segment(service.id)}/${segment(service.revision)}/${action}`,
    {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: "{}",
    },
  );
}

export function changeRuntimeState(
  apiBase: string,
  action: "start" | "stop" | "restart",
): Promise<BackendRuntime> {
  return request(apiBase, `/api/v1/runtime/${action}`, { method: "POST" });
}

export async function invokeComponent(
  apiBase: string,
  service: Pick<BackendService, "id" | "revision">,
  input: Uint8Array,
): Promise<InvokeResult> {
  const response = await request<{
    output_base64: string;
    output_bytes: number;
    latency_ms: number;
  }>(apiBase, `/api/v1/services/${segment(service.id)}/${segment(service.revision)}/invoke`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ input_base64: bytesToBase64(input) }),
  });
  return {
    output: base64ToBytes(response.output_base64),
    outputBytes: response.output_bytes,
    latencyMs: response.latency_ms,
  };
}

async function request<T>(apiBase: string, path: string, init?: RequestInit): Promise<T> {
  let response: Response;
  try {
    response = await fetch(`${apiBase}${path}`, {
      ...init,
      headers: {
        accept: "application/json",
        ...init?.headers,
      },
    });
  } catch {
    throw new Error(`无法连接 Wasmeld：${displayApiBase(apiBase)}`);
  }

  if (!response.ok) {
    const body = (await response.json().catch(() => null)) as {
      error?: { message?: string };
    } | null;
    throw new Error(body?.error?.message ?? `请求失败：HTTP ${response.status}`);
  }
  return (await response.json()) as T;
}

function segment(value: string): string {
  return encodeURIComponent(value);
}

function bytesToBase64(bytes: Uint8Array): string {
  let binary = "";
  for (const byte of bytes) binary += String.fromCharCode(byte);
  return window.btoa(binary);
}

function base64ToBytes(value: string): Uint8Array {
  const binary = window.atob(value);
  return Uint8Array.from(binary, (character) => character.charCodeAt(0));
}
