const BASE_URL =
  process.env.NEXT_PUBLIC_ATHENA_BASE_URL ?? "https://athena-db.com";
const DEFAULT_DB_NAME = process.env.NEXT_PUBLIC_ATHENA_DB_NAME ?? "postgres";
const DEFAULT_CLIENT = process.env.NEXT_PUBLIC_ATHENA_CLIENT ?? "supabase";

export type TableMetadata = {
  table_name: string;
  table_schema?: string;
};

export type ColumnMetadata = {
  column_name: string;
  data_type?: string | null;
  column_default?: string | null;
  is_nullable?: string;
};

export type RowValues = Record<string, unknown>;

export type ManagementColumnRequest = {
  name: string;
  data_type: string;
  nullable?: boolean;
  default_expression?: string | null;
};

export type CreateTableRequest = {
  schema_name?: string;
  table_name: string;
  columns: ManagementColumnRequest[];
  if_not_exists?: boolean;
};

export type EditTableOperation =
  | { type: "add_column"; column: ManagementColumnRequest }
  | { type: "rename_column"; from: string; to: string }
  | { type: "set_default"; column_name: string; default_expression: string }
  | { type: "drop_default"; column_name: string }
  | { type: "set_not_null"; column_name: string }
  | { type: "drop_not_null"; column_name: string };

export type EditTableRequest = {
  schema_name?: string;
  operations: EditTableOperation[];
};

export type AthenaClientRecord = {
  id?: string;
  client_name: string;
  description?: string | null;
  pg_uri?: string | null;
  pg_uri_env_var?: string | null;
  source?: string;
  is_active?: boolean;
  is_frozen?: boolean;
  last_seen_at?: string | null;
  created_at?: string;
  updated_at?: string;
};

export type RuntimeAthenaClient = {
  client_name: string;
  source?: string;
  description?: string | null;
  is_active?: boolean;
  is_frozen?: boolean;
  pool_connected?: boolean;
};

export type ApiKeyRecord = {
  id: string;
  public_id: string;
  name: string;
  description?: string | null;
  client_name?: string | null;
  expires_at?: string | null;
  is_active: boolean;
  last_used_at?: string | null;
  created_at?: string;
  updated_at?: string;
  rights: string[];
};

export type ApiKeyRightRecord = {
  id: string;
  name: string;
  description?: string | null;
  created_at?: string;
  updated_at?: string;
};

export type ApiKeyClientConfig = {
  client_name: string;
  enforce_api_keys: boolean;
  created_at?: string;
  updated_at?: string;
};

type ApiOptions = {
  clientName?: string;
  adminApiKey?: string;
  gatewayApiKey?: string;
  managementApiKey?: string;
};

function normalizeEnvelope<T>(payload: unknown): T {
  if (payload && typeof payload === "object" && "data" in payload) {
    return (payload as { data: T }).data;
  }
  return payload as T;
}

async function requestJson<T>(
  path: string,
  init: RequestInit,
  defaultError: string,
): Promise<T> {
  const response = await fetch(`${BASE_URL}${path}`, init);
  const payload = await response.json().catch(() => null);
  if (!response.ok) {
    const message =
      (payload && typeof payload === "object" && "error" in payload
        ? (payload as { error?: string }).error
        : null) ??
      (payload && typeof payload === "object" && "message" in payload
        ? (payload as { message?: string }).message
        : null) ??
      defaultError;
    throw new Error(message);
  }
  return normalizeEnvelope<T>(payload);
}

function buildHeaders(input: {
  clientName?: string;
  adminApiKey?: string;
  gatewayApiKey?: string;
  managementApiKey?: string;
  extra?: Record<string, string>;
}) {
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
  };

  if (input.clientName) {
    headers["X-Athena-Client"] = input.clientName;
  }
  const apiKey = input.managementApiKey || input.gatewayApiKey || input.adminApiKey;
  if (apiKey) {
    headers.apikey = apiKey;
    headers["x-api-key"] = apiKey;
    headers.Authorization = `Bearer ${apiKey}`;
  }
  for (const [key, value] of Object.entries(input.extra ?? {})) {
    if (value.trim()) {
      headers[key] = value;
    }
  }
  return headers;
}

export async function fetchSchemaClients(adminApiKey?: string): Promise<string[]> {
  const data = await requestJson<{ clients?: string[] }>(
    "/schema/clients",
    {
      method: "GET",
      headers: buildHeaders({ clientName: DEFAULT_CLIENT, adminApiKey }),
    },
    "Failed to fetch schema clients",
  );
  return Array.isArray(data.clients) ? data.clients : [];
}

