import { randomUUID } from "node:crypto";
import { requestCodexAppServer } from "./codex-runtime.js";

export function publicRateLimits(response, now = Date.now()) {
  const source = response?.rateLimits ?? {};
  const windows = classifyRateLimitWindows(source, now);
  const resetCreditSource = response?.rateLimitResetCredits;
  return {
    primary: publicWindow(windows.primary),
    secondary: publicWindow(windows.secondary),
    resetCreditsAvailable: Number.isSafeInteger(resetCreditSource?.availableCount)
      ? Math.max(0, resetCreditSource.availableCount)
      : null,
    ...(resetCreditSource ? { resetCredits: publicResetCredits(resetCreditSource.credits) } : {})
  };
}

function publicResetCredits(credits) {
  if (!Array.isArray(credits)) return null;
  return credits.flatMap((credit) => {
    if (!credit || typeof credit !== "object") return [];
    return [{
      resetType: safeMetadataValue(credit.resetType),
      status: safeMetadataValue(credit.status),
      grantedAt: publicEpochSeconds(credit.grantedAt),
      expiresAt: publicEpochSeconds(credit.expiresAt)
    }];
  });
}

function safeMetadataValue(value) {
  return typeof value === "string" && value.length <= 64 && /^[a-zA-Z0-9_-]+$/.test(value) ? value : null;
}

function publicEpochSeconds(value) {
  if (!Number.isFinite(value)) return null;
  const date = new Date(value * 1000);
  return Number.isFinite(date.getTime()) ? date.toISOString() : null;
}

function classifyRateLimitWindows(source, now) {
  const entries = [source.primary, source.secondary].filter(Boolean);
  if (!entries.some((window) => Number.isFinite(window.windowDurationMins))) {
    if (source.primary && !source.secondary && Number.isFinite(source.primary.resetsAt)
      && source.primary.resetsAt * 1000 - now > 24 * 60 * 60 * 1000) {
      return { primary: null, secondary: source.primary };
    }
    return { primary: source.primary, secondary: source.secondary };
  }

  const classified = { primary: null, secondary: null };
  for (const window of entries) {
    if (!Number.isFinite(window.windowDurationMins)) continue;
    classified[window.windowDurationMins <= 24 * 60 ? "primary" : "secondary"] = window;
  }
  return classified;
}

function publicWindow(window) {
  if (!window) return null;
  const remainingPercent = Number.isFinite(window.remainingPercent)
    ? window.remainingPercent
    : Number.isFinite(window.usedPercent)
      ? 100 - window.usedPercent
      : null;
  if (!Number.isFinite(remainingPercent)) return null;
  return {
    remainingPercent: Math.max(0, Math.min(100, remainingPercent)),
    resetsAt: Number.isFinite(window.resetsAt) ? new Date(window.resetsAt * 1000).toISOString() : null
  };
}

export function publicTokenUsage(tokenUsage) {
  const totalTokens = tokenUsage?.last?.totalTokens ?? tokenUsage?.total?.totalTokens;
  const contextWindow = tokenUsage?.modelContextWindow;
  if (!Number.isFinite(totalTokens) || !Number.isFinite(contextWindow) || contextWindow <= 0) return null;
  const usedPercent = Math.min(100, Math.round(totalTokens / contextWindow * 100));
  return { totalTokens, contextWindow, usedPercent, remainingPercent: Math.max(0, 100 - usedPercent) };
}

export function publicAccountTokenUsage(response) {
  const summary = response?.summary ?? {};
  return {
    dailyUsageBuckets: Array.isArray(response?.dailyUsageBuckets)
      ? response.dailyUsageBuckets.flatMap((bucket) => {
        if (!/^\d{4}-\d{2}-\d{2}$/.test(bucket?.startDate ?? "") || !Number.isSafeInteger(bucket?.tokens) || bucket.tokens < 0) return [];
        return [{ startDate: bucket.startDate, tokens: bucket.tokens }];
      })
      : [],
    summary: {
      lifetimeTokens: publicCount(summary.lifetimeTokens),
      peakDailyTokens: publicCount(summary.peakDailyTokens),
      currentStreakDays: publicCount(summary.currentStreakDays),
      longestStreakDays: publicCount(summary.longestStreakDays),
      longestRunningTurnSec: publicCount(summary.longestRunningTurnSec)
    }
  };
}

function publicCount(value) {
  return Number.isSafeInteger(value) && value >= 0 ? value : null;
}

export async function readAccountRateLimits(config, accountId, requester = requestCodexAppServer) {
  const account = requireAccount(config, accountId);
  try {
    const result = await requester({ codexCliPath: config.codexCliPath, codexHome: account.codexHome, method: "account/rateLimits/read" });
    return publicRateLimits(result);
  } catch (cause) {
    if (/token_invalidated|401 Unauthorized|signing in again/i.test(cause?.message ?? "")) {
      const error = new Error("account_login_required");
      error.status = 401;
      throw error;
    }
    throw cause;
  }
}

export async function readAccountTokenUsage(config, accountId, requester = requestCodexAppServer) {
  const account = requireAccount(config, accountId);
  try {
    const result = await requester({ codexCliPath: config.codexCliPath, codexHome: account.codexHome, method: "account/usage/read" });
    return publicAccountTokenUsage(result);
  } catch (cause) {
    if (/token_invalidated|401 Unauthorized|signing in again/i.test(cause?.message ?? "")) {
      const error = new Error("account_login_required");
      error.status = 401;
      throw error;
    }
    throw cause;
  }
}

export async function consumeAccountRateLimitResetCredit(config, accountId, requester = requestCodexAppServer, idFactory = randomUUID) {
  const account = requireAccount(config, accountId);
  try {
    const result = await requester({
      codexCliPath: config.codexCliPath,
      codexHome: account.codexHome,
      method: "account/rateLimitResetCredit/consume",
      params: { idempotencyKey: idFactory() }
    });
    if (!["reset", "nothingToReset", "noCredit", "alreadyRedeemed"].includes(result?.outcome)) {
      const error = new Error("reset_credit_unavailable");
      error.status = 502;
      throw error;
    }
    return { outcome: result.outcome };
  } catch (cause) {
    if (/token_invalidated|401 Unauthorized|signing in again/i.test(cause?.message ?? "")) {
      const error = new Error("account_login_required");
      error.status = 401;
      throw error;
    }
    throw cause;
  }
}

function requireAccount(config, accountId) {
  const account = config.accounts.find((item) => item.id === accountId);
  if (account?.historyOnly) {
    const error = new Error("account_not_available_for_execution");
    error.status = 403;
    throw error;
  }
  if (account) return account;
  const error = new Error("account_not_found");
  error.status = 404;
  throw error;
}
