import { getRegisteredContainer } from '@bytelyst/cosmos';
import type { ErrorEvent, ErrorClusterDoc } from './types.js';

// ============================================================================
// Telemetry Linking Service
// ============================================================================

interface TelemetryEvent {
  id: string;
  correlationId?: string;
  productId: string;
  userId?: string;
  sessionId?: string;
  eventType: string;
  eventName: string;
  timestamp: string;
  platform?: string;
  osVersion?: string;
  appVersion?: string;
  deviceModel?: string;
  screen?: string;
  properties?: Record<string, unknown>;
}

interface TelemetryContext {
  correlationId: string;
  precedingEvents: TelemetryEvent[];
  followingEvents: TelemetryEvent[];
  sessionEvents: TelemetryEvent[];
  userJourney: UserJourneyStep[];
  deviceContext: DeviceContext;
  apiCalls: ApiCall[];
}

interface UserJourneyStep {
  timestamp: string;
  screen: string;
  action: string;
  durationMs?: number;
}

interface DeviceContext {
  platform?: string;
  osVersion?: string;
  appVersion?: string;
  deviceModel?: string;
  screenContext?: string;
  memory?: number;
  batteryLevel?: number;
  networkType?: string;
}

interface ApiCall {
  endpoint: string;
  method: string;
  statusCode?: number;
  durationMs?: number;
  timestamp: string;
  error?: string;
}

interface EnrichedErrorContext {
  errorEvent: ErrorEvent;
  telemetryContext?: TelemetryContext;
  sessionState: SessionState;
  recentActions: string[];
  apiFailures: ApiCall[];
  featureFlags: Record<string, boolean>;
  configSnapshot: Record<string, unknown>;
}

interface SessionState {
  screen: string;
  durationOnScreen: number;
  previousScreen?: string;
  userActions: string[];
  formData?: Record<string, unknown>;
  scrollPosition?: number;
}

// ============================================================================
// Container Access
// ============================================================================

function getTelemetryContainer() {
  return getRegisteredContainer('telemetry_events');
}

// ============================================================================
// Correlation ID Propagation
// ============================================================================

/**
 * Links errors to telemetry events via correlation ID
 * Returns telemetry events within 5-minute window of the error
 */
export async function linkErrorToTelemetry(
  errorEvent: ErrorEvent,
  options: {
    windowMinutes?: number;
    maxEvents?: number;
  } = {}
): Promise<TelemetryContext | null> {
  const windowMinutes = options.windowMinutes || 5;
  const maxEvents = options.maxEvents || 50;

  if (!errorEvent.correlationId) {
    // Try to find by user + timestamp if no correlation ID
    return await linkByUserAndTime(errorEvent, windowMinutes, maxEvents);
  }

  const container = getTelemetryContainer();

  // Fetch events with same correlation ID
  const query = `
    SELECT * FROM c 
    WHERE c.correlationId = @correlationId
    AND c.productId = @productId
    ORDER BY c.timestamp ASC
  `;

  const { resources } = await container.items
    .query({
      query,
      parameters: [
        { name: '@correlationId', value: errorEvent.correlationId },
        { name: '@productId', value: errorEvent.productId },
      ],
    })
    .fetchAll();

  const events = resources as TelemetryEvent[];

  if (events.length === 0) {
    return null;
  }

  // Find the error event position
  const errorIndex = events.findIndex(e => e.timestamp === errorEvent.timestamp);

  const windowStart = errorIndex >= 0 ? Math.max(0, errorIndex - Math.floor(maxEvents / 2)) : 0;
  const windowEnd = Math.min(events.length, windowStart + maxEvents);

  const windowEvents = events.slice(windowStart, windowEnd);
  const errorPosition = errorIndex >= 0 ? errorIndex - windowStart : windowEvents.length / 2;

  // Split into preceding and following events
  const precedingEvents = windowEvents.slice(0, errorPosition);
  const followingEvents = windowEvents.slice(errorPosition + 1);

  // Extract user journey
  const userJourney = extractUserJourney(windowEvents);

  // Extract device context from the latest event
  const deviceContext = extractDeviceContext(windowEvents[windowEvents.length - 1]);

  // Extract API calls
  const apiCalls = extractApiCalls(windowEvents);

  return {
    correlationId: errorEvent.correlationId,
    precedingEvents,
    followingEvents,
    sessionEvents: windowEvents,
    userJourney,
    deviceContext,
    apiCalls,
  };
}

