/**
 * Alerting Service
 *
 * Centralized alerting system for system health notifications.
 * Supports multiple channels (Slack, email, logs) with configurable severity levels.
 *
 * Features:
 * - Multi-channel support: Slack webhooks, email, structured logs
 * - Severity levels: INFO, WARN, CRITICAL
 * - Rate limiting to prevent alert spam (1 per issue per 5 minutes)
 * - Graceful fallback if channels are not configured
 * - Context enrichment (service, timestamp, correlation ID)
 *
 * @module alerting.service
 */

import { logger } from '../logger';
import { sendEmail } from './email';

// =============================================================================
// Types
// =============================================================================

/**
 * Alert severity levels
 */
export type AlertSeverity = 'INFO' | 'WARN' | 'CRITICAL';

/**
 * Alert context - provides additional information about the alert
 */
export interface AlertContext {
  /** Service or component that generated the alert */
  service: string;
  /** Correlation ID for tracing across requests */
  correlationId?: string;
  /** Additional error details */
  error?: string | Error;
  /** Stack trace (only included in non-production for CRITICAL alerts) */
  stack?: string;
  /** Current value that triggered the alert (for threshold alerts) */
  currentValue?: number | string;
  /** Threshold that was exceeded */
  threshold?: number | string;
  /** Any additional metadata */
  metadata?: Record<string, unknown>;
}

/**
 * Alert payload
 */
export interface Alert {
  /** Unique key for deduplication (rate limiting) */
  key: string;
  /** Alert severity level */
  severity: AlertSeverity;
  /** Short, descriptive title */
  title: string;
  /** Detailed message */
  message: string;
  /** Alert context */
  context: AlertContext;
  /** Timestamp when alert was generated */
  timestamp: Date;
}

/**
 * Alert channel configuration
 */
export interface AlertChannelConfig {
  /** Slack webhook URL */
  slackWebhookUrl?: string;
  /** Email recipients (comma-separated) */
  emailRecipients?: string[];
  /** Minimum severity level for alerts (default: WARN) */
  minSeverity: AlertSeverity;
  /** Whether to log all alerts (default: true) */
  logEnabled: boolean;
}

// =============================================================================
// Configuration
// =============================================================================

/**
 * Get alert channel configuration from environment
 */
function getAlertConfig(): AlertChannelConfig {
  const envMinSeverity = process.env.ALERT_MIN_SEVERITY?.toUpperCase();
  let minSeverity: AlertSeverity = 'WARN';

  if (envMinSeverity === 'INFO' || envMinSeverity === 'WARN' || envMinSeverity === 'CRITICAL') {
    minSeverity = envMinSeverity;
  }

  const emailRecipients = process.env.ALERT_EMAIL_RECIPIENTS
    ? process.env.ALERT_EMAIL_RECIPIENTS.split(',').map((e) => e.trim()).filter(Boolean)
    : undefined;

  return {
    slackWebhookUrl: process.env.ALERT_SLACK_WEBHOOK_URL,
    emailRecipients,
    minSeverity,
    logEnabled: true,
  };
}

// =============================================================================
// Rate Limiting
// =============================================================================

/**
 * In-memory store for alert rate limiting
 * Maps alert key to last sent timestamp
 */
const alertRateLimit = new Map<string, number>();

/**
 * Rate limit window in milliseconds (5 minutes)
 */
const RATE_LIMIT_WINDOW_MS = 5 * 60 * 1000;

/**
 * Maximum entries in rate limit store (prevent memory leak)
 */
const MAX_RATE_LIMIT_ENTRIES = 1000;

/**
 * Check if alert should be rate limited
 * Returns true if alert should be sent, false if rate limited
 */
function shouldSendAlert(key: string): boolean {
  const now = Date.now();
  const lastSent = alertRateLimit.get(key);

  if (lastSent && now - lastSent < RATE_LIMIT_WINDOW_MS) {
    return false;
  }

  // Clean up old entries if needed
  if (alertRateLimit.size >= MAX_RATE_LIMIT_ENTRIES) {
    const cutoff = now - RATE_LIMIT_WINDOW_MS;
    for (const [k, v] of alertRateLimit.entries()) {
      if (v < cutoff) {
        alertRateLimit.delete(k);
      }
    }
  }

  // Update last sent time
  alertRateLimit.set(key, now);
  return true;
}

// =============================================================================
// Severity Helpers
// =============================================================================

/**
 * Severity level weights for comparison
 */
const SEVERITY_WEIGHTS: Record<AlertSeverity, number> = {
  INFO: 1,
  WARN: 2,
  CRITICAL: 3,
};

