/**
 * Telemetry Service
 *
 * Centralized telemetry for Core Web Vitals, errors, and rate-limit failures.
 * Integrates with Sentry when configured, with sampling and PII scrubbing.
 *
 * @module telemetry.service
 */

import { logger } from '../logger';
import type { TelemetryEvent, TelemetryEventType } from '../types/health';
import { storeWebVitalSample } from './web-vitals-storage';

// Sampling rates (0.0 - 1.0)
const SAMPLING_CONFIG: Record<TelemetryEventType, number> = {
  web_vital: process.env.NODE_ENV === 'production' ? 0.1 : 1.0, // 10% in prod
  error: 1.0, // Always capture errors
  rate_limit: 0.5, // 50% of rate limit events
  security: 1.0, // Always capture security events
  sync_queue: 0.2, // 20% of sync queue metrics
  performance: process.env.NODE_ENV === 'production' ? 0.05 : 1.0, // 5% in prod
  job_queue: 0.2, // 20% of job queue metrics
};

// PII patterns for scrubbing
const PII_PATTERNS = [
  // Email addresses
  /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
  // Phone numbers (international, GCC, generic)
  /\+?\d{1,4}[-.\s]?\(?\d{1,4}\)?[-.\s]?\d{1,4}[-.\s]?\d{1,9}/g,
  // Credit card patterns
  /\b(?:4\d{3}|5[1-5]\d{2}|3[47]\d{2})[-.\s]?\d{4}[-.\s]?\d{4}[-.\s]?\d{4}\b/g,
  // JWT tokens
  /eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+/g,
  // API keys
  /\b(?:sk|pk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g,
  // Generic long alphanumeric (likely keys)
  /\b[A-Za-z0-9]{32,}\b/g,
];

/**
 * Scrub PII from string values
 */
function scrubPII(value: string): string {
  let result = value;
  for (const pattern of PII_PATTERNS) {
    result = result.replace(pattern, '[REDACTED]');
  }
  return result;
}

/**
 * Scrub PII from object recursively
 */
function scrubPIIFromObject<T extends Record<string, unknown>>(obj: T): T {
  const result: Record<string, unknown> = {};

  for (const [key, value] of Object.entries(obj)) {
    // Sensitive key names
    const sensitiveKeys = ['email', 'phone', 'password', 'token', 'key', 'secret', 'auth'];
    if (sensitiveKeys.some(k => key.toLowerCase().includes(k))) {
      result[key] = '[REDACTED]';
    } else if (typeof value === 'string') {
      result[key] = scrubPII(value);
    } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
      result[key] = scrubPIIFromObject(value as Record<string, unknown>);
    } else {
      result[key] = value;
    }
  }

  return result as T;
}

/**
 * Check if event should be sampled
 */
function shouldSample(type: TelemetryEventType): boolean {
  const rate = SAMPLING_CONFIG[type] ?? 0.1;
  return Math.random() < rate;
}

// Sentry types (only for type checking - actual import is dynamic)
interface SentryLike {
  captureMessage: (message: string, options?: {
    level?: 'fatal' | 'error' | 'warning' | 'log' | 'info' | 'debug';
    tags?: Record<string, string>;
    extra?: Record<string, unknown>;
  }) => void;
}

// Sentry reference (lazy loaded)
let sentryInstance: SentryLike | null = null;
let sentryChecked = false;

/**
 * Try to load Sentry dynamically
 * This is wrapped in a separate function to isolate the dynamic import
 */
async function loadSentryModule(): Promise<SentryLike | null> {
  try {
    // Use webpackIgnore comment to prevent webpack from bundling this
    // eslint-disable-next-line @typescript-eslint/no-require-imports
    const sentry = require('@sentry/nextjs');
    return sentry as SentryLike;
  } catch {
    // Sentry not installed - this is expected in many environments
    return null;
  }
}

/**
 * Initialize Sentry connection (called lazily)
 * Uses require to avoid build errors when @sentry/nextjs isn't installed
 */
async function getSentry(): Promise<SentryLike | null> {
  // Return cached result if already checked
  if (sentryChecked) {
    return sentryInstance;
  }

  // Check if Sentry DSN is configured
  if (!process.env.NEXT_PUBLIC_SENTRY_DSN) {
    sentryChecked = true;
    return null;
  }

  sentryInstance = await loadSentryModule();
  sentryChecked = true;

  if (!sentryInstance) {
    logger.debug('[Telemetry] Sentry SDK not available');
  }

  return sentryInstance;
}

/**
 * Send telemetry event to configured sink (Sentry or console)
 */