/**
 * Fallback linking when correlation ID is not available
 * Uses user ID + time window
 */
async function linkByUserAndTime(
  errorEvent: ErrorEvent,
  windowMinutes: number,
  maxEvents: number
): Promise<TelemetryContext | null> {
  if (!errorEvent.userId) {
    return null;
  }

  const container = getTelemetryContainer();

  const errorTime = new Date(errorEvent.timestamp);
  const windowStart = new Date(errorTime.getTime() - windowMinutes * 60 * 1000).toISOString();
  const windowEnd = new Date(errorTime.getTime() + windowMinutes * 60 * 1000).toISOString();

  const query = `
    SELECT * FROM c 
    WHERE c.userId = @userId
    AND c.productId = @productId
    AND c.timestamp >= @windowStart
    AND c.timestamp <= @windowEnd
    ORDER BY c.timestamp ASC
  `;

  const { resources } = await container.items
    .query(
      {
        query,
        parameters: [
          { name: '@userId', value: errorEvent.userId },
          { name: '@productId', value: errorEvent.productId },
          { name: '@windowStart', value: windowStart },
          { name: '@windowEnd', value: windowEnd },
        ],
      },
      { maxItemCount: maxEvents }
    )
    .fetchAll();

  const events = resources as TelemetryEvent[];

  if (events.length === 0) {
    return null;
  }

  // Generate a synthetic correlation ID
  const correlationId = `synth_${errorEvent.userId}_${errorTime.getTime()}`;

  // Find events before and after error timestamp
  const errorTimeMs = errorTime.getTime();
  const errorIndex = events.findIndex(e => new Date(e.timestamp).getTime() >= errorTimeMs);

  const precedingEvents = errorIndex > 0 ? events.slice(0, errorIndex) : [];
  const followingEvents = errorIndex >= 0 ? events.slice(errorIndex + 1) : events;

  return {
    correlationId,
    precedingEvents,
    followingEvents,
    sessionEvents: events,
    userJourney: extractUserJourney(events),
    deviceContext: extractDeviceContext(events[events.length - 1]),
    apiCalls: extractApiCalls(events),
  };
}

// ============================================================================
// Context Extraction
// ============================================================================

function extractUserJourney(events: TelemetryEvent[]): UserJourneyStep[] {
  const journey: UserJourneyStep[] = [];
  let lastScreen: string | null = null;
  let lastTimestamp: string | null = null;

  for (const event of events) {
    if (event.eventType === 'screen_view' || event.eventName.includes('screen')) {
      const durationMs = lastTimestamp
        ? new Date(event.timestamp).getTime() - new Date(lastTimestamp).getTime()
        : undefined;

      journey.push({
        timestamp: event.timestamp,
        screen: event.screen || (event.properties?.screen as string) || 'unknown',
        action: event.eventName,
        durationMs,
      });

      lastScreen = event.screen || null;
      lastTimestamp = event.timestamp;
    }

    if (event.eventType === 'action' || event.eventType === 'interaction') {
      journey.push({
        timestamp: event.timestamp,
        screen: lastScreen || 'unknown',
        action: event.eventName,
      });
    }
  }

  return journey;
}

function extractDeviceContext(event?: TelemetryEvent): DeviceContext {
  if (!event) {
    return {};
  }

  return {
    platform: event.platform,
    osVersion: event.osVersion,
    appVersion: event.appVersion,
    deviceModel: event.deviceModel,
    screenContext: event.screen,
    memory: event.properties?.memory as number,
    batteryLevel: event.properties?.batteryLevel as number,
    networkType: event.properties?.networkType as string,
  };
}

function extractApiCalls(events: TelemetryEvent[]): ApiCall[] {
  return events
    .filter(
      e =>
        e.eventType === 'api_call' || e.eventName.includes('api') || e.eventName.includes('request')
    )
    .map(e => ({
      endpoint: (e.properties?.endpoint as string) || 'unknown',
      method: (e.properties?.method as string) || 'GET',
      statusCode: e.properties?.statusCode as number,
      durationMs: e.properties?.durationMs as number,
      timestamp: e.timestamp,
      error: e.properties?.error as string,
    }));
}

// ============================================================================
// Error Context Enrichment
// ============================================================================

/**
 * Enriches error event with full telemetry context
 */
