/**
 * Usage Analytics — Aggregation functions for the analytics dashboard
 *
 * Processes usage.json history entries into dashboard-ready data:
 * summary cards, daily trends, activity heatmap, model breakdown, etc.
 */

import {
  normalizeModelName as normModel,
  computeCostFromPricing,
} from "@/lib/usage/costCalculator";

/**
 * Compute date range boundaries
 * @param {string} range - "1d" | "7d" | "30d" | "90d" | "ytd" | "all" | "custom"
 * @param {string} [startDate] - ISO string for custom range start
 * @param {string} [endDate] - ISO string for custom range end
 * @returns {{ start: Date, end: Date }}
 */
function getDateRange(range: string, startDate?: string, endDate?: string) {
  const end = new Date();
  let start;

  switch (range) {
    case "custom":
      start = startDate ? new Date(startDate) : new Date(0);
      return {
        start,
        end: endDate ? new Date(endDate) : end,
      };
    case "1d":
      start = new Date(end);
      start.setDate(start.getDate() - 1);
      break;
    case "7d":
      start = new Date(end);
      start.setDate(start.getDate() - 7);
      break;
    case "30d":
      start = new Date(end);
      start.setDate(start.getDate() - 30);
      break;
    case "90d":
      start = new Date(end);
      start.setDate(start.getDate() - 90);
      break;
    case "ytd":
      start = new Date(end.getFullYear(), 0, 1);
      break;
    case "all":
    default:
      start = new Date(0);
      break;
  }

  return { start, end };
}

/**
 * Format a Date to "YYYY-MM-DD" string
 */
function toDateKey(date: Date) {
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, "0");
  const d = String(date.getDate()).padStart(2, "0");
  return `${y}-${m}-${d}`;
}

/**
 * Short model name (strip provider prefix paths)
 */
function shortModelName(model: string) {
  if (!model) return "unknown";
  // "accounts/fireworks/models/gpt-oss-120b" → "gpt-oss-120b"
  const parts = model.split("/");
  return parts[parts.length - 1] || model;
}

function getApiKeyAnalyticsKey(
  apiKeyId: string | null | undefined,
  apiKeyName: string | null | undefined
) {
  return apiKeyId ? `id:${apiKeyId}` : `name:${apiKeyName || "unknown"}`;
}

/**
 * Compute all analytics data from usage history
 * @param {Array} history - Array of usage entries
 * @param {string} range - Time range filter
 * @param {Object} connectionMap - Map of connectionId → account name
 * @returns {Object} Analytics data
 */
