/**
 * Telemetry repository — cloud-agnostic via @bytelyst/datastore.
 */

import type { FilterMap } from '@bytelyst/datastore';
import { getCollection } from '../../lib/datastore.js';
import type {
  TelemetryEventDoc,
  TelemetryCollectionPolicyDoc,
  TelemetryErrorCluster,
  TelemetryQueryInput,
} from './types.js';

// ─── Collection accessors ───────────────────────────────────────────

function eventsCollection() {
  return getCollection<TelemetryEventDoc>('telemetry_events', '/pk');
}

function policiesCollection() {
  return getCollection<TelemetryCollectionPolicyDoc>('telemetry_collection_policies', '/productId');
}

function clustersCollection() {
  return getCollection<TelemetryErrorCluster>('telemetry_error_clusters', '/pk');
}

// ─── Events ─────────────────────────────────────────────────────────

export async function upsertEvent(doc: TelemetryEventDoc): Promise<void> {
  await eventsCollection().upsert(doc);
}

export async function upsertEventsBatch(docs: TelemetryEventDoc[]): Promise<void> {
  // Group by pk so same-partition writes are sequential (avoids contention),
  // while different partitions run in parallel (reduces total latency).
  const byPk = new Map<string, TelemetryEventDoc[]>();
  for (const doc of docs) {
    const group = byPk.get(doc.pk) ?? [];
    group.push(doc);
    byPk.set(doc.pk, group);
  }

  const col = eventsCollection();
  const partitionBatches = [...byPk.values()].map(async group => {
    for (const doc of group) {
      await col.upsert(doc);
    }
  });
  await Promise.all(partitionBatches);
}

export async function queryEvents(
  productId: string,
  input: TelemetryQueryInput
): Promise<{ events: TelemetryEventDoc[]; continuationToken?: string }> {
  const filter: FilterMap = { productId };

  if (input.userId) filter.userId = input.userId;
  if (input.anonymousInstallId) filter.anonymousInstallId = input.anonymousInstallId;
  if (input.platform) filter.platform = input.platform;
  if (input.channel) filter.channel = input.channel;
  if (input.osFamily) filter.osFamily = input.osFamily;
  if (input.appVersion) filter.appVersion = input.appVersion;
  if (input.buildNumber) filter.buildNumber = input.buildNumber;
  if (input.module) filter.module = input.module;
  if (input.eventName) filter.eventName = input.eventName;
  if (input.eventType) filter.eventType = input.eventType;
  if (input.from)
    filter.occurredAt = { ...((filter.occurredAt as object) ?? {}), $gte: input.from };
  if (input.to) filter.occurredAt = { ...((filter.occurredAt as object) ?? {}), $lte: input.to };

  // Note: continuationToken is not supported in the abstraction layer.
  // For v1, we fetch with limit only.
  const events = await eventsCollection().findMany({
    filter,
    sort: { occurredAt: -1 },
    limit: input.limit,
  });

  return { events, continuationToken: undefined };
}

export async function queryGeoDistribution(
  productId: string,
  from?: string,
  to?: string
): Promise<Array<{ countryCode: string; count: number }>> {
  const filter: FilterMap = {
    productId,
    countryCode: { $exists: true },
  };
  if (from) filter.occurredAt = { ...((filter.occurredAt as object) ?? {}), $gte: from };
  if (to) filter.occurredAt = { ...((filter.occurredAt as object) ?? {}), $lte: to };

  // Fetch docs and aggregate in-memory
  const docs = await eventsCollection().findMany({ filter });

  const counts: Record<string, number> = {};
  for (const doc of docs) {
    if (doc.countryCode) {
      counts[doc.countryCode] = (counts[doc.countryCode] ?? 0) + 1;
    }
  }

  return Object.entries(counts)
    .map(([countryCode, count]) => ({ countryCode, count }))
    .sort((a, b) => b.count - a.count);
}

export async function deleteEventsByUserId(productId: string, userId: string): Promise<number> {
  // Find all events for this user, then delete them
  const docs = await eventsCollection().findMany({
    filter: { productId, userId },
  });

  let deleted = 0;
  for (const doc of docs) {
    try {
      await eventsCollection().delete(doc.id, doc.pk);
      deleted++;
    } catch {
      // Skip docs that fail to delete (already deleted, etc.)
    }
  }
  return deleted;
}

// ─── Policies ───────────────────────────────────────────────────────

export async function listPolicies(productId: string): Promise<TelemetryCollectionPolicyDoc[]> {
  return policiesCollection().findMany({
    filter: { productId },
    sort: { priority: -1 },
  });
}

export async function getPolicy(
  id: string,
  productId: string
): Promise<TelemetryCollectionPolicyDoc | null> {
  try {
    return await policiesCollection().findById(id, productId);
  } catch {
    return null;
  }
}

export async function createPolicy(
  doc: TelemetryCollectionPolicyDoc
): Promise<TelemetryCollectionPolicyDoc> {
  return policiesCollection().create(doc);
}

export async function updatePolicy(
  id: string,
  productId: string,
  updates: Partial<TelemetryCollectionPolicyDoc>
): Promise<TelemetryCollectionPolicyDoc | null> {
  try {
    return await policiesCollection().update(id, productId, {
      ...updates,
      updatedAt: new Date().toISOString(),
    });
  } catch {
    return null;
  }
}

