import {
  EmailEvent,
  ObservabilityConfig,
  EmailMetrics,
  AuditLog,
} from '../interfaces/observability.interface';
import {
  EmailOptions,
  EmailResponse,
  EmailProviderType,
} from '../interfaces/email-options.interface';

// Simple UUID generator (in production, use a proper UUID library)
function generateUUID(): string {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    const r = (Math.random() * 16) | 0;
    const v = c === 'x' ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

export class ObservabilityService {
  private static instance: ObservabilityService;
  private config: ObservabilityConfig;
  private events: EmailEvent[] = [];
  private metrics: EmailMetrics;
  private auditLogs: AuditLog[] = [];

  private constructor(config: ObservabilityConfig = { enabled: true }) {
    this.config = config;
    this.metrics = this.initializeMetrics();
  }

  static getInstance(config?: ObservabilityConfig): ObservabilityService {
    if (!ObservabilityService.instance) {
      ObservabilityService.instance = new ObservabilityService(config);
    }
    return ObservabilityService.instance;
  }

  private initializeMetrics(): EmailMetrics {
    return {
      totalSent: 0,
      totalFailed: 0,
      successRate: 0,
      averageSendTime: 0,
      providerBreakdown: {
        smtp: { sent: 0, failed: 0, avgTime: 0 },
        sendgrid: { sent: 0, failed: 0, avgTime: 0 },
        ses: { sent: 0, failed: 0, avgTime: 0 },
      },
      last24Hours: {
        sent: 0,
        failed: 0,
        avgTime: 0,
      },
      sesMetrics: {
        quotaUsage: 0,
        bounceRate: 0,
        complaintRate: 0,
        deliveryRate: 0,
        lastQuotaCheck: undefined,
      },
    };
  }

  /**
   * Track email sending attempt
   */
  trackEmailAttempt(
    options: EmailOptions,
    provider: EmailProviderType,
    _startTime: number
  ): string {
    if (!this.config.enabled) return '';

    const eventId = generateUUID();
    const event = this.createEmailEvent(eventId, 'email_sent', provider, options);
    this.events.push(event);
    // Don't log here - wait for completion to log with complete information
    return eventId;
  }

  /**
   * Track email success
   */
  trackEmailSuccess(
    eventId: string,
    response: EmailResponse,
    duration: number,
    provider: EmailProviderType
  ): void {
    if (!this.config.enabled) return;

    const event = this.events.find(e => e.id === eventId);
    if (event) {
      event.messageId = response.messageId;
      event.duration = duration;
      event.type = 'email_sent';

      this.updateMetrics(provider, true, duration);
      this.createAuditLog('email_sent', response, provider);
      this.logEvent(event); // Log the completed event with duration information
    }
  }

  /**
   * Track email failure
   */
  trackEmailFailure(
    eventId: string,
    error: any,
    duration: number,
    provider: EmailProviderType
  ): void {
    if (!this.config.enabled) return;

    const event = this.events.find(e => e.id === eventId);
    if (event) {
      event.type = 'email_failed';
      event.duration = duration;
      event.error = {
        message: error.message || 'Unknown error',
        code: error.code,
        details: error.details,
      };

      this.updateMetrics(provider, false, duration);
      this.createAuditLog('email_failed', { success: false, error }, provider);
      this.logEvent(event); // Log the completed event with duration and error information
    }
  }

  /**
   * Track connection verification
   */
  trackConnectionVerification(
    provider: EmailProviderType,
    success: boolean,
    duration?: number
  ): void {
    if (!this.config.enabled) return;

    const event = this.createConnectionEvent(provider, success, duration);
    this.events.push(event);
    this.logEvent(event);
  }

  /**
   * Track SES quota usage
   */
  trackSesQuotaUsage(quotaData: {
    max24HourSend: number;
    sentLast24Hours: number;
    maxSendRate: number;
  }): void {
    if (!this.config.enabled || !this.metrics.sesMetrics) return;

    const usagePercent = (quotaData.sentLast24Hours / quotaData.max24HourSend) * 100;
    this.metrics.sesMetrics.quotaUsage = usagePercent;
    this.metrics.sesMetrics.lastQuotaCheck = new Date().toISOString();

    // Log quota usage event
    const event: EmailEvent = {
      id: generateUUID(),
      timestamp: new Date().toISOString(),
      type: 'connection_verified',
      provider: 'ses',
      metadata: {
        quotaUsage: usagePercent,
        max24HourSend: quotaData.max24HourSend,
        sentLast24Hours: quotaData.sentLast24Hours,
        maxSendRate: quotaData.maxSendRate,
      },
    };

    this.events.push(event);
    this.logEvent(event);
  }

  /**
   * Track SES sending statistics
   */
  trackSesStatistics(statsData: {
    deliveryAttempts: number;
    bounces: number;
    complaints: number;
    rejects: number;
  }): void {
    if (!this.config.enabled || !this.metrics.sesMetrics) return;

    const totalAttempts = statsData.deliveryAttempts;
    const successfulDeliveries = totalAttempts - statsData.bounces - statsData.complaints - statsData.rejects;

    this.metrics.sesMetrics.deliveryRate = totalAttempts > 0 ? (successfulDeliveries / totalAttempts) * 100 : 0;
    this.metrics.sesMetrics.bounceRate = totalAttempts > 0 ? (statsData.bounces / totalAttempts) * 100 : 0;
    this.metrics.sesMetrics.complaintRate = totalAttempts > 0 ? (statsData.complaints / totalAttempts) * 100 : 0;

    // Log statistics event
    const event: EmailEvent = {
      id: generateUUID(),
      timestamp: new Date().toISOString(),
      type: 'connection_verified',
      provider: 'ses',
      metadata: {
        deliveryRate: this.metrics.sesMetrics.deliveryRate,
        bounceRate: this.metrics.sesMetrics.bounceRate,
        complaintRate: this.metrics.sesMetrics.complaintRate,
        deliveryAttempts: statsData.deliveryAttempts,
        bounces: statsData.bounces,
        complaints: statsData.complaints,
        rejects: statsData.rejects,
      },
    };

    this.events.push(event);
    this.logEvent(event);
  }

  /**
   * Track SES error with specific error type
   */
  trackSesError(
    error: any,
    errorType: 'MessageRejected' | 'MailFromDomainNotVerified' | 'ConfigurationSetDoesNotExist' | 'TemplateDoesNotExist' | 'AccountSendingPaused' | 'SendingPaused' | 'MessageTooLarge' | 'InvalidParameterValue' | 'ValidationError' | 'ThrottlingException' | 'ServiceUnavailable' | 'InternalError' | 'NetworkError' | 'TimeoutError',
    duration: number
  ): void {
    if (!this.config.enabled) return;

    const event: EmailEvent = {
      id: generateUUID(),
      timestamp: new Date().toISOString(),
      type: 'email_failed',
      provider: 'ses',
      duration,
      error: {
        message: error.message || String(error),
        code: errorType,
        details: {
          sesErrorType: errorType,
          originalError: error,
        },
      },
      metadata: {
        errorType,
        duration,
      },
    };

    this.events.push(event);
    this.updateMetrics(EmailProviderType.SES, false, duration);
    this.logEvent(event);
  }

  /**
   * Get current metrics
   */
  getMetrics(): EmailMetrics {
    return { ...this.metrics };
  }

  /**
   * Get SES-specific metrics
   */
  getSesMetrics() {
    return this.metrics.sesMetrics;
  }

  /**
   * Get metrics for a specific provider
   */
  getProviderMetrics(provider: EmailProviderType) {
    let providerKey: 'smtp' | 'sendgrid' | 'ses';
    
    switch (provider) {
      case EmailProviderType.SMTP:
        providerKey = 'smtp';
        break;
      case EmailProviderType.SENDGRID:
        providerKey = 'sendgrid';
        break;
      case EmailProviderType.SES:
        providerKey = 'ses';
        break;
      default:
        providerKey = 'smtp';
    }

    return this.metrics.providerBreakdown[providerKey];
  }

  /**
   * Get events within time range
   */
  getEvents(startTime?: Date, endTime?: Date): EmailEvent[] {
    let filteredEvents = [...this.events];

    if (startTime) {
      filteredEvents = filteredEvents.filter(e => new Date(e.timestamp) >= startTime);
    }

    if (endTime) {
      filteredEvents = filteredEvents.filter(e => new Date(e.timestamp) <= endTime);
    }

    return filteredEvents;
  }

  /**
   * Get audit logs
   */
  getAuditLogs(startTime?: Date, endTime?: Date): AuditLog[] {
    let filteredLogs = [...this.auditLogs];

    if (startTime) {
      filteredLogs = filteredLogs.filter(log => new Date(log.timestamp) >= startTime);
    }

    if (endTime) {
      filteredLogs = filteredLogs.filter(log => new Date(log.timestamp) <= endTime);
    }

    return filteredLogs;
  }

  /**
   * Clear all data (useful for testing)
   */
  clearData(): void {
    this.events = [];
    this.auditLogs = [];
    this.metrics = this.initializeMetrics();
  }

  private sanitizeRecipients(recipients: EmailOptions['to']): string {
    if (!this.config.includeSensitiveData) {
      return '[REDACTED]';
    }

    if (Array.isArray(recipients)) {
      return recipients.map(r => r.email).join(', ');
    }
    return recipients.email;
  }

  private updateMetrics(provider: EmailProviderType, success: boolean, duration: number): void {
    let providerKey: 'smtp' | 'sendgrid' | 'ses';
    
    switch (provider) {
      case EmailProviderType.SMTP:
        providerKey = 'smtp';
        break;
      case EmailProviderType.SENDGRID:
        providerKey = 'sendgrid';
        break;
      case EmailProviderType.SES:
        providerKey = 'ses';
        break;
      default:
        providerKey = 'smtp'; // fallback
    }

    this.metrics.totalSent++;
    if (success) {
      this.metrics.providerBreakdown[providerKey].sent++;
    } else {
      this.metrics.totalFailed++;
      this.metrics.providerBreakdown[providerKey].failed++;
    }

    // Update average send time
    const currentAvg = this.metrics.providerBreakdown[providerKey].avgTime;
    const currentCount =
      this.metrics.providerBreakdown[providerKey].sent +
      this.metrics.providerBreakdown[providerKey].failed;
    this.metrics.providerBreakdown[providerKey].avgTime =
      (currentAvg * (currentCount - 1) + duration) / currentCount;

    // Update overall success rate
    this.metrics.successRate =
      (this.metrics.totalSent - this.metrics.totalFailed) / this.metrics.totalSent;

    // Update 24-hour metrics (simplified - in production you'd want more sophisticated time tracking)
    const now = new Date();
    const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
    const recentEvents = this.events.filter(e => new Date(e.timestamp) >= oneDayAgo);

    this.metrics.last24Hours.sent = recentEvents.filter(e => e.type === 'email_sent').length;
    this.metrics.last24Hours.failed = recentEvents.filter(e => e.type === 'email_failed').length;

    if (recentEvents.length > 0) {
      const recentDurations = recentEvents.filter(e => e.duration).map(e => e.duration!);
      this.metrics.last24Hours.avgTime =
        recentDurations.reduce((a, b) => a + b, 0) / recentDurations.length;
    }

    // Call custom metrics collector if provided
    if (this.config.metricsCollector) {
      this.config.metricsCollector(this.metrics);
    }
  }

  private createAuditLog(
    action: AuditLog['action'],
    response: EmailResponse,
    provider: EmailProviderType
  ): void {
    let providerKey: 'smtp' | 'sendgrid' | 'ses';
    
    switch (provider) {
      case EmailProviderType.SMTP:
        providerKey = 'smtp';
        break;
      case EmailProviderType.SENDGRID:
        providerKey = 'sendgrid';
        break;
      case EmailProviderType.SES:
        providerKey = 'ses';
        break;
      default:
        providerKey = 'smtp'; // fallback
    }

    const auditLog: AuditLog = {
      timestamp: new Date().toISOString(),
      action,
      emailDetails: {
        to: [], // Would be populated from original options
        subject: '', // Would be populated from original options
        provider: providerKey,
        messageId: response.messageId,
      },
      metadata: {
        success: response.success,
        error: response.error,
      },
    };

    this.auditLogs.push(auditLog);
  }

  private logEvent(event: EmailEvent): void {
    if (this.config.customLogger) {
      this.config.customLogger(event);
    } else {
      console.log(`[EMAIL-EVENT] ${event.type.toUpperCase()}: ${event.provider}`, {
        recipient: event.recipient,
        subject: event.subject,
        duration: event.duration,
        error: event.error,
      });
    }
  }

  /**
   * Create email event with common properties
   * @private
   */
  private createEmailEvent(
    eventId: string,
    type: 'email_sent' | 'email_failed',
    provider: EmailProviderType,
    options: EmailOptions
  ): EmailEvent {
    let providerKey: 'smtp' | 'sendgrid' | 'ses';
    
    switch (provider) {
      case EmailProviderType.SMTP:
        providerKey = 'smtp';
        break;
      case EmailProviderType.SENDGRID:
        providerKey = 'sendgrid';
        break;
      case EmailProviderType.SES:
        providerKey = 'ses';
        break;
      default:
        providerKey = 'smtp'; // fallback
    }

    return {
      id: eventId,
      timestamp: new Date().toISOString(),
      type,
      provider: providerKey,
      recipient: this.sanitizeRecipients(options.to),
      subject: options.subject,
      metadata: {
        hasAttachments: !!options.attachments?.length,
        hasHtml: !!options.html,
        hasText: !!options.text,
        ccCount: Array.isArray(options.cc) ? options.cc.length : options.cc ? 1 : 0,
        bccCount: Array.isArray(options.bcc) ? options.bcc.length : options.bcc ? 1 : 0,
      },
    };
  }

  /**
   * Create connection verification event
   * @private
   */
  private createConnectionEvent(
    provider: EmailProviderType,
    success: boolean,
    duration?: number
  ): EmailEvent {
    let providerKey: 'smtp' | 'sendgrid' | 'ses';
    
    switch (provider) {
      case EmailProviderType.SMTP:
        providerKey = 'smtp';
        break;
      case EmailProviderType.SENDGRID:
        providerKey = 'sendgrid';
        break;
      case EmailProviderType.SES:
        providerKey = 'ses';
        break;
      default:
        providerKey = 'smtp'; // fallback
    }

    return {
      id: generateUUID(),
      timestamp: new Date().toISOString(),
      type: success ? 'connection_verified' : 'connection_failed',
      provider: providerKey,
      duration,
      error: success
        ? undefined
        : {
            message: 'Connection verification failed',
            code: 'CONNECTION_FAILED',
          },
    };
  }
}
