import { createHash } from "crypto";
import {
  getApiKeys,
  getCombos,
  getModelAliases,
  getProviderConnections,
  getProviderNodes,
  getSettings,
} from "@/lib/localDb";

type JsonRecord = Record<string, unknown>;

export interface ConfigSyncBundle {
  settings: JsonRecord;
  providerConnections: JsonRecord[];
  providerNodes: JsonRecord[];
  modelAliases: JsonRecord;
  combos: JsonRecord[];
  apiKeys: JsonRecord[];
}

function asRecord(value: unknown): JsonRecord {
  return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}

function sanitizeSettingsForSync(settings: unknown): JsonRecord {
  const record = asRecord(settings);
  const {
    password: _password,
    requireLogin: _requireLogin,
    cloudEnabled: _cloudEnabled,
    ...safeSettings
  } = record;
  return safeSettings;
}

function sortByStringKeys<T extends JsonRecord>(items: T[], keys: string[]) {
  return [...items].sort((a, b) => {
    for (const key of keys) {
      const leftRaw = a[key];
      const rightRaw = b[key];

      if (typeof leftRaw === "number" || typeof rightRaw === "number") {
        const left = typeof leftRaw === "number" ? leftRaw : Number.MAX_SAFE_INTEGER;
        const right = typeof rightRaw === "number" ? rightRaw : Number.MAX_SAFE_INTEGER;
        if (left !== right) return left - right;
        continue;
      }

      const left = typeof leftRaw === "string" ? String(leftRaw) : "";
      const right = typeof rightRaw === "string" ? String(rightRaw) : "";
      const comparison = left.localeCompare(right, undefined, { numeric: true });
      if (comparison !== 0) return comparison;
    }
    return 0;
  });
}

function pickDefined(record: JsonRecord, keys: string[]) {
  return Object.fromEntries(
    keys.filter((key) => record[key] !== undefined).map((key) => [key, record[key]])
  );
}

function sanitizeProviderConnectionForSync(connection: unknown): JsonRecord {
  const record = asRecord(connection);
  return pickDefined(record, [
    "id",
    "provider",
    "authType",
    "name",
    "displayName",
    "email",
    "priority",
    "globalPriority",
    "defaultModel",
    "isActive",
    "accessToken",
    "refreshToken",
    "expiresAt",
    "expiresIn",
    "tokenType",
    "scope",
    "idToken",
    "projectId",
    "apiKey",
    "providerSpecificData",
    "group",
  ]);
}

function sanitizeProviderNodeForSync(node: unknown): JsonRecord {
  const record = asRecord(node);
  return pickDefined(record, [
    "id",
    "type",
    "name",
    "prefix",
    "apiType",
    "baseUrl",
    "chatPath",
    "modelsPath",
  ]);
}

function sanitizeComboForSync(combo: unknown): JsonRecord {
  const record = asRecord(combo);
  const { createdAt: _createdAt, updatedAt: _updatedAt, ...rest } = record;
  return rest;
}

function sanitizeApiKeyForSync(apiKey: unknown): JsonRecord {
  const record = asRecord(apiKey);
  return pickDefined(record, [
    "id",
    "name",
    "key",
    "machineId",
    "allowedModels",
    "allowedCombos",
    "allowedConnections",
    "noLog",
    "autoResolve",
    "isActive",
    "accessSchedule",
    "maxRequestsPerDay",
    "maxRequestsPerMinute",
    "throttleDelayMs",
    "maxSessions",
  ]);
}

function canonicalizeJson(value: unknown): unknown {
  if (Array.isArray(value)) {
    return value.map((entry) => canonicalizeJson(entry));
  }

  if (value && typeof value === "object") {
    return Object.fromEntries(
      Object.keys(value as JsonRecord)
        .sort((a, b) => a.localeCompare(b))
        .map((key) => [key, canonicalizeJson((value as JsonRecord)[key])])
    );
  }

  return value;
}

export function serializeStableJson(value: unknown) {
  return JSON.stringify(canonicalizeJson(value));
}

export function computeConfigSyncVersion(bundle: ConfigSyncBundle) {
  return createHash("sha256").update(serializeStableJson(bundle)).digest("hex");
}

export async function buildConfigSyncBundle(): Promise<ConfigSyncBundle> {
  const [settings, providerConnections, providerNodes, modelAliases, combos, apiKeys] =
    await Promise.all([
      getSettings(),
      getProviderConnections(),
      getProviderNodes(),
      getModelAliases(),
      getCombos(),
      getApiKeys(),
    ]);

  return {
    settings: sanitizeSettingsForSync(settings),
    providerConnections: sortByStringKeys(
      providerConnections.map((connection) => sanitizeProviderConnectionForSync(connection)),
      ["provider", "name", "id"]
    ),
    providerNodes: sortByStringKeys(
      providerNodes.map((node) => sanitizeProviderNodeForSync(node)),
      ["type", "name", "id"]
    ),
    modelAliases: asRecord(modelAliases),
    combos: sortByStringKeys(
      combos.map((combo) => sanitizeComboForSync(combo)),
      ["sortOrder", "name", "id"]
    ),
    apiKeys: sortByStringKeys(
      apiKeys.map((apiKey) => sanitizeApiKeyForSync(apiKey)),
      ["name", "id"]
    ),
  };
}

export async function buildConfigSyncEnvelope() {
  const bundle = await buildConfigSyncBundle();
  const version = computeConfigSyncVersion(bundle);
  return {
    version,
    bundle,
  };
}

export function toLegacyCloudSyncPayload(bundle: ConfigSyncBundle) {
  return {
    providers: bundle.providerConnections,
    providerNodes: bundle.providerNodes,
    modelAliases: bundle.modelAliases,
    combos: bundle.combos,
    apiKeys: bundle.apiKeys,
    settings: bundle.settings,
  };
}
