/**
 * Structured logging utility for edge functions
 * Prevents sensitive data exposure and provides consistent log format
 */

import { createClient } from '@supabase/supabase-js';
import { formatEdgeError } from './errorFormatter.ts';

type LogLevel = 'info' | 'warn' | 'error' | 'debug';

interface LogContext {
  requestId?: string; // Correlation ID for tracing
  userId?: string;
  action?: string;
  duration?: number; // Request duration in ms
  traceId?: string; // Distributed tracing ID
  [key: string]: unknown;
}

// Span types for distributed tracing
export interface Span {
  spanId: string;
  traceId: string;
  parentSpanId?: string;
  name: string;
  kind: 'SERVER' | 'CLIENT' | 'INTERNAL' | 'DATABASE';
  startTime: number;
  endTime?: number;
  duration?: number;
  attributes: Record<string, unknown>;
  events: SpanEvent[];
  status: 'ok' | 'error' | 'unset';
  error?: {
    type: string;
    message: string;
    stack?: string;
  };
}

export interface SpanEvent {
  timestamp: number;
  name: string;
  attributes?: Record<string, unknown>;
}

export interface SpanContext {
  traceId: string;
  spanId: string;
  traceFlags?: number;
}

// Request tracking utilities (legacy - use spans instead)
export interface RequestTracking {
  requestId: string;
  start: number;
  traceId?: string;
}

export function startRequest(traceId?: string): RequestTracking {
  return {
    requestId: crypto.randomUUID(),
    start: Date.now(),
    traceId: traceId || crypto.randomUUID(),
  };
}

export function endRequest(tracking: RequestTracking): number {
  return Date.now() - tracking.start;
}

// ============================================================================
// Span Lifecycle Functions
// ============================================================================

/**
 * Start a new span
 */
export function startSpan(
  name: string,
  kind: Span['kind'],
  parentSpan?: SpanContext,
  attributes?: Record<string, unknown>
): Span {
  const traceId = parentSpan?.traceId || crypto.randomUUID();
  
  return {
    spanId: crypto.randomUUID(),
    traceId,
    parentSpanId: parentSpan?.spanId,
    name,
    kind,
    startTime: Date.now(),
    attributes: attributes || {},
    events: [],
    status: 'unset',
  };
}

/**
 * End a span with final status
 */
export function endSpan(span: Span, status?: 'ok' | 'error', error?: unknown): Span {
  span.endTime = Date.now();
  span.duration = span.endTime - span.startTime;
  span.status = status || 'ok';
  
  if (error) {
    const err = error instanceof Error ? error : new Error(formatEdgeError(error));
    span.error = {
      type: err.name,
      message: err.message,
      stack: err.stack,
    };
  }
  
  return span;
}

/**
 * Add event to span
 */
export function addSpanEvent(
  span: Span,
  name: string,
  attributes?: Record<string, unknown>
): void {
  span.events.push({
    timestamp: Date.now(),
    name,
    attributes,
  });
}

/**
 * Set span attributes
 */
export function setSpanAttributes(
  span: Span,
  attributes: Record<string, unknown>
): void {
  span.attributes = { ...span.attributes, ...attributes };
}

/**
 * Extract span context for propagation
 */
export function getSpanContext(span: Span): SpanContext {
  return {
    traceId: span.traceId,
    spanId: span.spanId,
  };
}

/**
 * Extract span context from HTTP headers (W3C Trace Context)
 */
export function extractSpanContextFromHeaders(headers: Headers): SpanContext | undefined {
  const traceparent = headers.get('traceparent');
  if (!traceparent) return undefined;
  
  // Parse W3C traceparent: version-traceId-spanId-flags
  const parts = traceparent.split('-');
  if (parts.length !== 4) return undefined;
  
  return {
    traceId: parts[1],
    spanId: parts[2],
    traceFlags: parseInt(parts[3], 16),
  };
}

/**
 * Inject span context into headers
 */
export function injectSpanContextIntoHeaders(spanContext: SpanContext): Record<string, string> {
  return {
    'traceparent': `00-${spanContext.traceId}-${spanContext.spanId}-01`,
  };
}

/**
 * Log completed span (console only)
 */
export function logSpan(span: Span): void {
  const sanitizedAttributes = sanitizeContext(span.attributes);
  const sanitizedEvents = span.events.map(e => ({
    ...e,
    attributes: e.attributes ? sanitizeContext(e.attributes) : undefined,
  }));
  
  edgeLogger.info('Span completed', {
    span: {
      ...span,
      attributes: sanitizedAttributes,
      events: sanitizedEvents,
    },
  });
}

/**
 * Persist span to database (fire-and-forget)
 * Call this after logging the span to store it for monitoring/debugging
 */
export function logSpanToDatabase(span: Span, requestId?: string): void {
  // Fire-and-forget - don't await or block on this
  persistSpanToDatabase(span, requestId).catch((error) => {
    edgeLogger.error('Failed to persist span to database', {
      spanId: span.spanId,
      traceId: span.traceId,
      error: formatEdgeError(error),
    });
  });
}

/**
 * Internal function to persist span to database
 */
