/**
 * N8N Webhook Service
 *
 * Centralized service for N8N webhook operations:
 * - Outbound event dispatch to n8n workflows
 * - Auth token validation
 * - Retry logic with exponential backoff
 * - Webhook event logging
 *
 * @module lib/services/n8n-webhook.service
 */

import { logger } from '@/lib/logger';
import { systemSettingsService } from '@/lib/services/system-settings.service';
import { N8NEventType } from '@/lib/validators/n8n-booking.validator';
import { WEBHOOK_CONFIG } from '@/lib/config/constants';

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

export interface WebhookConfig {
  url: string;
  authToken: string;
  maxRetries: number;
  timeoutMs: number;
  retryDelays: number[];
}

export interface WebhookDispatchResult {
  success: boolean;
  attemptCount: number;
  response?: string;
  error?: string;
  eventId?: string;
}

export interface OutboundEventPayload {
  event: N8NEventType;
  timestamp: string;
  booking_id: string;
  user_id: string;
  organization_id?: string | null;
  data: Record<string, unknown>;
}

// Re-export for convenience
export { N8NEventType };

// =============================================================================
// Service Class
// =============================================================================

class N8NWebhookService {
  private defaultConfig: Partial<WebhookConfig> = {
    maxRetries: WEBHOOK_CONFIG?.MAX_RETRIES || 3,
    timeoutMs: WEBHOOK_CONFIG?.TIMEOUT_MS || 10000,
    retryDelays: WEBHOOK_CONFIG?.RETRY_DELAYS_MS ? [...WEBHOOK_CONFIG.RETRY_DELAYS_MS] : [1000, 2000, 4000],
  };

  /**
   * Get webhook configuration from settings/env
   */
  async getWebhookConfig(): Promise<WebhookConfig> {
    const authToken = await systemSettingsService.getN8NWebhookToken();
    const baseUrl = await systemSettingsService.getN8NBaseUrl();

    return {
      url: process.env.N8N_OUTBOUND_WEBHOOK_URL || baseUrl || '',
      authToken: authToken || '',
      maxRetries: this.defaultConfig.maxRetries!,
      timeoutMs: this.defaultConfig.timeoutMs!,
      retryDelays: [...this.defaultConfig.retryDelays!],
    };
  }

  /**
   * Validate incoming n8n auth token
   */
  async validateAuth(token: string): Promise<boolean> {
    return systemSettingsService.validateN8NToken(token);
  }