export async function computeAnalytics(
  history: any[],
  range = "30d",
  connectionMap: Record<string, string> = {},
  options: { startDate?: string; endDate?: string } = {}
) {
  const { start, end } = getDateRange(range, options.startDate, options.endDate);

  // ---- Filtered entries ----
  const entries = history.filter((e) => {
    const t = new Date(e.timestamp);
    return t >= start && t <= end;
  });

  // ---- Summary ----
  const summary = {
    totalTokens: 0,
    promptTokens: 0,
    completionTokens: 0,
    totalCost: 0,
    totalRequests: entries.length,
    uniqueModels: new Set<string>(),
    uniqueAccounts: new Set<string>(),
    uniqueApiKeys: new Set<string>(),
  };

  // ---- Daily trend ----
  const dailyMap: Record<string, any> = {}; // "YYYY-MM-DD" → { requests, promptTokens, completionTokens, cost }
  const dailyByModelMap: Record<string, Record<string, number>> = {}; // "YYYY-MM-DD" → { modelShort → tokens }

  // ---- Activity heatmap (always last 365 days, regardless of range filter) ----
  const heatmapStart = new Date();
  heatmapStart.setDate(heatmapStart.getDate() - 364);
  const activityMap: Record<string, number> = {};

  // ---- By model / account / provider ----
  const byModelMap: Record<string, any> = {};
  const byAccountMap: Record<string, any> = {};
  const byProviderMap: Record<string, any> = {};
  const byApiKeyMap: Record<string, any> = {};

  // ---- Weekly pattern (0=Sun..6=Sat) ----
  const weeklyTokens = [0, 0, 0, 0, 0, 0, 0];
  const weeklyCounts = [0, 0, 0, 0, 0, 0, 0];

  // ---- Single pass over ALL history for heatmap ----
  for (const entry of history) {
    const entryDate = new Date(entry.timestamp);
    if (entryDate >= heatmapStart) {
      const key = toDateKey(entryDate);
      const tokens =
        (entry.tokens?.input ?? entry.tokens?.prompt_tokens ?? 0) +
        (entry.tokens?.output ?? entry.tokens?.completion_tokens ?? 0);
      activityMap[key] = (activityMap[key] || 0) + tokens;
    }
  }

  // Pre-fetch pricing for all unique (provider, model) pairs — one DB round-trip
  // per unique pair instead of one per entry, then compute costs synchronously.
  const { getPricingForModel } = await import("@/lib/localDb");
  const pricingCache = new Map<string, Record<string, unknown> | null>();
  const uniquePairs = new Set(entries.map((e) => `${e.provider}|||${e.model}`));
  await Promise.all(
    [...uniquePairs].map(async (key) => {
      const [provider, model] = key.split("|||");
      let pricing = (await getPricingForModel(provider, model)) as Record<string, unknown> | null;
      if (!pricing) {
        const normalized = normModel(model);
        if (normalized !== model) {
          pricing = (await getPricingForModel(provider, normalized)) as Record<
            string,
            unknown
          > | null;
        }
      }
      pricingCache.set(key, pricing ?? null);
    })
  );

  // ---- Single pass over filtered entries for everything else ----
  for (let i = 0; i < entries.length; i++) {
    const entry = entries[i];
    const pt = entry.tokens?.input ?? entry.tokens?.prompt_tokens ?? 0;
    const ct = entry.tokens?.output ?? entry.tokens?.completion_tokens ?? 0;
    const totalTkns = pt + ct;
    const entryDate = new Date(entry.timestamp);
    const dateKey = toDateKey(entryDate);
    const dayOfWeek = entryDate.getDay();
    const modelShort = shortModelName(entry.model);

    const pricingKey = `${entry.provider}|||${entry.model}`;
    const cost = computeCostFromPricing(pricingCache.get(pricingKey), entry.tokens);

    // Summary
    summary.promptTokens += pt;
    summary.completionTokens += ct;
    summary.totalTokens += totalTkns;
    summary.totalCost += cost;
    if (entry.model) summary.uniqueModels.add(modelShort);
    if (entry.connectionId) summary.uniqueAccounts.add(entry.connectionId);
    if (entry.apiKeyId || entry.apiKeyName) {
      summary.uniqueApiKeys.add(getApiKeyAnalyticsKey(entry.apiKeyId, entry.apiKeyName));
    }

    // Daily trend
    if (!dailyMap[dateKey]) {
      dailyMap[dateKey] = {
        date: dateKey,
        requests: 0,
        promptTokens: 0,
        completionTokens: 0,
        cost: 0,
      };
    }
    dailyMap[dateKey].requests++;
    dailyMap[dateKey].promptTokens += pt;
    dailyMap[dateKey].completionTokens += ct;
    dailyMap[dateKey].cost += cost;

    // Daily by model
    if (!dailyByModelMap[dateKey]) dailyByModelMap[dateKey] = {};
    dailyByModelMap[dateKey][modelShort] = (dailyByModelMap[dateKey][modelShort] || 0) + totalTkns;

    // Weekly pattern
    weeklyTokens[dayOfWeek] += totalTkns;
    weeklyCounts[dayOfWeek]++;

    // By model
    if (!byModelMap[modelShort]) {
      byModelMap[modelShort] = {
        model: modelShort,
        provider: entry.provider,
        requests: 0,
        promptTokens: 0,
        completionTokens: 0,
        totalTokens: 0,
        cost: 0,
      };
    }
    byModelMap[modelShort].requests++;
    byModelMap[modelShort].promptTokens += pt;
    byModelMap[modelShort].completionTokens += ct;
    byModelMap[modelShort].totalTokens += totalTkns;
    byModelMap[modelShort].cost += cost;

    // By account
    const accountName = entry.connectionId
      ? connectionMap[entry.connectionId] || `Account ${entry.connectionId.slice(0, 8)}`
      : entry.provider || "unknown";
    if (!byAccountMap[accountName]) {
      byAccountMap[accountName] = { account: accountName, totalTokens: 0, requests: 0, cost: 0 };
    }
    byAccountMap[accountName].totalTokens += totalTkns;
    byAccountMap[accountName].requests++;
    byAccountMap[accountName].cost += cost;

    // By provider
    const prov = entry.provider || "unknown";
    if (!byProviderMap[prov]) {
      byProviderMap[prov] = {
        provider: prov,
        requests: 0,
        promptTokens: 0,
        completionTokens: 0,
        totalTokens: 0,
        cost: 0,
      };
    }
    byProviderMap[prov].requests++;
    byProviderMap[prov].promptTokens += pt;
    byProviderMap[prov].completionTokens += ct;
    byProviderMap[prov].totalTokens += totalTkns;
    byProviderMap[prov].cost += cost;

    // By API key
    if (entry.apiKeyId || entry.apiKeyName) {
      const keyName = entry.apiKeyName || entry.apiKeyId || "unknown";
      const key = getApiKeyAnalyticsKey(entry.apiKeyId, entry.apiKeyName);
      const keyLabel = entry.apiKeyId ? `${keyName} (${entry.apiKeyId})` : keyName;
      if (!byApiKeyMap[key]) {
        byApiKeyMap[key] = {
          apiKey: keyLabel,
          apiKeyId: entry.apiKeyId || null,
          apiKeyName: keyName,
          historicalApiKeyNames: [],
          requests: 0,
          promptTokens: 0,
          completionTokens: 0,
          totalTokens: 0,
          cost: 0,
        };
      }
      if (entry.apiKeyName && !byApiKeyMap[key].historicalApiKeyNames.includes(entry.apiKeyName)) {
        byApiKeyMap[key].historicalApiKeyNames.push(entry.apiKeyName);
      }
      byApiKeyMap[key].requests++;
      byApiKeyMap[key].promptTokens += pt;
      byApiKeyMap[key].completionTokens += ct;
      byApiKeyMap[key].totalTokens += totalTkns;
      byApiKeyMap[key].cost += cost;
    }
  }

  // ---- Build sorted arrays ----
  const dailyTrend = Object.values(dailyMap).sort((a, b) => a.date.localeCompare(b.date));

  // Daily by model — collect all unique model names
  const allModels = new Set<string>();
  for (const day of Object.values(dailyByModelMap)) {
    for (const m of Object.keys(day)) allModels.add(m);
  }
  const dailyByModel = dailyTrend.map((d) => {
    const row = { date: d.date };
    for (const m of allModels) {
      row[m] = dailyByModelMap[d.date]?.[m] || 0;
    }
    return row;
  });

  const byModel = Object.values(byModelMap)
    .sort((a, b) => b.totalTokens - a.totalTokens)
    .map((m) => ({
      ...m,
      pct: summary.totalTokens > 0 ? ((m.totalTokens / summary.totalTokens) * 100).toFixed(1) : "0",
    }));

  const byAccount = Object.values(byAccountMap).sort((a, b) => b.totalTokens - a.totalTokens);
  const byProvider = Object.values(byProviderMap).sort((a, b) => b.totalTokens - a.totalTokens);
  const byApiKey = Object.values(byApiKeyMap).sort((a, b) => b.totalTokens - a.totalTokens);

  // Weekly pattern (avg tokens per day of week)
  const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
  const weeklyPattern = weekDays.map((name, i) => ({
    day: name,
    avgTokens: weeklyCounts[i] > 0 ? Math.round(weeklyTokens[i] / weeklyCounts[i]) : 0,
    totalTokens: weeklyTokens[i],
  }));

  // Streak — consecutive days with activity (from today going back)
  let streak = 0;
  const today = new Date();
  for (let i = 0; i < 365; i++) {
    const d = new Date(today);
    d.setDate(d.getDate() - i);
    const key = toDateKey(d);
    if (activityMap[key] && activityMap[key] > 0) {
      streak++;
    } else if (i > 0) {
      break; // Stop at first gap (skip today if no activity yet)
    }
  }

  return {
    summary: {
      totalTokens: summary.totalTokens,
      promptTokens: summary.promptTokens,
      completionTokens: summary.completionTokens,
      totalCost: summary.totalCost,
      totalRequests: summary.totalRequests,
      uniqueModels: summary.uniqueModels.size,
      uniqueAccounts: summary.uniqueAccounts.size,
      uniqueApiKeys: summary.uniqueApiKeys.size,
      streak,
    },
    dailyTrend,
    dailyByModel,
    modelNames: [...allModels],
    byModel,
    byAccount,
    byProvider,
    byApiKey,
    activityMap,
    weeklyPattern,
    range,
  };
}