export async function fetchTables(
  clientName = DEFAULT_CLIENT,
  adminApiKey?: string,
): Promise<TableMetadata[]> {
  const data = await requestJson<{ tables?: TableMetadata[] }>(
    "/schema/tables",
    {
      method: "GET",
      headers: buildHeaders({ clientName, adminApiKey }),
    },
    "Failed to fetch tables",
  );
  return Array.isArray(data.tables) ? data.tables : [];
}

export async function fetchColumns(
  tableName: string,
  clientName = DEFAULT_CLIENT,
  adminApiKey?: string,
): Promise<ColumnMetadata[]> {
  const data = await requestJson<{ columns?: ColumnMetadata[] }>(
    `/schema/columns?table_name=${encodeURIComponent(tableName)}`,
    {
      method: "GET",
      headers: buildHeaders({ clientName, adminApiKey }),
    },
    "Failed to fetch columns",
  );
  return Array.isArray(data.columns) ? data.columns : [];
}

export async function fetchRows(
  tableName: string,
  clientName = DEFAULT_CLIENT,
  columns: string[] = ["*"],
  gatewayApiKey?: string,
): Promise<RowValues[]> {
  const data = await requestJson<{ data?: RowValues[]; rows?: RowValues[] }>(
    "/gateway/fetch",
    {
      method: "POST",
      headers: buildHeaders({ clientName, gatewayApiKey }),
      body: JSON.stringify({ table_name: tableName, columns }),
    },
    "Failed to fetch rows",
  );
  if (Array.isArray(data.data)) return data.data;
  if (Array.isArray(data.rows)) return data.rows;
  return [];
}

export async function insertRow(
  tableName: string,
  insertBody: RowValues,
  options: ApiOptions,
): Promise<void> {
  await requestJson(
    "/gateway/insert",
    {
      method: "PUT",
      headers: buildHeaders({
        clientName: options.clientName,
        gatewayApiKey: options.gatewayApiKey,
      }),
      body: JSON.stringify({ table_name: tableName, insert_body: insertBody }),
    },
    "Failed to insert row",
  );
}

export async function updateRows(
  tableName: string,
  conditionColumn: string,
  conditionValue: unknown,
  data: RowValues,
  options: ApiOptions,
): Promise<RowValues[]> {
  const result = await requestJson<{ data?: RowValues[] }>(
    "/gateway/update",
    {
      method: "POST",
      headers: buildHeaders({
        clientName: options.clientName,
        gatewayApiKey: options.gatewayApiKey,
      }),
      body: JSON.stringify({
        table_name: tableName,
        conditions: [{ eq_column: conditionColumn, eq_value: conditionValue }],
        data,
      }),
    },
    "Failed to update row",
  );
  return Array.isArray(result.data) ? result.data : [];
}

export async function deleteRow(
  tableName: string,
  resourceId: string,
  options: ApiOptions,
): Promise<void> {
  await requestJson(
    "/gateway/delete",
    {
      method: "DELETE",
      headers: buildHeaders({
        clientName: options.clientName,
        gatewayApiKey: options.gatewayApiKey,
      }),
      body: JSON.stringify({ table_name: tableName, resource_id: resourceId }),
    },
    "Failed to delete row",
  );
}

export async function runSql(
  query: string,
  options: {
    clientName?: string;
    driver?: "athena" | "postgresql" | "supabase";
    dbName?: string;
    gatewayApiKey?: string;
  },
): Promise<unknown> {
  const result = await requestJson<unknown>(
    "/query/sql",
    {
      method: "POST",
      headers: buildHeaders({
        clientName: options.clientName,
        gatewayApiKey: options.gatewayApiKey,
      }),
      body: JSON.stringify({
        query,
        driver: options.driver ?? "supabase",
        db_name: options.dbName ?? DEFAULT_DB_NAME,
      }),
    },
    "Failed to run SQL",
  );
  return result;
}

export async function createManagedTable(
  body: CreateTableRequest,
  options: ApiOptions,
): Promise<void> {
  await requestJson(
    "/management/tables",
    {
      method: "POST",
      headers: buildHeaders({
        clientName: options.clientName,
        managementApiKey: options.managementApiKey,
      }),
      body: JSON.stringify(body),
    },
    "Failed to create table",
  );
}