export async function sendTelemetryEvent(event: TelemetryEvent): Promise<void> {
  // Check sampling
  if (!shouldSample(event.type)) {
    return;
  }

  // Scrub PII from event
  const scrubbedEvent = {
    ...event,
    tags: event.tags ? scrubPIIFromObject(event.tags) : undefined,
    extra: event.extra ? scrubPIIFromObject(event.extra as Record<string, unknown>) : undefined,
  };

  const sentry = await getSentry();

  if (sentry) {
    try {
      switch (event.type) {
        case 'error':
          // Capture as exception
          sentry.captureMessage(scrubbedEvent.name, {
            level: 'error',
            tags: {
              telemetry_type: event.type,
              ...scrubbedEvent.tags,
            },
            extra: scrubbedEvent.extra,
          });
          break;

        case 'web_vital':
          // Capture Web Vital metrics
          sentry.captureMessage(`Web Vital: ${scrubbedEvent.name}`, {
            level: 'info',
            tags: {
              telemetry_type: 'web_vital',
              vital_name: scrubbedEvent.name,
              vital_rating: scrubbedEvent.rating || 'unknown',
              ...scrubbedEvent.tags,
            },
            extra: {
              value: scrubbedEvent.value,
              rating: scrubbedEvent.rating,
              ...scrubbedEvent.extra,
            },
          });
          break;

        case 'rate_limit':
          // Capture rate limit events as warnings
          sentry.captureMessage(`Rate Limit: ${scrubbedEvent.name}`, {
            level: 'warning',
            tags: {
              telemetry_type: 'rate_limit',
              endpoint: scrubbedEvent.name,
              ...scrubbedEvent.tags,
            },
            extra: scrubbedEvent.extra,
          });
          break;

        case 'security':
          // Capture security events
          sentry.captureMessage(`Security: ${scrubbedEvent.name}`, {
            level: 'warning',
            tags: {
              telemetry_type: 'security',
              security_event: scrubbedEvent.name,
              ...scrubbedEvent.tags,
            },
            extra: scrubbedEvent.extra,
          });
          break;

        case 'sync_queue':
          // Capture sync queue metrics
          sentry.captureMessage(`Sync Queue: ${scrubbedEvent.name}`, {
            level: scrubbedEvent.extra?.alertLevel === 'critical' ? 'error' : 'info',
            tags: {
              telemetry_type: 'sync_queue',
              ...scrubbedEvent.tags,
            },
            extra: scrubbedEvent.extra,
          });
          break;

        case 'performance':
          // Capture performance events
          sentry.captureMessage(`Performance: ${scrubbedEvent.name}`, {
            level: 'info',
            tags: {
              telemetry_type: 'performance',
              ...scrubbedEvent.tags,
            },
            extra: {
              value: scrubbedEvent.value,
              ...scrubbedEvent.extra,
            },
          });
          break;
      }
    } catch (error) {
      logger.error('[Telemetry] Failed to send event to Sentry', error);
    }
  } else {
    // Fallback to structured logging
    logger.info('[Telemetry]', {
      ...scrubbedEvent,
    });
  }
}

/**
 * Report Web Vital metric
 */
export async function reportWebVital(
  name: string,
  value: number,
  rating?: string,
  extra?: Record<string, unknown>
): Promise<void> {
  const storeResult = await storeWebVitalSample({
    name: name as 'LCP' | 'FID' | 'CLS' | 'FCP' | 'TTFB' | 'INP',
    value,
    rating,
    url: typeof extra?.url === 'string' ? (extra.url as string) : undefined,
    timestamp: new Date().toISOString(),
  });

  await sendTelemetryEvent({
    type: 'web_vital',
    name,
    value,
    rating,
    extra,
    timestamp: new Date().toISOString(),
  });

  if (storeResult.alertLevel && (name === 'LCP' || name === 'TTFB')) {
    await sendTelemetryEvent({
      type: 'performance',
      name: `${name}_regression`,
      value: storeResult.stats?.p95,
      tags: {
        level: storeResult.alertLevel,
      },
      extra: {
        avg: storeResult.stats?.avg,
        p90: storeResult.stats?.p90,
        p95: storeResult.stats?.p95,
        threshold: name === 'LCP' ? '3000/4000 p95' : '1000/1500 p95',
      },
      timestamp: new Date().toISOString(),
    });
    logger.warn(`Web vital regression detected for ${name}`, { level: storeResult.alertLevel, stats: storeResult.stats });
  }
}

/**
 * Report error event
 */
export async function reportError(
  name: string,
  error?: Error | unknown,
  extra?: Record<string, unknown>
): Promise<void> {
  const errorInfo: Record<string, unknown> = { ...extra };

  if (error instanceof Error) {
    errorInfo.errorMessage = error.message;
    errorInfo.errorName = error.name;
    // Only include stack in non-production
    if (process.env.NODE_ENV !== 'production') {
      errorInfo.errorStack = error.stack;
    }
  } else if (error) {
    errorInfo.errorMessage = String(error);
  }

  await sendTelemetryEvent({
    type: 'error',
    name,
    extra: errorInfo,
    timestamp: new Date().toISOString(),
  });
}

/**
 * Report rate limit event
 */
export async function reportRateLimit(
  endpoint: string,
  clientId: string,
  extra?: Record<string, unknown>
): Promise<void> {
  await sendTelemetryEvent({
    type: 'rate_limit',
    name: endpoint,
    tags: {
      // Hash client ID for privacy
      clientIdHash: Buffer.from(clientId).toString('base64').slice(0, 8),
    },
    extra,
    timestamp: new Date().toISOString(),
  });
}

/**
 * Report security event
 */
export async function reportSecurityEvent(
  eventName: string,
  extra?: Record<string, unknown>
): Promise<void> {
  await sendTelemetryEvent({
    type: 'security',
    name: eventName,
    extra,
    timestamp: new Date().toISOString(),
  });
}

/**
 * Report sync queue metrics
 */
export async function reportSyncQueueMetrics(
  metrics: {
    pending: number;
    failed: number;
    oldestJobAgeMs?: number;
    alertLevel?: 'ok' | 'warning' | 'critical';
    alertMessage?: string;
  }
): Promise<void> {
  await sendTelemetryEvent({
    type: 'sync_queue',
    name: 'sync_queue_metrics',
    value: metrics.pending,
    extra: {
      ...metrics,
    },
    timestamp: new Date().toISOString(),
  });
}

/**
 * Report performance event
 */
export async function reportPerformance(
  name: string,
  durationMs: number,
  extra?: Record<string, unknown>
): Promise<void> {
  await sendTelemetryEvent({
    type: 'performance',
    name,
    value: durationMs,
    extra,
    timestamp: new Date().toISOString(),
  });
}

const telemetryService = {
  sendTelemetryEvent,
  reportWebVital,
  reportError,
  reportRateLimit,
  reportSecurityEvent,
  reportSyncQueueMetrics,
  reportPerformance,
};

export default telemetryService;