/**
 * Check if alert severity meets minimum threshold
 */
function meetsSeverityThreshold(alertSeverity: AlertSeverity, minSeverity: AlertSeverity): boolean {
  return SEVERITY_WEIGHTS[alertSeverity] >= SEVERITY_WEIGHTS[minSeverity];
}

/**
 * Get emoji for severity level (for Slack messages)
 */
function getSeverityEmoji(severity: AlertSeverity): string {
  switch (severity) {
    case 'INFO':
      return ':information_source:';
    case 'WARN':
      return ':warning:';
    case 'CRITICAL':
      return ':rotating_light:';
    default:
      return ':bell:';
  }
}

/**
 * Get color for severity level (for Slack attachments)
 */
function getSeverityColor(severity: AlertSeverity): string {
  switch (severity) {
    case 'INFO':
      return '#36a64f'; // Green
    case 'WARN':
      return '#ffcc00'; // Yellow
    case 'CRITICAL':
      return '#ff0000'; // Red
    default:
      return '#808080'; // Gray
  }
}

// =============================================================================
// Channel Senders
// =============================================================================

/**
 * Send alert to Slack via webhook
 */
async function sendSlackAlert(alert: Alert, webhookUrl: string): Promise<boolean> {
  try {
    const fields = [
      {
        title: 'Service',
        value: alert.context.service,
        short: true,
      },
      {
        title: 'Severity',
        value: alert.severity,
        short: true,
      },
    ];

    if (alert.context.correlationId) {
      fields.push({
        title: 'Correlation ID',
        value: alert.context.correlationId,
        short: true,
      });
    }

    if (alert.context.currentValue !== undefined) {
      fields.push({
        title: 'Current Value',
        value: String(alert.context.currentValue),
        short: true,
      });
    }

    if (alert.context.threshold !== undefined) {
      fields.push({
        title: 'Threshold',
        value: String(alert.context.threshold),
        short: true,
      });
    }

    if (alert.context.error) {
      const errorMessage =
        alert.context.error instanceof Error
          ? alert.context.error.message
          : String(alert.context.error);
      fields.push({
        title: 'Error',
        value: errorMessage.slice(0, 200), // Truncate long errors
        short: false,
      });
    }

    const payload = {
      text: `${getSeverityEmoji(alert.severity)} *${alert.title}*`,
      attachments: [
        {
          color: getSeverityColor(alert.severity),
          text: alert.message,
          fields,
          footer: 'Mawidi Alerting System',
          ts: Math.floor(alert.timestamp.getTime() / 1000).toString(),
        },
      ],
    };

    const response = await fetch(webhookUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
    });

    if (!response.ok) {
      logger.error('[Alerting] Slack webhook failed', undefined, {
        status: response.status,
        statusText: response.statusText,
      });
      return false;
    }

    return true;
  } catch (error) {
    logger.error('[Alerting] Failed to send Slack alert', error);
    return false;
  }
}

/**
 * Send alert via email
 */
async function sendEmailAlert(alert: Alert, recipients: string[]): Promise<boolean> {
  try {
    const severityPrefix =
      alert.severity === 'CRITICAL'
        ? '[CRITICAL]'
        : alert.severity === 'WARN'
          ? '[WARNING]'
          : '[INFO]';

    const subject = `${severityPrefix} ${alert.title}`;

    const contextHtml = Object.entries({
      Service: alert.context.service,
      Severity: alert.severity,
      Timestamp: alert.timestamp.toISOString(),
      'Correlation ID': alert.context.correlationId,
      'Current Value': alert.context.currentValue,
      Threshold: alert.context.threshold,
    })
      .filter(([, v]) => v !== undefined)
      .map(([k, v]) => `<tr><td style="padding:8px;border:1px solid #ddd;"><strong>${k}</strong></td><td style="padding:8px;border:1px solid #ddd;">${v}</td></tr>`)
      .join('');

    const errorHtml = alert.context.error
      ? `<div style="background:#fff3cd;padding:12px;margin-top:16px;border-radius:4px;">
          <strong>Error:</strong><br/>
          <code>${alert.context.error instanceof Error ? alert.context.error.message : String(alert.context.error)}</code>
        </div>`
      : '';

    const html = `
      <div style="font-family:Arial,sans-serif;max-width:600px;margin:0 auto;">
        <div style="background:${getSeverityColor(alert.severity)};color:white;padding:16px;border-radius:4px 4px 0 0;">
          <h2 style="margin:0;">${severityPrefix} System Alert</h2>
        </div>
        <div style="padding:20px;background:#f9f9f9;border:1px solid #ddd;border-top:none;">
          <h3 style="margin-top:0;">${alert.title}</h3>
          <p>${alert.message}</p>
          <table style="width:100%;border-collapse:collapse;margin-top:16px;">
            ${contextHtml}
          </table>
          ${errorHtml}
        </div>
        <div style="padding:12px;background:#f0f0f0;text-align:center;font-size:12px;color:#666;">
          Mawidi Alerting System | ${new Date().toISOString()}
        </div>
      </div>
    `;

    // Send to all recipients
    for (const recipient of recipients) {
      await sendEmail(recipient, {
        subject,
        html,
        text: `Alert: ${alert.title}\n\n${alert.message}`,
      });
    }

    return true;
  } catch (error) {
    logger.error('[Alerting] Failed to send email alert', error);
    return false;
  }
}

