/**
 * Analytics Rollups repository — Cosmos DB CRUD + rollup logic.
 */

import { getRegisteredContainer } from '@bytelyst/cosmos';
import type {
  AnalyticsRollupDoc,
  RollupPeriod,
  IngestMetricInput,
  AnalyticsSummary,
} from './types.js';

function getContainer() {
  return getRegisteredContainer('analytics_rollups');
}

// ── Date helpers ─────────────────────────────────────────────

export function toDailyKey(date: Date): string {
  return date.toISOString().slice(0, 10); // YYYY-MM-DD
}

export function toWeeklyKey(date: Date): string {
  const jan1 = new Date(date.getFullYear(), 0, 1);
  const dayOfYear = Math.ceil((date.getTime() - jan1.getTime()) / 86_400_000);
  const weekNum = Math.ceil((dayOfYear + jan1.getDay()) / 7);
  return `${date.getFullYear()}-W${String(weekNum).padStart(2, '0')}`;
}

export function toMonthlyKey(date: Date): string {
  return date.toISOString().slice(0, 7); // YYYY-MM
}

export function getDateKey(date: Date, period: RollupPeriod): string {
  switch (period) {
    case 'daily':
      return toDailyKey(date);
    case 'weekly':
      return toWeeklyKey(date);
    case 'monthly':
      return toMonthlyKey(date);
  }
}

// ── Ingest ───────────────────────────────────────────────────

export async function ingestMetric(productId: string, event: IngestMetricInput): Promise<void> {
  const ts = event.timestamp ? new Date(event.timestamp) : new Date();
  // Increment daily rollup (other periods aggregated by scheduled job)
  await incrementRollup(productId, 'daily', toDailyKey(ts), event.metric, event.value);
}

export async function ingestBatch(productId: string, events: IngestMetricInput[]): Promise<number> {
  let count = 0;
  for (const event of events) {
    await ingestMetric(productId, event);
    count++;
  }
  return count;
}

// ── Rollup CRUD ──────────────────────────────────────────────

async function incrementRollup(
  productId: string,
  period: RollupPeriod,
  dateKey: string,
  metric: string,
  value: number
): Promise<void> {
  const id = `${productId}:${period}:${dateKey}`;
  const container = getContainer();
  const now = new Date().toISOString();

  try {
    const { resource } = await container.item(id, productId).read<AnalyticsRollupDoc>();
    if (resource) {
      resource.metrics[metric] = (resource.metrics[metric] ?? 0) + value;
      resource.updatedAt = now;
      await container.item(id, productId).replace(resource);
      return;
    }
  } catch {
    // Does not exist — create
  }

  const doc: AnalyticsRollupDoc = {
    id,
    productId,
    period,
    date: dateKey,
    metrics: { [metric]: value },
    createdAt: now,
    updatedAt: now,
  };
  await container.items.create(doc);
}

export async function queryRollups(
  productId: string,
  period: RollupPeriod,
  from?: string,
  to?: string,
  metric?: string
): Promise<AnalyticsRollupDoc[]> {
  const conditions = ['c.productId = @pid', 'c.period = @period'];
  const params: { name: string; value: string }[] = [
    { name: '@pid', value: productId },
    { name: '@period', value: period },
  ];

  if (from) {
    conditions.push('c.date >= @from');
    params.push({ name: '@from', value: from });
  }
  if (to) {
    conditions.push('c.date <= @to');
    params.push({ name: '@to', value: to });
  }

  const query = `SELECT * FROM c WHERE ${conditions.join(' AND ')} ORDER BY c.date ASC`;
  const { resources } = await getContainer()
    .items.query<AnalyticsRollupDoc>({ query, parameters: params })
    .fetchAll();

  // Filter to specific metric if requested
  if (metric) {
    return resources.map(r => ({
      ...r,
      metrics: { [metric]: r.metrics[metric] ?? 0 },
    }));
  }

  return resources;
}

export async function getRollup(
  productId: string,
  period: RollupPeriod,
  dateKey: string
): Promise<AnalyticsRollupDoc | null> {
  const id = `${productId}:${period}:${dateKey}`;
  try {
    const { resource } = await getContainer().item(id, productId).read<AnalyticsRollupDoc>();
    return resource ?? null;
  } catch {
    return null;
  }
}