export async function enrichErrorContext(errorEvent: ErrorEvent): Promise<EnrichedErrorContext> {
  // Link to telemetry
  const telemetryContext = await linkErrorToTelemetry(errorEvent, {
    windowMinutes: 5,
    maxEvents: 100,
  });

  // Build session state
  const sessionState = buildSessionState(telemetryContext);

  // Extract recent actions
  const recentActions = extractRecentActions(telemetryContext);

  // Extract API failures
  const apiFailures =
    telemetryContext?.apiCalls.filter(
      call => call.error || (call.statusCode && call.statusCode >= 400)
    ) || [];

  // Extract feature flags from telemetry
  const featureFlags = extractFeatureFlags(telemetryContext);

  // Build config snapshot
  const configSnapshot = buildConfigSnapshot(telemetryContext);

  return {
    errorEvent,
    telemetryContext: telemetryContext || undefined,
    sessionState,
    recentActions,
    apiFailures,
    featureFlags,
    configSnapshot,
  };
}

function buildSessionState(telemetryContext: TelemetryContext | null): SessionState {
  if (!telemetryContext || telemetryContext.sessionEvents.length === 0) {
    return {
      screen: 'unknown',
      durationOnScreen: 0,
      userActions: [],
    };
  }

  const events = telemetryContext.sessionEvents;
  const lastEvent = events[events.length - 1];

  // Find last screen view
  const screenViews = events.filter(
    e => e.eventType === 'screen_view' || e.eventName.includes('screen')
  );

  const currentScreen =
    screenViews.length > 0
      ? screenViews[screenViews.length - 1].screen ||
        (screenViews[screenViews.length - 1].properties?.screen as string)
      : 'unknown';

  const previousScreen =
    screenViews.length > 1
      ? screenViews[screenViews.length - 2].screen ||
        (screenViews[screenViews.length - 2].properties?.screen as string)
      : undefined;

  // Calculate duration on current screen
  let durationOnScreen = 0;
  if (screenViews.length >= 1) {
    const screenEnterTime = new Date(screenViews[screenViews.length - 1].timestamp).getTime();
    const lastEventTime = new Date(lastEvent.timestamp).getTime();
    durationOnScreen = lastEventTime - screenEnterTime;
  }

  // Extract user actions
  const userActions = events
    .filter(e => e.eventType === 'action' || e.eventType === 'interaction')
    .map(e => e.eventName);

  return {
    screen: currentScreen || 'unknown',
    durationOnScreen,
    previousScreen,
    userActions,
    scrollPosition: lastEvent.properties?.scrollPosition as number,
    formData: lastEvent.properties?.formData as Record<string, unknown>,
  };
}

function extractRecentActions(telemetryContext: TelemetryContext | null): string[] {
  if (!telemetryContext) return [];

  return telemetryContext.precedingEvents
    .slice(-10) // Last 10 actions before error
    .filter(e => e.eventType === 'action' || e.eventType === 'interaction')
    .map(e => e.eventName);
}

function extractFeatureFlags(telemetryContext: TelemetryContext | null): Record<string, boolean> {
  if (!telemetryContext) return {};

  const flags: Record<string, boolean> = {};

  for (const event of telemetryContext.sessionEvents) {
    if (event.properties?.featureFlags) {
      Object.assign(flags, event.properties.featureFlags);
    }
  }

  return flags;
}

function buildConfigSnapshot(telemetryContext: TelemetryContext | null): Record<string, unknown> {
  if (!telemetryContext) return {};

  const config: Record<string, unknown> = {};

  for (const event of telemetryContext.sessionEvents) {
    if (event.properties?.config) {
      Object.assign(config, event.properties.config);
    }
  }

  return config;
}

// ============================================================================
// Breadcrumb Trail Generation
// ============================================================================

export interface Breadcrumb {
  timestamp: string;
  category: 'navigation' | 'action' | 'api' | 'error' | 'state';
  message: string;
  data?: Record<string, unknown>;
}

/**
 * Generates a breadcrumb trail from telemetry context
 */
export function generateBreadcrumbTrail(telemetryContext: TelemetryContext | null): Breadcrumb[] {
  if (!telemetryContext) return [];

  const breadcrumbs: Breadcrumb[] = [];

  // Add navigation breadcrumbs
  for (const step of telemetryContext.userJourney) {
    breadcrumbs.push({
      timestamp: step.timestamp,
      category: 'navigation',
      message: `Screen: ${step.screen}`,
      data: { durationMs: step.durationMs },
    });
  }

  // Add API call breadcrumbs
  for (const call of telemetryContext.apiCalls) {
    breadcrumbs.push({
      timestamp: call.timestamp,
      category: 'api',
      message: `${call.method} ${call.endpoint}`,
      data: {
        statusCode: call.statusCode,
        durationMs: call.durationMs,
        error: call.error,
      },
    });
  }

  // Sort by timestamp
  breadcrumbs.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());

  return breadcrumbs;
}