export async function editManagedTable(
  tableName: string,
  body: EditTableRequest,
  options: ApiOptions,
): Promise<void> {
  await requestJson(
    `/management/tables/${encodeURIComponent(tableName)}`,
    {
      method: "PATCH",
      headers: buildHeaders({
        clientName: options.clientName,
        managementApiKey: options.managementApiKey,
      }),
      body: JSON.stringify(body),
    },
    "Failed to edit table",
  );
}

export async function dropManagedTable(
  tableName: string,
  schemaName: string,
  cascade: boolean,
  options: ApiOptions,
): Promise<void> {
  await requestJson(
    `/management/tables/${encodeURIComponent(tableName)}`,
    {
      method: "DELETE",
      headers: buildHeaders({
        clientName: options.clientName,
        managementApiKey: options.managementApiKey,
      }),
      body: JSON.stringify({ schema_name: schemaName, cascade }),
    },
    "Failed to drop table",
  );
}

export async function createManagedIndex(
  body: {
    schema_name?: string;
    table_name: string;
    index_name?: string;
    columns: string[];
    unique?: boolean;
    method?: string;
  },
  options: ApiOptions,
): Promise<void> {
  await requestJson(
    "/management/indexes",
    {
      method: "POST",
      headers: buildHeaders({
        clientName: options.clientName,
        managementApiKey: options.managementApiKey,
      }),
      body: JSON.stringify(body),
    },
    "Failed to create index",
  );
}

export async function dropManagedIndex(
  indexName: string,
  schemaName: string,
  options: ApiOptions,
): Promise<void> {
  await requestJson(
    `/management/indexes/${encodeURIComponent(indexName)}`,
    {
      method: "DELETE",
      headers: buildHeaders({
        clientName: options.clientName,
        managementApiKey: options.managementApiKey,
      }),
      body: JSON.stringify({ schema_name: schemaName }),
    },
    "Failed to drop index",
  );
}

export async function listAthenaClients(adminApiKey: string): Promise<{
  clients: AthenaClientRecord[];
  runtime: RuntimeAthenaClient[];
}> {
  const data = await requestJson<{
    clients?: AthenaClientRecord[];
    runtime?: RuntimeAthenaClient[];
  }>(
    "/admin/clients",
    {
      method: "GET",
      headers: buildHeaders({ adminApiKey }),
    },
    "Failed to list Athena clients",
  );
  return {
    clients: Array.isArray(data.clients) ? data.clients : [],
    runtime: Array.isArray(data.runtime) ? data.runtime : [],
  };
}

export async function createAthenaClient(
  body: {
    client_name: string;
    description?: string;
    pg_uri?: string;
    pg_uri_env_var?: string;
    is_active?: boolean;
  },
  adminApiKey: string,
): Promise<void> {
  await requestJson(
    "/admin/clients",
    {
      method: "POST",
      headers: buildHeaders({ adminApiKey }),
      body: JSON.stringify(body),
    },
    "Failed to create Athena client",
  );
}

export async function updateAthenaClient(
  clientName: string,
  body: {
    description?: string;
    pg_uri?: string;
    pg_uri_env_var?: string;
    is_active?: boolean;
  },
  adminApiKey: string,
): Promise<void> {
  await requestJson(
    `/admin/clients/${encodeURIComponent(clientName)}`,
    {
      method: "PATCH",
      headers: buildHeaders({ adminApiKey }),
      body: JSON.stringify(body),
    },
    "Failed to update Athena client",
  );
}

export async function freezeAthenaClient(
  clientName: string,
  isFrozen: boolean,
  adminApiKey: string,
): Promise<void> {
  await requestJson(
    `/admin/clients/${encodeURIComponent(clientName)}/freeze`,
    {
      method: "PUT",
      headers: buildHeaders({ adminApiKey }),
      body: JSON.stringify({ is_frozen: isFrozen }),
    },
    "Failed to update freeze state",
  );
}

export async function deleteAthenaClient(
  clientName: string,
  adminApiKey: string,
): Promise<void> {
  await requestJson(
    `/admin/clients/${encodeURIComponent(clientName)}`,
    {
      method: "DELETE",
      headers: buildHeaders({ adminApiKey }),
    },
    "Failed to delete Athena client",
  );
}

export async function listApiKeys(adminApiKey: string): Promise<ApiKeyRecord[]> {
  const data = await requestJson<{ api_keys?: ApiKeyRecord[] }>(
    "/admin/api-keys",
    {
      method: "GET",
      headers: buildHeaders({ adminApiKey }),
    },
    "Failed to list API keys",
  );
  return Array.isArray(data.api_keys) ? data.api_keys : [];
}