// ── Rollup Aggregation (daily → weekly/monthly) ─────────────

export async function aggregateDailyToWeekly(productId: string, dateStr: string): Promise<number> {
  const date = new Date(dateStr);
  const weekKey = toWeeklyKey(date);

  // Find the Monday of this week
  const dayOfWeek = date.getDay();
  const monday = new Date(date);
  monday.setDate(date.getDate() - ((dayOfWeek + 6) % 7));

  const dailies = await queryRollups(productId, 'daily', toDailyKey(monday), dateStr);
  if (dailies.length === 0) return 0;

  const merged: Record<string, number> = {};
  for (const d of dailies) {
    for (const [k, v] of Object.entries(d.metrics)) {
      merged[k] = (merged[k] ?? 0) + v;
    }
  }

  const id = `${productId}:weekly:${weekKey}`;
  const now = new Date().toISOString();
  const doc: AnalyticsRollupDoc = {
    id,
    productId,
    period: 'weekly',
    date: weekKey,
    metrics: merged,
    createdAt: now,
    updatedAt: now,
  };

  try {
    await getContainer().item(id, productId).replace(doc);
  } catch {
    await getContainer().items.create(doc);
  }
  return Object.values(merged).reduce((a, b) => a + b, 0);
}

export async function aggregateDailyToMonthly(productId: string, dateStr: string): Promise<number> {
  const date = new Date(dateStr);
  const monthKey = toMonthlyKey(date);
  const firstDay = `${monthKey}-01`;

  const dailies = await queryRollups(productId, 'daily', firstDay, dateStr);
  if (dailies.length === 0) return 0;

  const merged: Record<string, number> = {};
  for (const d of dailies) {
    for (const [k, v] of Object.entries(d.metrics)) {
      merged[k] = (merged[k] ?? 0) + v;
    }
  }

  const id = `${productId}:monthly:${monthKey}`;
  const now = new Date().toISOString();
  const doc: AnalyticsRollupDoc = {
    id,
    productId,
    period: 'monthly',
    date: monthKey,
    metrics: merged,
    createdAt: now,
    updatedAt: now,
  };

  try {
    await getContainer().item(id, productId).replace(doc);
  } catch {
    await getContainer().items.create(doc);
  }
  return Object.values(merged).reduce((a, b) => a + b, 0);
}

// ── Summary Dashboard ───────────────────────────────────────

export async function getSummary(productId: string, days: number): Promise<AnalyticsSummary> {
  const to = new Date();
  const from = new Date();
  from.setDate(to.getDate() - days);

  const rollups = await queryRollups(productId, 'daily', toDailyKey(from), toDailyKey(to));

  const metricTotals: Record<string, number> = {};
  const trend: { date: string; total: number }[] = [];

  for (const r of rollups) {
    let dayTotal = 0;
    for (const [k, v] of Object.entries(r.metrics)) {
      metricTotals[k] = (metricTotals[k] ?? 0) + v;
      dayTotal += v;
    }
    trend.push({ date: r.date, total: dayTotal });
  }

  const totalEvents = Object.values(metricTotals).reduce((a, b) => a + b, 0);
  const topMetrics = Object.entries(metricTotals)
    .map(([metric, total]) => ({ metric, total }))
    .sort((a, b) => b.total - a.total)
    .slice(0, 10);

  return {
    productId,
    days,
    totalEvents,
    uniqueMetrics: Object.keys(metricTotals).length,
    topMetrics,
    trend,
  };
}

// ── Top Metrics for a Date ──────────────────────────────────

export async function getTopMetrics(
  productId: string,
  period: RollupPeriod,
  dateKey: string,
  limit: number
): Promise<{ metric: string; value: number }[]> {
  const rollup = await getRollup(productId, period, dateKey);
  if (!rollup) return [];

  return Object.entries(rollup.metrics)
    .map(([metric, value]) => ({ metric, value }))
    .sort((a, b) => b.value - a.value)
    .slice(0, limit);
}