// ============================================================================
// Cluster Context Aggregation
// ============================================================================

export interface ClusterContextSummary {
  totalOccurrences: number;
  affectedUsers: string[];
  timeRange: { start: string; end: string };
  mostCommonScreens: Array<{ screen: string; count: number }>;
  mostCommonActions: Array<{ action: string; count: number }>;
  apiFailurePattern?: {
    endpoint: string;
    errorPattern: string;
    occurrenceCount: number;
  };
  featureFlagCorrelations: Array<{
    flag: string;
    enabled: boolean;
    errorCorrelation: number;
  }>;
}

/**
 * Aggregates context across all errors in a cluster
 */
export async function aggregateClusterContext(
  cluster: ErrorClusterDoc,
  errorEvents: ErrorEvent[]
): Promise<ClusterContextSummary> {
  const affectedUsers = new Set<string>();
  const screenCounts = new Map<string, number>();
  const actionCounts = new Map<string, number>();
  const apiErrors = new Map<string, { count: number; errors: string[] }>();
  const flagCorrelations = new Map<string, { enabled: number; total: number }>();

  const earliestTime = new Date(cluster.firstSeenAt);
  const latestTime = new Date(cluster.lastSeenAt);

  for (const error of errorEvents) {
    if (error.userId) {
      affectedUsers.add(error.userId);
    }

    // Track screens and actions
    if (error.screen) {
      screenCounts.set(error.screen, (screenCounts.get(error.screen) || 0) + 1);
    }

    // Enrich and extract more context
    const enriched = await enrichErrorContext(error);

    for (const action of enriched.recentActions) {
      actionCounts.set(action, (actionCounts.get(action) || 0) + 1);
    }

    // Track API failures
    for (const apiFailure of enriched.apiFailures) {
      const key = `${apiFailure.method} ${apiFailure.endpoint}`;
      const existing = apiErrors.get(key) || { count: 0, errors: [] };
      existing.count++;
      if (apiFailure.error) {
        existing.errors.push(apiFailure.error);
      }
      apiErrors.set(key, existing);
    }

    // Track feature flag correlations
    for (const [flag, enabled] of Object.entries(enriched.featureFlags)) {
      const existing = flagCorrelations.get(flag) || { enabled: 0, total: 0 };
      existing.total++;
      if (enabled) existing.enabled++;
      flagCorrelations.set(flag, existing);
    }
  }

  // Find most common API error pattern
  let apiFailurePattern: ClusterContextSummary['apiFailurePattern'] | undefined;
  let maxApiErrors = 0;

  for (const [endpoint, data] of apiErrors.entries()) {
    if (data.count > maxApiErrors) {
      maxApiErrors = data.count;
      // Find common error pattern
      const errorFrequency = new Map<string, number>();
      for (const err of data.errors) {
        errorFrequency.set(err, (errorFrequency.get(err) || 0) + 1);
      }
      const mostCommonError = Array.from(errorFrequency.entries()).sort((a, b) => b[1] - a[1])[0];

      apiFailurePattern = {
        endpoint,
        errorPattern: mostCommonError?.[0] || 'unknown',
        occurrenceCount: data.count,
      };
    }
  }

  // Calculate feature flag correlations
  const featureFlagCorrelations = Array.from(flagCorrelations.entries())
    .map(([flag, data]) => ({
      flag,
      enabled: data.enabled > 0,
      errorCorrelation: data.enabled / data.total,
    }))
    .filter(f => f.errorCorrelation > 0.5) // Only include flags with >50% correlation
    .sort((a, b) => b.errorCorrelation - a.errorCorrelation)
    .slice(0, 5);

  return {
    totalOccurrences: errorEvents.length,
    affectedUsers: Array.from(affectedUsers),
    timeRange: {
      start: earliestTime.toISOString(),
      end: latestTime.toISOString(),
    },
    mostCommonScreens: Array.from(screenCounts.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 5)
      .map(([screen, count]) => ({ screen, count })),
    mostCommonActions: Array.from(actionCounts.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 5)
      .map(([action, count]) => ({ action, count })),
    apiFailurePattern,
    featureFlagCorrelations,
  };
}
