/* eslint-disable @typescript-eslint/no-unused-vars */
/**
 * Diagnostics repository — debug session management and data ingestion.
 *
 * Uses @bytelyst/datastore for cloud-agnostic storage.
 *
 * @module diagnostics
 */

import type { FilterMap } from '@bytelyst/datastore';
import { getCollection } from '../../lib/datastore.js';
import type {
  DebugSessionDoc,
  DebugTraceDoc,
  DebugLogEntryDoc,
  DebugScreenshotDoc,
  ListDebugSessionsQuery,
  QueryTracesInput,
  QueryLogsInput,
} from './types.js';

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

function sessionsCollection() {
  return getCollection<DebugSessionDoc>('debug_sessions', '/id');
}

function tracesCollection() {
  return getCollection<DebugTraceDoc>('debug_traces', '/pk');
}

function logsCollection() {
  return getCollection<DebugLogEntryDoc>('debug_logs', '/pk');
}

function screenshotsCollection() {
  return getCollection<DebugScreenshotDoc>('debug_screenshots', '/sessionId');
}

function logDiagnosticsWarning(message: string, extra?: Record<string, unknown>): void {
  process.stderr.write(
    JSON.stringify({ level: 'warn', module: 'diagnostics.repository', message, ...extra }) + '\n'
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Helper: Build composite partition key
// ─────────────────────────────────────────────────────────────────────────────

function buildPk(productId: string, sessionId: string): string {
  return `${productId}:${sessionId}`;
}

// ─────────────────────────────────────────────────────────────────────────────
// Sessions
// ─────────────────────────────────────────────────────────────────────────────

export async function createSession(doc: DebugSessionDoc): Promise<DebugSessionDoc> {
  await sessionsCollection().upsert(doc);
  return doc;
}

export async function getSession(sessionId: string): Promise<DebugSessionDoc | null> {
  const result = await sessionsCollection().findMany({
    filter: { id: sessionId },
    limit: 1,
  });
  return result[0] ?? null;
}

export async function updateSession(
  sessionId: string,
  updates: Partial<DebugSessionDoc>
): Promise<DebugSessionDoc | null> {
  const existing = await getSession(sessionId);
  if (!existing) return null;

  const updated = { ...existing, ...updates, updatedAt: new Date().toISOString() };
  await sessionsCollection().upsert(updated);
  return updated;
}

export async function deleteSession(sessionId: string): Promise<boolean> {
  // Datastore doesn't have delete - use soft delete
  const existing = await getSession(sessionId);
  if (!existing) return false;

  await sessionsCollection().upsert({
    ...existing,
    status: 'cancelled',
    updatedAt: new Date().toISOString(),
  });
  return true;
}

export async function listSessions(
  query: ListDebugSessionsQuery
): Promise<{ sessions: DebugSessionDoc[]; total: number }> {
  const filter: FilterMap = {};

  if (query.productId) filter.productId = query.productId;
  if (query.status) filter.status = query.status;
  if (query.targetUserId) filter.targetUserId = query.targetUserId;

  // Date range filtering on createdAt
  if (query.from || query.to) {
    filter.createdAt = {};
    if (query.from) (filter.createdAt as Record<string, string>).gte = query.from;
    if (query.to) (filter.createdAt as Record<string, string>).lte = query.to;
  }

  const result = await sessionsCollection().findMany({
    filter,
    sort: { createdAt: -1 },
    limit: query.limit,
    offset: query.offset,
  });

  // Get total count (simplified - in production use COUNT query)
  const allResult = await sessionsCollection().findMany({ filter });
  const total = allResult.length;

  return { sessions: result, total };
}

export async function getActiveSessionForTarget(
  productId: string,
  target: { userId?: string; anonymousId?: string; deviceId?: string }
): Promise<DebugSessionDoc | null> {
  const filter: FilterMap = { productId, status: 'active' };

  if (target.userId) filter.targetUserId = target.userId;
  if (target.anonymousId) filter.targetAnonymousId = target.anonymousId;
  if (target.deviceId) filter.targetDeviceId = target.deviceId;

  const result = await sessionsCollection().findMany({
    filter,
    sort: { createdAt: -1 },
    limit: 1,
  });

  // Check if session hasn't expired
  const session = result[0];
  if (!session) return null;

  const now = new Date().toISOString();
  if (session.expiresAt < now) return null;

  return session;
}

// ─────────────────────────────────────────────────────────────────────────────
// Traces with best-effort batching (BUG-5: Transaction support limited by datastore)
// Note: Individual upserts may partially fail. Client should retry on 500.
// ─────────────────────────────────────────────────────────────────────────────

export async function ingestTraces(
  productId: string,
  sessionId: string,
  traces: DebugTraceDoc[]
): Promise<void> {
  const pk = buildPk(productId, sessionId);

  // Prepare docs with composite pk
  const docs = traces.map(trace => ({
    ...trace,
    pk,
    sessionId,
    productId,
  }));

  // Batch upsert for idempotency
  await Promise.all(docs.map(doc => tracesCollection().upsert(doc)));
}

export async function getTraces(
  productId: string,
  sessionId: string,
  query: QueryTracesInput
): Promise<{ traces: DebugTraceDoc[]; continuationToken?: string }> {
  const pk = buildPk(productId, sessionId);

  const result = await tracesCollection().findMany({
    filter: { pk },
    sort: { startTime: 1 },
    limit: query.limit,
  });

  return {
    traces: result,
    continuationToken: undefined,
  };
}

// ─────────────────────────────────────────────────────────────────────────────
// Logs with best-effort batching (BUG-5: Transaction support limited by datastore)
// Note: Individual upserts may partially fail. Client should retry on 500.
// ─────────────────────────────────────────────────────────────────────────────

export async function ingestLogs(
  productId: string,
  sessionId: string,
  logs: DebugLogEntryDoc[]
): Promise<void> {
  const pk = buildPk(productId, sessionId);

  // Prepare docs with composite pk and receivedAt
  const now = new Date().toISOString();
  const docs = logs.map(log => ({
    ...log,
    pk,
    sessionId,
    productId,
    receivedAt: now,
  }));

  // Batch upsert
  await Promise.all(docs.map(doc => logsCollection().upsert(doc)));
}

export async function getLogs(
  productId: string,
  sessionId: string,
  query: QueryLogsInput
): Promise<{ logs: DebugLogEntryDoc[]; continuationToken?: string }> {
  const pk = buildPk(productId, sessionId);

  const filter: FilterMap = { pk };

  if (query.level) filter.level = query.level;

  // Date range
  if (query.from || query.to) {
    filter.timestamp = {};
    if (query.from) (filter.timestamp as Record<string, string>).gte = query.from;
    if (query.to) (filter.timestamp as Record<string, string>).lte = query.to;
  }

  // Search in message (if supported by datastore, otherwise filter in memory)
  // Note: Cosmos supports CONTAINS in queries

  const result = await logsCollection().findMany({
    filter,
    sort: { timestamp: -1 },
    limit: query.limit,
  });

  // In-memory search filtering if needed
  let logs = result;
  if (query.search) {
    const searchLower = query.search.toLowerCase();
    logs = logs.filter(
      (log: DebugLogEntryDoc) =>
        log.message.toLowerCase().includes(searchLower) ||
        log.module.toLowerCase().includes(searchLower)
    );
  }

  return {
    logs,
    continuationToken: undefined,
  };
}

// ─────────────────────────────────────────────────────────────────────────────
// Screenshots
// ─────────────────────────────────────────────────────────────────────────────

export async function createScreenshotMetadata(
  doc: DebugScreenshotDoc
): Promise<DebugScreenshotDoc> {
  await screenshotsCollection().upsert(doc);
  return doc;
}

export async function getScreenshots(sessionId: string): Promise<DebugScreenshotDoc[]> {
  const result = await screenshotsCollection().findMany({
    filter: { sessionId },
    sort: { capturedAt: -1 },
  });
  return result;
}

export async function getScreenshot(screenshotId: string): Promise<DebugScreenshotDoc | null> {
  const result = await screenshotsCollection().findMany({
    filter: { id: screenshotId },
    limit: 1,
  });
  return result[0] ?? null;
}

// ─────────────────────────────────────────────────────────────────────────────
// Stats update (denormalized counters) with optimistic concurrency control
// BUG-4 FIX: Implements retry logic to handle race conditions
// ─────────────────────────────────────────────────────────────────────────────

const MAX_RETRIES = 3;

export async function updateSessionStats(
  sessionId: string,
  stats: { logCount?: number; traceCount?: number; screenshotCount?: number }
): Promise<void> {
  let retries = 0;

  while (retries < MAX_RETRIES) {
    const existing = await getSession(sessionId);
    if (!existing) return;

    const updated: DebugSessionDoc = {
      ...existing,
      ...(stats.logCount !== undefined && { logCount: existing.logCount + stats.logCount }),
      ...(stats.traceCount !== undefined && { traceCount: existing.traceCount + stats.traceCount }),
      ...(stats.screenshotCount !== undefined && {
        screenshotCount: existing.screenshotCount + stats.screenshotCount,
      }),
      updatedAt: new Date().toISOString(),
    };

    try {
      await sessionsCollection().upsert(updated);
      return; // Success
    } catch (err) {
      // If conflict (etag mismatch), retry
      retries++;
      if (retries >= MAX_RETRIES) {
        // Log warning but don't fail the ingest - data integrity > stats accuracy
        logDiagnosticsWarning('Failed to update session stats after max retries', {
          retries: MAX_RETRIES,
          sessionId,
        });
        return;
      }
      // Small delay before retry
      await new Promise(resolve => setTimeout(resolve, 10 * retries));
    }
  }
}