  /**
   * Dispatch outbound event to n8n webhook
   * Non-blocking: logs errors but doesn't throw
   */
  async dispatchEvent(
    eventType: N8NEventType,
    bookingId: string,
    userId: string,
    data: Record<string, unknown>,
    organizationId?: string | null
  ): Promise<WebhookDispatchResult> {
    const config = await this.getWebhookConfig();

    // Validate configuration
    if (!config.url) {
      logger.debug('[N8NWebhook] Outbound webhook URL not configured, skipping dispatch', {
        eventType,
        bookingId,
      });
      return {
        success: false,
        attemptCount: 0,
        error: 'Outbound webhook URL not configured',
      };
    }

    const eventId = `evt_${Date.now()}_${Math.random().toString(36).substring(7)}`;

    const payload: OutboundEventPayload = {
      event: eventType,
      timestamp: new Date().toISOString(),
      booking_id: bookingId,
      user_id: userId,
      organization_id: organizationId,
      data,
    };

    let lastError: string | undefined;
    let attemptCount = 0;

    // Retry loop with exponential backoff
    for (let attempt = 0; attempt < config.maxRetries; attempt++) {
      attemptCount = attempt + 1;

      try {
        // Add delay before retry attempts
        if (attempt > 0) {
          await new Promise((resolve) =>
            setTimeout(resolve, config.retryDelays[attempt - 1] || 4000)
          );
        }

        // Send webhook with timeout
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), config.timeoutMs);

        const headers: Record<string, string> = {
          'Content-Type': 'application/json',
          'X-Webhook-Event': eventType,
          'X-Webhook-Event-Id': eventId,
        };

        // Add auth header if token is configured
        if (config.authToken) {
          headers['Authorization'] = `Bearer ${config.authToken}`;
        }

        const response = await fetch(config.url, {
          method: 'POST',
          headers,
          body: JSON.stringify(payload),
          signal: controller.signal,
        });

        clearTimeout(timeoutId);

        if (response.ok) {
          const responseText = await response.text();

          logger.info('[N8NWebhook] Event dispatched successfully', {
            eventType,
            bookingId,
            eventId,
            attemptCount,
          });

          return {
            success: true,
            attemptCount,
            response: responseText,
            eventId,
          };
        } else {
          lastError = `HTTP ${response.status}: ${response.statusText}`;
          logger.warn('[N8NWebhook] Dispatch attempt failed', {
            eventType,
            bookingId,
            attemptCount,
            error: lastError,
          });
        }
      } catch (error) {
        if (error instanceof Error) {
          if (error.name === 'AbortError') {
            lastError = 'Request timeout';
          } else {
            lastError = error.message;
          }
        } else {
          lastError = 'Unknown error';
        }

        logger.warn('[N8NWebhook] Dispatch attempt failed', {
          eventType,
          bookingId,
          attemptCount,
          error: lastError,
        });
      }
    }

    // All retries exhausted
    logger.error('[N8NWebhook] Event dispatch failed after all attempts', {
      eventType,
      bookingId,
      eventId,
      attemptCount,
      error: lastError,
    });

    return {
      success: false,
      attemptCount,
      error: lastError,
      eventId,
    };
  }

  /**
   * Dispatch booking created event
   */
  async notifyBookingCreated(
    bookingId: string,
    userId: string,
    bookingData: Record<string, unknown>,
    organizationId?: string | null
  ): Promise<WebhookDispatchResult> {
    return this.dispatchEvent(
      N8NEventType.BOOKING_CREATED,
      bookingId,
      userId,
      bookingData,
      organizationId
    );
  }

  /**
   * Dispatch booking updated event (reschedule, status change)
   */
  async notifyBookingUpdated(
    bookingId: string,
    userId: string,
    updateData: Record<string, unknown>,
    organizationId?: string | null
  ): Promise<WebhookDispatchResult> {
    return this.dispatchEvent(
      N8NEventType.BOOKING_UPDATED,
      bookingId,
      userId,
      updateData,
      organizationId
    );
  }

  /**
   * Dispatch booking cancelled event
   */
  async notifyBookingCancelled(
    bookingId: string,
    userId: string,
    cancellationData: Record<string, unknown>,
    organizationId?: string | null
  ): Promise<WebhookDispatchResult> {
    return this.dispatchEvent(
      N8NEventType.BOOKING_CANCELLED,
      bookingId,
      userId,
      cancellationData,
      organizationId
    );
  }

  /**
   * Dispatch payment received event
   */
  async notifyPaymentReceived(
    bookingId: string,
    userId: string,
    paymentData: Record<string, unknown>,
    organizationId?: string | null
  ): Promise<WebhookDispatchResult> {
    return this.dispatchEvent(
      N8NEventType.PAYMENT_RECEIVED,
      bookingId,
      userId,
      paymentData,
      organizationId
    );
  }

  /**
   * Dispatch payment refunded event
   */
  async notifyPaymentRefunded(
    bookingId: string,
    userId: string,
    refundData: Record<string, unknown>,
    organizationId?: string | null
  ): Promise<WebhookDispatchResult> {
    return this.dispatchEvent(
      N8NEventType.PAYMENT_REFUNDED,
      bookingId,
      userId,
      refundData,
      organizationId
    );
  }
}

// Export singleton instance
export const n8nWebhookService = new N8NWebhookService();
