import {
  ADDRESS_CANDIDATES_CACHE_TTL_SECONDS,
  L1_TTL_SECONDS,
  L2_TTL_BASE_SECONDS,
  L2_TTL_JITTER_MAX_SECONDS,
  L3_TTL_SECONDS,
  LOCK_TTL_SECONDS,
  LOCK_WAIT_MS,
} from "@/lib/constants/cache";
import { getRedis } from "@/lib/redis";

export const cacheKeys = {
  l1: (requestId: string) => `idem:${requestId}`,
  l2: (cargoHash: string) => `quote:${cargoHash}`,
  l3: (cargoHash: string) => `stale:${cargoHash}`,
  lock: (cargoHash: string) => `lock:quote:${cargoHash}`,
  addrCand: (hash: string) => `addr-cand:${hash}`,
};

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function l2TtlWithJitter(): number {
  const jitter = Math.floor(Math.random() * (L2_TTL_JITTER_MAX_SECONDS + 1));
  return L2_TTL_BASE_SECONDS + jitter;
}

export async function getL1<T = unknown>(
  requestId: string,
): Promise<T | null> {
  const raw = await getRedis().get(cacheKeys.l1(requestId));
  if (!raw) {
    return null;
  }
  return JSON.parse(raw) as T;
}

/** SETNX + TTL；已存在时不覆盖，返回 false */
export async function setL1(
  requestId: string,
  data: unknown,
): Promise<boolean> {
  const key = cacheKeys.l1(requestId);
  const value = JSON.stringify(data);
  const result = await getRedis().set(key, value, "EX", L1_TTL_SECONDS, "NX");
  return result === "OK";
}

/** 覆盖写入 L1（RPA 完成后更新幂等响应） */
export async function overwriteL1(
  requestId: string,
  data: unknown,
): Promise<void> {
  await getRedis().set(
    cacheKeys.l1(requestId),
    JSON.stringify(data),
    "EX",
    L1_TTL_SECONDS,
  );
}

export async function getL2<T = unknown>(
  cargoHash: string,
): Promise<T | null> {
  const raw = await getRedis().get(cacheKeys.l2(cargoHash));
  if (!raw) {
    return null;
  }
  return JSON.parse(raw) as T;
}

export async function setL2(
  cargoHash: string,
  data: unknown,
): Promise<number> {
  const ttl = l2TtlWithJitter();
  await getRedis().set(
    cacheKeys.l2(cargoHash),
    JSON.stringify(data),
    "EX",
    ttl,
  );
  return ttl;
}

export async function getL3<T = unknown>(
  cargoHash: string,
): Promise<T | null> {
  const raw = await getRedis().get(cacheKeys.l3(cargoHash));
  if (!raw) {
    return null;
  }
  return JSON.parse(raw) as T;
}

/** 仅 RPA 成功时写入；正常路径禁止读取 L3 */
export async function setL3(
  cargoHash: string,
  data: unknown,
): Promise<void> {
  await getRedis().set(
    cacheKeys.l3(cargoHash),
    JSON.stringify(data),
    "EX",
    L3_TTL_SECONDS,
  );
}

/** SETNX 获取击穿锁，TTL 5s */
export async function acquireLock(cargoHash: string): Promise<boolean> {
  const result = await getRedis().set(
    cacheKeys.lock(cargoHash),
    "1",
    "EX",
    LOCK_TTL_SECONDS,
    "NX",
  );
  return result === "OK";
}

export async function releaseLock(cargoHash: string): Promise<void> {
  await getRedis().del(cacheKeys.lock(cargoHash));
}

/** 未获锁时等待 2s 后读 L2 */
export async function waitAndGetL2<T = unknown>(
  cargoHash: string,
): Promise<T | null> {
  await sleep(LOCK_WAIT_MS);
  return getL2<T>(cargoHash);
}

export async function getAddressCandidatesCache<T = unknown>(
  hash: string,
): Promise<T | null> {
  const raw = await getRedis().get(cacheKeys.addrCand(hash));
  if (!raw) {
    return null;
  }
  return JSON.parse(raw) as T;
}

/** 地址联想成功结果短缓存（默认 120s） */
export async function setAddressCandidatesCache(
  hash: string,
  data: unknown,
): Promise<void> {
  await getRedis().set(
    cacheKeys.addrCand(hash),
    JSON.stringify(data),
    "EX",
    ADDRESS_CANDIDATES_CACHE_TTL_SECONDS,
  );
}