/**
 * Log alert using structured logger
 */
function logAlert(alert: Alert): void {
  const logContext = {
    alertKey: alert.key,
    service: alert.context.service,
    correlationId: alert.context.correlationId,
    currentValue: alert.context.currentValue,
    threshold: alert.context.threshold,
    metadata: alert.context.metadata,
  };

  const message = `[ALERT] ${alert.title}: ${alert.message}`;

  switch (alert.severity) {
    case 'INFO':
      logger.info(message, logContext);
      break;
    case 'WARN':
      logger.warn(message, logContext);
      break;
    case 'CRITICAL':
      logger.error(message, alert.context.error, logContext);
      break;
  }
}

// =============================================================================
// Main Alert Function
// =============================================================================

/**
 * Send an alert through configured channels
 *
 * @param alert - Alert to send
 * @returns Object indicating which channels succeeded
 *
 * @example
 * ```typescript
 * await sendAlert({
 *   key: 'circuit-breaker:stripe:open',
 *   severity: 'CRITICAL',
 *   title: 'Circuit Breaker Opened',
 *   message: 'Stripe circuit breaker has opened due to repeated failures',
 *   context: {
 *     service: 'stripe',
 *     error: 'Connection timeout',
 *     currentValue: 5,
 *     threshold: 3,
 *   },
 *   timestamp: new Date(),
 * });
 * ```
 */
export async function sendAlert(
  alert: Omit<Alert, 'timestamp'> & { timestamp?: Date }
): Promise<{
  sent: boolean;
  rateLimited: boolean;
  channels: {
    log: boolean;
    slack: boolean;
    email: boolean;
  };
}> {
  const config = getAlertConfig();
  const fullAlert: Alert = {
    ...alert,
    timestamp: alert.timestamp ?? new Date(),
  };

  const result = {
    sent: false,
    rateLimited: false,
    channels: {
      log: false,
      slack: false,
      email: false,
    },
  };

  // Check severity threshold
  if (!meetsSeverityThreshold(fullAlert.severity, config.minSeverity)) {
    return result;
  }

  // Check rate limiting
  if (!shouldSendAlert(fullAlert.key)) {
    result.rateLimited = true;
    logger.debug('[Alerting] Alert rate limited', { key: fullAlert.key });
    return result;
  }

  // Always log alerts
  if (config.logEnabled) {
    logAlert(fullAlert);
    result.channels.log = true;
    result.sent = true;
  }

  // Send to Slack if configured
  if (config.slackWebhookUrl) {
    result.channels.slack = await sendSlackAlert(fullAlert, config.slackWebhookUrl);
    if (result.channels.slack) {
      result.sent = true;
    }
  }

  // Send email if configured (only for WARN and CRITICAL)
  if (
    config.emailRecipients &&
    config.emailRecipients.length > 0 &&
    (fullAlert.severity === 'WARN' || fullAlert.severity === 'CRITICAL')
  ) {
    result.channels.email = await sendEmailAlert(fullAlert, config.emailRecipients);
    if (result.channels.email) {
      result.sent = true;
    }
  }

  return result;
}

// =============================================================================
// Helper Functions for Common Alert Types
// =============================================================================

/**
 * Send a circuit breaker state change alert
 *
 * @param circuitName - Name of the circuit breaker
 * @param previousState - Previous circuit state
 * @param newState - New circuit state
 * @param failures - Current failure count (optional)
 * @param threshold - Failure threshold that triggered the open (optional)
 * @param resetTimeout - Timeout before recovery attempt in ms (optional)
 */