export async function deletePolicy(id: string, productId: string): Promise<boolean> {
  try {
    await policiesCollection().delete(id, productId);
    return true;
  } catch {
    return false;
  }
}

// ─── Clusters ───────────────────────────────────────────────────────

export async function listClusters(
  productId: string,
  filters?: {
    platform?: string;
    module?: string;
    from?: string;
    to?: string;
    limit?: number;
  }
): Promise<TelemetryErrorCluster[]> {
  const filter: FilterMap = { productId };

  if (filters?.platform) filter.platform = filters.platform;
  if (filters?.module) filter.module = filters.module;
  if (filters?.from)
    filter.lastSeenAt = { ...((filter.lastSeenAt as object) ?? {}), $gte: filters.from };
  if (filters?.to)
    filter.lastSeenAt = { ...((filter.lastSeenAt as object) ?? {}), $lte: filters.to };

  return clustersCollection().findMany({
    filter,
    sort: { totalCount: -1 },
    limit: filters?.limit ?? 50,
  });
}

export async function upsertCluster(cluster: TelemetryErrorCluster): Promise<void> {
  await clustersCollection().upsert(cluster);
}

export async function getCluster(id: string, pk: string): Promise<TelemetryErrorCluster | null> {
  try {
    return await clustersCollection().findById(id, pk);
  } catch {
    return null;
  }
}

export async function updateCluster(
  id: string,
  pk: string,
  updates: Partial<TelemetryErrorCluster>
): Promise<TelemetryErrorCluster | null> {
  try {
    return await clustersCollection().update(id, pk, updates);
  } catch {
    return null;
  }
}

// ─── Cross-Product Queries ──────────────────────────────────────────

export interface CrossProductSummary {
  productId: string;
  totalEvents: number;
  errorEvents: number;
  errorRate: number;
  topModules: Array<{ module: string; count: number }>;
  platforms: Array<{ platform: string; count: number }>;
}

export interface DailyEventCount {
  date: string;
  productId: string;
  count: number;
}

export async function queryCrossProductSummary(
  productIds: string[],
  from?: string,
  to?: string
): Promise<CrossProductSummary[]> {
  const results: CrossProductSummary[] = [];

  for (const productId of productIds) {
    const filter: FilterMap = { productId };
    if (from) filter.occurredAt = { ...((filter.occurredAt as object) ?? {}), $gte: from };
    if (to) filter.occurredAt = { ...((filter.occurredAt as object) ?? {}), $lte: to };

    const events = await eventsCollection().findMany({ filter, limit: 1000 });

    const errorEvents = events.filter(e => ['warn', 'error', 'fatal'].includes(e.eventType));

    // Aggregate modules
    const moduleCounts: Record<string, number> = {};
    for (const e of events) {
      moduleCounts[e.module] = (moduleCounts[e.module] ?? 0) + 1;
    }
    const topModules = Object.entries(moduleCounts)
      .map(([module, count]) => ({ module, count }))
      .sort((a, b) => b.count - a.count)
      .slice(0, 10);

    // Aggregate platforms
    const platformCounts: Record<string, number> = {};
    for (const e of events) {
      platformCounts[e.platform] = (platformCounts[e.platform] ?? 0) + 1;
    }
    const platforms = Object.entries(platformCounts)
      .map(([platform, count]) => ({ platform, count }))
      .sort((a, b) => b.count - a.count);

    results.push({
      productId,
      totalEvents: events.length,
      errorEvents: errorEvents.length,
      errorRate: events.length > 0 ? errorEvents.length / events.length : 0,
      topModules,
      platforms,
    });
  }

  return results;
}

export async function queryCrossProductDaily(
  productIds: string[],
  from?: string,
  to?: string
): Promise<DailyEventCount[]> {
  const results: DailyEventCount[] = [];

  for (const productId of productIds) {
    const filter: FilterMap = { productId };
    if (from) filter.occurredAt = { ...((filter.occurredAt as object) ?? {}), $gte: from };
    if (to) filter.occurredAt = { ...((filter.occurredAt as object) ?? {}), $lte: to };

    const events = await eventsCollection().findMany({ filter, limit: 1000 });

    // Aggregate by date
    const dayCounts: Record<string, number> = {};
    for (const e of events) {
      const date = e.occurredAt.substring(0, 10); // YYYY-MM-DD
      dayCounts[date] = (dayCounts[date] ?? 0) + 1;
    }

    for (const [date, count] of Object.entries(dayCounts)) {
      results.push({ date, productId, count });
    }
  }

  return results.sort((a, b) => a.date.localeCompare(b.date));
}

export async function queryCrossProductClusters(
  productIds: string[],
  limit = 20
): Promise<TelemetryErrorCluster[]> {
  const allClusters: TelemetryErrorCluster[] = [];

  for (const productId of productIds) {
    const clusters = await clustersCollection().findMany({
      filter: { productId },
      sort: { totalCount: -1 },
      limit,
    });
    allClusters.push(...clusters);
  }

  // Sort by totalCount descending and take top N
  return allClusters.sort((a, b) => b.totalCount - a.totalCount).slice(0, limit);
}
