/**
 * Log Drain Transport
 *
 * Ships structured JSON logs to an external log drain without
 * blocking the request/response cycle. Supports both generic log drains
 * and Grafana Loki push API. Controlled via environment variables.
 *
 * Loki Push API format: https://grafana.com/docs/loki/latest/api/#push-log-entries-to-loki
 */

import type { StructuredLog } from './logger';
import { shouldShipLog } from './log-sampler';
import { recordLogEntry } from './metrics';

const LOG_DRAIN_URL = process.env.LOG_DRAIN_URL;
const LOG_DRAIN_TOKEN = process.env.LOG_DRAIN_TOKEN;
const LOG_DRAIN_TIMEOUT_MS = parseInt(process.env.LOG_DRAIN_TIMEOUT_MS || '1000', 10);
const LOG_DRAIN_SAMPLE_RATE = parseFloat(process.env.LOG_DRAIN_SAMPLE_RATE || '1');
const LOG_DRAIN_ENABLED_IN_DEV = process.env.LOG_DRAIN_ENABLE_DEV === 'true';
const LOG_DRAIN_TYPE = process.env.LOG_DRAIN_TYPE || 'loki'; // 'loki' or 'generic'

// Loki-specific configuration
const LOKI_URL = process.env.LOKI_URL || 'http://localhost:3100/loki/api/v1/push';
const LOKI_ENABLED = process.env.LOKI_ENABLED === 'true' || process.env.NODE_ENV === 'production';

/**
 * Convert structured log to Loki push API format
 * Reference: https://grafana.com/docs/loki/latest/api/#push-log-entries-to-loki
 */
function convertToLokiFormat(log: StructuredLog): object {
  // Build labels from log metadata
  const labels: Record<string, string> = {
    job: 'mawidi-app',
    service: log.service || 'mawidi-site',
    environment: log.environment || 'development',
    level: log.level,
  };

  // Add optional labels
  if (log.requestId) {
    labels.request_id = log.requestId;
  }
  if (log.correlationId) {
    labels.correlation_id = log.correlationId;
  }

  // Loki timestamp is nanoseconds since epoch
  const timestampNs = new Date(log.timestamp).getTime() * 1000000;

  // Build log line (keep as JSON for structured querying)
  const logLine = JSON.stringify({
    message: log.message,
    context: log.context,
    ...(log.error && { error: log.error }),
  });

  return {
    streams: [
      {
        stream: labels,
        values: [[timestampNs.toString(), logLine]],
      },
    ],
  };
}

/**
 * Send log to Loki
 */
async function sendToLoki(log: StructuredLog): Promise<void> {
  if (!LOKI_ENABLED) {
    return;
  }

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), LOG_DRAIN_TIMEOUT_MS);

  try {
    const lokiPayload = convertToLokiFormat(log);
    await fetch(LOKI_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...(LOG_DRAIN_TOKEN ? { Authorization: `Bearer ${LOG_DRAIN_TOKEN}` } : {}),
      },
      body: JSON.stringify(lokiPayload),
      signal: controller.signal,
    });
  } catch {
    // Intentionally ignore; log drain failures should not disrupt the app
  } finally {
    clearTimeout(timeout);
  }
}

/**
 * Send log to generic log drain (original implementation)
 */
async function sendToGenericDrain(log: StructuredLog): Promise<void> {
  const isProduction = process.env.NODE_ENV === 'production';
  const shouldSend =
    LOG_DRAIN_URL &&
    LOG_DRAIN_SAMPLE_RATE > 0 &&
    (isProduction || LOG_DRAIN_ENABLED_IN_DEV) &&
    Math.random() <= LOG_DRAIN_SAMPLE_RATE;

  if (!shouldSend) {
    return;
  }

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), LOG_DRAIN_TIMEOUT_MS);

  try {
    await fetch(LOG_DRAIN_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...(LOG_DRAIN_TOKEN ? { Authorization: `Bearer ${LOG_DRAIN_TOKEN}` } : {}),
      },
      body: JSON.stringify(log),
      signal: controller.signal,
    });
  } catch {
    // Intentionally ignore; log drain failures should not disrupt the app
  } finally {
    clearTimeout(timeout);
  }
}

/**
 * Send a structured log to the configured drain.
 * Best-effort: timeouts and errors are swallowed to avoid impacting the app.
 *
 * Supports both Loki and generic log drains.
 */
export async function sendToLogDrain(log: StructuredLog): Promise<void> {
  // Track log level distribution for metrics dashboard
  recordLogEntry(log.level);

  // Per-level sampling: always ship errors/fatal/security, sample info/debug
  if (!shouldShipLog(log)) {
    return;
  }

  // Send to both if configured (they run in parallel, failures are independent)
  const promises: Promise<void>[] = [];

  if (LOG_DRAIN_TYPE === 'loki' || LOKI_ENABLED) {
    promises.push(sendToLoki(log));
  }

  if (LOG_DRAIN_TYPE === 'generic' && LOG_DRAIN_URL) {
    promises.push(sendToGenericDrain(log));
  }

  // Wait for all drains (but don't fail if any do)
  await Promise.allSettled(promises);
}