export async function sendCircuitBreakerAlert(
  circuitName: string,
  previousState: string,
  newState: string,
  failures?: number,
  threshold?: number,
  resetTimeout?: number
): Promise<void> {
  // Only alert on state changes to OPEN or from OPEN to HALF_OPEN
  if (newState !== 'OPEN' && !(previousState === 'OPEN' && newState === 'HALF_OPEN')) {
    return;
  }

  const severity: AlertSeverity = newState === 'OPEN' ? 'CRITICAL' : 'INFO';
  const title =
    newState === 'OPEN'
      ? `Circuit Breaker OPENED: ${circuitName}`
      : `Circuit Breaker Recovering: ${circuitName}`;

  const resetSeconds = resetTimeout ? Math.ceil(resetTimeout / 1000) : undefined;
  const message =
    newState === 'OPEN'
      ? `The ${circuitName} circuit breaker has opened after ${failures ?? 'unknown'} failures (threshold: ${threshold ?? 'unknown'}). Requests will be rejected for ${resetSeconds ?? 'unknown'} seconds until recovery is attempted.`
      : `The ${circuitName} circuit breaker is attempting recovery (HALF_OPEN state).`;

  await sendAlert({
    key: `circuit-breaker:${circuitName}:${newState.toLowerCase()}`,
    severity,
    title,
    message,
    context: {
      service: circuitName,
      currentValue: failures,
      threshold,
      metadata: {
        previousState,
        newState,
        resetTimeoutMs: resetTimeout,
      },
    },
  });
}

/**
 * Send a rate limit threshold alert
 */
export async function sendRateLimitAlert(
  endpoint: string,
  identifier: string,
  context?: Partial<AlertContext>
): Promise<void> {
  await sendAlert({
    key: `rate-limit:${endpoint}:threshold`,
    severity: 'WARN',
    title: `Rate Limit Exceeded: ${endpoint}`,
    message: `Rate limit has been exceeded for endpoint ${endpoint}. This may indicate an attack or misconfigured client.`,
    context: {
      service: 'rate-limiter',
      metadata: {
        endpoint,
        identifierHash: Buffer.from(identifier).toString('base64').slice(0, 8),
      },
      ...context,
    },
  });
}

/**
 * Send a DLQ threshold alert
 */
export async function sendDlqAlert(
  queueName: string,
  dlqCount: number,
  threshold: number,
  context?: Partial<AlertContext>
): Promise<void> {
  const severity: AlertSeverity = dlqCount >= threshold * 2 ? 'CRITICAL' : 'WARN';

  await sendAlert({
    key: `dlq:${queueName}:threshold`,
    severity,
    title: `DLQ Threshold Exceeded: ${queueName}`,
    message: `The Dead-Letter Queue for ${queueName} has ${dlqCount} items, exceeding the threshold of ${threshold}. Manual intervention may be required.`,
    context: {
      service: 'sync-queue',
      currentValue: dlqCount,
      threshold,
      ...context,
    },
  });
}

/**
 * Send a sync queue health alert
 */
export async function sendSyncQueueAlert(
  pending: number,
  failed: number,
  oldestJobAgeMs: number | undefined,
  alertMessage: string,
  context?: Partial<AlertContext>
): Promise<void> {
  // Determine severity based on thresholds
  const severity: AlertSeverity =
    pending >= 50 || failed >= 20 || (oldestJobAgeMs !== undefined && oldestJobAgeMs >= 30 * 60 * 1000)
      ? 'CRITICAL'
      : 'WARN';

  await sendAlert({
    key: `sync-queue:health:${severity.toLowerCase()}`,
    severity,
    title: 'Sync Queue Health Alert',
    message: alertMessage,
    context: {
      service: 'sync-queue',
      metadata: {
        pending,
        failed,
        oldestJobAgeMinutes: oldestJobAgeMs ? Math.round(oldestJobAgeMs / 60000) : undefined,
      },
      ...context,
    },
  });
}

/**
 * Send a test alert (for testing alert configuration)
 */
export async function sendTestAlert(
  severity: AlertSeverity = 'INFO'
): Promise<{
  sent: boolean;
  rateLimited: boolean;
  channels: { log: boolean; slack: boolean; email: boolean };
}> {
  return sendAlert({
    key: `test-alert:${Date.now()}`, // Unique key to bypass rate limiting
    severity,
    title: 'Test Alert',
    message: `This is a test alert sent at ${new Date().toISOString()} to verify the alerting system is working correctly.`,
    context: {
      service: 'alerting-service',
      metadata: {
        test: true,
        environment: process.env.NODE_ENV,
      },
    },
  });
}

// =============================================================================
// Exports
// =============================================================================

const alertingService = {
  sendAlert,
  sendCircuitBreakerAlert,
  sendRateLimitAlert,
  sendDlqAlert,
  sendSyncQueueAlert,
  sendTestAlert,
};

export default alertingService;