async function persistSpanToDatabase(span: Span, requestId?: string): Promise<void> {
  const supabaseUrl = Deno.env.get('SUPABASE_URL');
  const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');
  
  if (!supabaseUrl || !supabaseServiceKey) {
    edgeLogger.warn('Skipping span persistence - Supabase credentials not configured');
    return;
  }
  
  const supabase = createClient(supabaseUrl, supabaseServiceKey);
  
  // Insert span
  const { error: spanError } = await supabase
    .from('request_spans')
    .insert({
      span_id: span.spanId,
      trace_id: span.traceId,
      parent_span_id: span.parentSpanId,
      request_id: requestId,
      name: span.name,
      kind: span.kind,
      start_time: new Date(span.startTime).toISOString(),
      end_time: span.endTime ? new Date(span.endTime).toISOString() : null,
      duration_ms: span.duration,
      status: span.status,
      error_type: span.error?.type,
      error_message: span.error?.message,
      error_stack: span.error?.stack,
    });
  
  if (spanError) {
    throw new Error(`Failed to insert span: ${spanError.message}`);
  }
  
  // Insert attributes
  const attributeInserts = Object.entries(span.attributes).map(([key, value]) => {
    let valueType: 'string' | 'number' | 'boolean' = 'string';
    let valueStr: string;
    
    if (typeof value === 'number') {
      valueType = 'number';
      valueStr = String(value);
    } else if (typeof value === 'boolean') {
      valueType = 'boolean';
      valueStr = String(value);
    } else {
      valueStr = String(value);
    }
    
    return {
      span_id: span.spanId,
      key,
      value: valueStr,
      value_type: valueType,
    };
  });
  
  if (attributeInserts.length > 0) {
    const { error: attrError } = await supabase
      .from('span_attributes')
      .insert(attributeInserts);
    
    if (attrError) {
      edgeLogger.warn('Failed to insert span attributes', {
        spanId: span.spanId,
        error: attrError.message,
      });
    }
  }
  
  // Insert events
  for (let i = 0; i < span.events.length; i++) {
    const event = span.events[i];
    
    const { data: eventData, error: eventError } = await supabase
      .from('span_events')
      .insert({
        span_id: span.spanId,
        timestamp: new Date(event.timestamp).toISOString(),
        name: event.name,
        sequence_order: i,
      })
      .select('id')
      .single();
    
    if (eventError || !eventData) {
      edgeLogger.warn('Failed to insert span event', {
        spanId: span.spanId,
        eventName: event.name,
        error: eventError?.message,
      });
      continue;
    }
    
    // Insert event attributes
    if (event.attributes) {
      const eventAttrInserts = Object.entries(event.attributes).map(([key, value]) => {
        let valueType: 'string' | 'number' | 'boolean' = 'string';
        let valueStr: string;
        
        if (typeof value === 'number') {
          valueType = 'number';
          valueStr = String(value);
        } else if (typeof value === 'boolean') {
          valueType = 'boolean';
          valueStr = String(value);
        } else {
          valueStr = String(value);
        }
        
        return {
          span_event_id: eventData.id,
          key,
          value: valueStr,
          value_type: valueType,
        };
      });
      
      if (eventAttrInserts.length > 0) {
        const { error: eventAttrError } = await supabase
          .from('span_event_attributes')
          .insert(eventAttrInserts);
        
        if (eventAttrError) {
          edgeLogger.warn('Failed to insert event attributes', {
            spanId: span.spanId,
            eventName: event.name,
            error: eventAttrError.message,
          });
        }
      }
    }
  }
}

// Fields that should never be logged
const SENSITIVE_FIELDS = [
  'password',
  'token',
  'secret',
  'api_key',
  'apikey',
  'authorization',
  'email',
  'phone',
  'ssn',
  'credit_card',
  'ip_address',
  'session_id'
];

/**
 * Sanitize context to remove sensitive data
 */
export function sanitizeContext(context: LogContext): LogContext {
  const sanitized: LogContext = {};
  
  for (const [key, value] of Object.entries(context)) {
    const lowerKey = key.toLowerCase();
    
    // Skip sensitive fields
    if (SENSITIVE_FIELDS.some(field => lowerKey.includes(field))) {
      sanitized[key] = '[REDACTED]';
      continue;
    }
    
    // Recursively sanitize objects
    if (value && typeof value === 'object' && !Array.isArray(value)) {
      sanitized[key] = sanitizeContext(value as LogContext);
    } else {
      sanitized[key] = value;
    }
  }
  
  return sanitized;
}

/**
 * Format log message with context
 */
function formatLog(level: LogLevel, message: string, context?: LogContext): string {
  const timestamp = new Date().toISOString();
  const sanitizedContext = context ? sanitizeContext(context) : {};
  
  return JSON.stringify({
    timestamp,
    level,
    message,
    ...sanitizedContext
  });
}

export const edgeLogger = {
  info: (message: string, context?: LogContext): void => {
    console.info(formatLog('info', message, context));
  },
  
  warn: (message: string, context?: LogContext): void => {
    console.warn(formatLog('warn', message, context));
  },
  
  error: (message: string, context?: LogContext): void => {
    console.error(formatLog('error', message, context));
  },
  
  debug: (message: string, context?: LogContext): void => {
    console.debug(formatLog('debug', message, context));
  }
};
