import crypto from 'crypto';
import fetch from 'node-fetch';
import { getDb } from '../db.ts';
import { webhookConfigs, webhookDeliveries } from '../../drizzle/schema.ts';
import { eq } from 'drizzle-orm';

interface WebhookPayload {
  eventType: string;
  timestamp: string;
  data: any;
}

class WebhookManager {
  /**
   * Send webhook to configured endpoint with retry logic
   */
  async sendWebhook(
    webhookConfigId: number,
    eventType: string,
    payload: any
  ): Promise<boolean> {
    const db = await getDb();
    if (!db) {
      console.error('[Webhooks] Database unavailable');
      return false;
    }

    try {
      // Get webhook config
      const config = await db.query.webhookConfigs.findFirst({
        where: eq(webhookConfigs.id, webhookConfigId),
      });

      if (!config || !config.isActive) {
        console.log(`[Webhooks] Webhook ${webhookConfigId} not found or inactive`);
        return false;
      }

      // Check if webhook subscribes to this event
      const subscribedEvents = Array.isArray(config.events) ? config.events : [];
      if (!subscribedEvents.includes(eventType) && !subscribedEvents.includes('*')) {
        return false;
      }

      const webhookPayload: WebhookPayload = {
        eventType,
        timestamp: new Date().toISOString(),
        data: payload,
      };

      // Create signature
      const signature = this.createSignature(webhookPayload, config.secret);

      // Prepare headers
      const headers: Record<string, string> = {
        'Content-Type': 'application/json',
        'X-Webhook-Signature': signature,
        'X-Webhook-Event': eventType,
        'X-Webhook-Timestamp': webhookPayload.timestamp,
        ...((config.headers as Record<string, string>) || {}),
      };

      // Send webhook
      const response = await fetch(config.url, {
        method: 'POST',
        headers,
        body: JSON.stringify(webhookPayload),
        timeout: (config.timeout || 30) * 1000,
      });

      const responseBody = await response.text();

      // Log delivery
      await db.insert(webhookDeliveries).values({
        webhookConfigId,
        eventType,
        payload: webhookPayload,
        statusCode: response.status,
        responseBody,
        deliveredAt: response.ok ? new Date() : undefined,
      });

      if (!response.ok) {
        console.warn(`[Webhooks] Webhook ${webhookConfigId} failed with status ${response.status}`);
        await this.scheduleRetry(db, webhookConfigId, eventType, webhookPayload, 1, config.retryDelay || 300);
        return false;
      }

      console.log(`[Webhooks] Webhook ${webhookConfigId} delivered successfully`);
      return true;

    } catch (error) {
      console.error(`[Webhooks] Error sending webhook ${webhookConfigId}:`, error);
      
      // Log error and schedule retry
      const db = await getDb();
      if (db) {
        await db.insert(webhookDeliveries).values({
          webhookConfigId,
          eventType,
          payload: { eventType, timestamp: new Date().toISOString(), data: payload },
          error: String(error),
          attemptNumber: 1,
        });

        const config = await db.query.webhookConfigs.findFirst({
          where: eq(webhookConfigs.id, webhookConfigId),
        });
        
        if (config) {
          await this.scheduleRetry(db, webhookConfigId, eventType, payload, 1, config.retryDelay || 300);
        }
      }
      
      return false;
    }
  }

  /**
   * Broadcast event to all subscribed webhooks
   */
  async broadcastEvent(eventType: string, payload: any): Promise<void> {
    const db = await getDb();
    if (!db) {
      console.error('[Webhooks] Database unavailable');
      return;
    }

    try {
      // Get all active webhooks
      const configs = await db
        .select()
        .from(webhookConfigs)
        .where(eq(webhookConfigs.isActive, 1));

      for (const config of configs) {
        // Check if webhook subscribes to this event
        const subscribedEvents = Array.isArray(config.events) ? config.events : [];
        if (subscribedEvents.includes(eventType) || subscribedEvents.includes('*')) {
          // Send webhook asynchronously
          this.sendWebhook(config.id, eventType, payload).catch(err => {
            console.error(`[Webhooks] Error broadcasting to webhook ${config.id}:`, err);
          });
        }
      }
    } catch (error) {
      console.error('[Webhooks] Error broadcasting event:', error);
    }
  }

  /**
   * Schedule webhook retry
   */
  private async scheduleRetry(
    db: any,
    webhookConfigId: number,
    eventType: string,
    payload: any,
    attemptNumber: number,
    retryDelay: number
  ): Promise<void> {
    const config = await db.query.webhookConfigs.findFirst({
      where: eq(webhookConfigs.id, webhookConfigId),
    });

    if (!config || attemptNumber >= (config.retryCount || 3)) {
      console.log(`[Webhooks] Webhook ${webhookConfigId} max retries reached`);
      return;
    }

    const nextRetryAt = new Date(Date.now() + retryDelay * 1000);

    await db.insert(webhookDeliveries).values({
      webhookConfigId,
      eventType,
      payload,
      attemptNumber: attemptNumber + 1,
      nextRetryAt,
    });

    console.log(`[Webhooks] Scheduled retry for webhook ${webhookConfigId} at ${nextRetryAt.toISOString()}`);
  }

  /**
   * Process pending webhook retries
   */
  async processPendingRetries(): Promise<void> {
    const db = await getDb();
    if (!db) return;

    try {
      // Get pending retries
      const pending = await db
        .select()
        .from(webhookDeliveries)
        .where((wd) => {
          return wd.nextRetryAt && wd.nextRetryAt <= new Date();
        });

      for (const delivery of pending) {
        const config = await db.query.webhookConfigs.findFirst({
          where: eq(webhookConfigs.id, delivery.webhookConfigId),
        });

        if (config) {
          await this.sendWebhook(
            delivery.webhookConfigId,
            delivery.eventType,
            delivery.payload
          );
        }
      }
    } catch (error) {
      console.error('[Webhooks] Error processing retries:', error);
    }
  }

  /**
   * Create HMAC signature for webhook
   */
  private createSignature(payload: any, secret: string): string {
    const message = JSON.stringify(payload);
    return crypto
      .createHmac('sha256', secret)
      .update(message)
      .digest('hex');
  }

  /**
   * Verify webhook signature
   */
  verifySignature(payload: string, signature: string, secret: string): boolean {
    const expectedSignature = crypto
      .createHmac('sha256', secret)
      .update(payload)
      .digest('hex');

    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    );
  }
}

export const webhookManager = new WebhookManager();

// Start processing retries every 5 minutes
setInterval(() => {
  webhookManager.processPendingRetries().catch(err => {
    console.error('[Webhooks] Error in retry processor:', err);
  });
}, 5 * 60 * 1000);