export async function listApiKeyRights(
  adminApiKey: string,
): Promise<ApiKeyRightRecord[]> {
  const data = await requestJson<{ rights?: ApiKeyRightRecord[] }>(
    "/admin/api-key-rights",
    {
      method: "GET",
      headers: buildHeaders({ adminApiKey }),
    },
    "Failed to list API key rights",
  );
  return Array.isArray(data.rights) ? data.rights : [];
}

export async function createApiKey(
  body: {
    name: string;
    description?: string;
    client_name?: string;
    expires_at?: string;
    rights?: string[];
  },
  adminApiKey: string,
): Promise<{ api_key?: string; record?: ApiKeyRecord }> {
  return requestJson<{ api_key?: string; record?: ApiKeyRecord }>(
    "/admin/api-keys",
    {
      method: "POST",
      headers: buildHeaders({ adminApiKey }),
      body: JSON.stringify(body),
    },
    "Failed to create API key",
  );
}

export async function updateApiKey(
  id: string,
  body: {
    name?: string;
    description?: string | null;
    client_name?: string | null;
    expires_at?: string | null;
    is_active?: boolean;
    rights?: string[];
  },
  adminApiKey: string,
): Promise<void> {
  await requestJson(
    `/admin/api-keys/${encodeURIComponent(id)}`,
    {
      method: "PATCH",
      headers: buildHeaders({ adminApiKey }),
      body: JSON.stringify(body),
    },
    "Failed to update API key",
  );
}

export async function deleteApiKey(id: string, adminApiKey: string): Promise<void> {
  await requestJson(
    `/admin/api-keys/${encodeURIComponent(id)}`,
    {
      method: "DELETE",
      headers: buildHeaders({ adminApiKey }),
    },
    "Failed to delete API key",
  );
}

export async function createApiKeyRight(
  name: string,
  description: string,
  adminApiKey: string,
): Promise<void> {
  await requestJson(
    "/admin/api-key-rights",
    {
      method: "POST",
      headers: buildHeaders({ adminApiKey }),
      body: JSON.stringify({ name, description }),
    },
    "Failed to create API key right",
  );
}

export async function deleteApiKeyRight(
  id: string,
  adminApiKey: string,
): Promise<void> {
  await requestJson(
    `/admin/api-key-rights/${encodeURIComponent(id)}`,
    {
      method: "DELETE",
      headers: buildHeaders({ adminApiKey }),
    },
    "Failed to delete API key right",
  );
}

export async function loadApiKeyConfig(adminApiKey: string): Promise<{
  global: { enforce_api_keys: boolean } | null;
  clients: ApiKeyClientConfig[];
}> {
  const data = await requestJson<{
    global?: { enforce_api_keys: boolean };
    clients?: ApiKeyClientConfig[];
  }>(
    "/admin/api-key-config",
    {
      method: "GET",
      headers: buildHeaders({ adminApiKey }),
    },
    "Failed to load API key config",
  );
  return {
    global: data.global ?? null,
    clients: Array.isArray(data.clients) ? data.clients : [],
  };
}

export async function setGlobalApiKeyConfig(
  enforceApiKeys: boolean,
  adminApiKey: string,
): Promise<void> {
  await requestJson(
    "/admin/api-key-config",
    {
      method: "PUT",
      headers: buildHeaders({ adminApiKey }),
      body: JSON.stringify({ enforce_api_keys: enforceApiKeys }),
    },
    "Failed to update API key config",
  );
}

export async function setClientApiKeyConfig(
  clientName: string,
  enforceApiKeys: boolean,
  adminApiKey: string,
): Promise<void> {
  await requestJson(
    `/admin/api-key-clients/${encodeURIComponent(clientName)}`,
    {
      method: "PUT",
      headers: buildHeaders({ adminApiKey }),
      body: JSON.stringify({ enforce_api_keys: enforceApiKeys }),
    },
    "Failed to update API key client config",
  );
}

export async function deleteClientApiKeyConfig(
  clientName: string,
  adminApiKey: string,
): Promise<void> {
  await requestJson(
    `/admin/api-key-clients/${encodeURIComponent(clientName)}`,
    {
      method: "DELETE",
      headers: buildHeaders({ adminApiKey }),
    },
    "Failed to delete API key client config",
  );
}
