import axios, { AxiosError } from 'axios';
import Bull from 'bull';
import { getAuthenticatedClient } from '../../scripts/utils/directus-client';

// Import Directus SDK functions
const directusSdk = require('@directus/sdk');
const createItem = directusSdk.createItem as any;
const readItems = directusSdk.readItems as any;
const updateItem = directusSdk.updateItem as any;
const deleteItem = directusSdk.deleteItem as any;

/**
 * Webhook Configuration Interface
 */
export interface WebhookConfig {
  id: string;
  url: string;
  events: string[];
  headers?: Record<string, string>;
  secret?: string;
  active: boolean;
  retryConfig?: {
    maxRetries: number;
    retryDelay: number;
    backoffMultiplier: number;
  };
  created_at: string;
  updated_at: string;
}

/**
 * Webhook Payload Interface
 */
export interface WebhookPayload {
  event: string;
  taskId: string;
  timestamp: string;
  data: {
    previousStatus?: string;
    currentStatus: string;
    task: any;
    metadata?: Record<string, any>;
  };
  signature?: string;
}

/**
 * Webhook Delivery Result
 */
export interface WebhookDeliveryResult {
  webhookId: string;
  success: boolean;
  statusCode?: number;
  response?: any;
  error?: string;
  attempts: number;
  timestamp: string;
}

/**
 * Webhook Service
 * Manages webhook registration, triggering, and delivery
 */
export class WebhookService {
  private client: any;
  private webhookQueue: Bull.Queue;
  private webhooks: Map<string, WebhookConfig> = new Map();

  constructor() {
    this.initializeClient();
    this.initializeQueue();
    this.loadWebhooks();
  }

  private async initializeClient() {
    this.client = await getAuthenticatedClient();
  }

  private initializeQueue() {
    // Initialize Bull queue for webhook processing
    this.webhookQueue = new Bull('webhook-queue', {
      redis: {
        host: process.env.REDIS_HOST || 'localhost',
        port: parseInt(process.env.REDIS_PORT || '6379'),
        password: process.env.REDIS_PASSWORD
      },
      defaultJobOptions: {
        removeOnComplete: true,
        removeOnFail: false,
        attempts: 3,
        backoff: {
          type: 'exponential',
          delay: 2000
        }
      }
    });

    // Process webhook jobs
    this.webhookQueue.process(async (job) => {
      return await this.processWebhookJob(job.data);
    });

    // Handle failed jobs
    this.webhookQueue.on('failed', (job, err) => {
      console.error(`Webhook job ${job.id} failed:`, err);
      this.logDeliveryFailure(job.data.webhookId, err);
    });
  }

  /**
   * Load webhooks from database
   */
  private async loadWebhooks() {
    if (!this.client) await this.initializeClient();

    try {
      const webhooks = await this.client.request(
        readItems('workflow_webhooks', {
          filter: { active: { _eq: true } }
        })
      );

      webhooks.forEach((webhook: WebhookConfig) => {
        this.webhooks.set(webhook.id, webhook);
      });

      console.log(`Loaded ${webhooks.length} active webhooks`);
    } catch (error) {
      console.error('Failed to load webhooks:', error);
    }
  }

  /**
   * Register a new webhook
   */
  async registerWebhook(config: Omit<WebhookConfig, 'id' | 'created_at' | 'updated_at'>): Promise<WebhookConfig> {
    if (!this.client) await this.initializeClient();

    try {
      // Validate webhook URL
      await this.validateWebhookUrl(config.url);

      // Create webhook in database
      const webhook = await this.client.request(
        createItem('workflow_webhooks', {
          ...config,
          created_at: new Date().toISOString(),
          updated_at: new Date().toISOString()
        })
      );

      // Add to local cache
      this.webhooks.set(webhook.id, webhook);

      return webhook;
    } catch (error: any) {
      throw new Error(`Failed to register webhook: ${error.message}`);
    }
  }

  /**
   * Update webhook configuration
   */
  async updateWebhook(id: string, updates: Partial<WebhookConfig>): Promise<WebhookConfig> {
    if (!this.client) await this.initializeClient();

    try {
      const webhook = await this.client.request(
        updateItem('workflow_webhooks', id, {
          ...updates,
          updated_at: new Date().toISOString()
        })
      );

      // Update local cache
      if (webhook.active) {
        this.webhooks.set(id, webhook);
      } else {
        this.webhooks.delete(id);
      }

      return webhook;
    } catch (error: any) {
      throw new Error(`Failed to update webhook: ${error.message}`);
    }
  }

  /**
   * Delete a webhook
   */
  async deleteWebhook(id: string): Promise<void> {
    if (!this.client) await this.initializeClient();

    try {
      await this.client.request(deleteItem('workflow_webhooks', id));
      this.webhooks.delete(id);
    } catch (error: any) {
      throw new Error(`Failed to delete webhook: ${error.message}`);
    }
  }

  /**
   * Trigger webhooks for an event
   */
  async triggerWebhooks(event: string, taskId: string, data: any): Promise<void> {
    const relevantWebhooks = Array.from(this.webhooks.values()).filter(
      webhook => webhook.active && webhook.events.includes(event)
    );

    if (relevantWebhooks.length === 0) {
      console.log(`No webhooks registered for event: ${event}`);
      return;
    }

    console.log(`Triggering ${relevantWebhooks.length} webhooks for event: ${event}`);

    // Create payload
    const payload: WebhookPayload = {
      event,
      taskId,
      timestamp: new Date().toISOString(),
      data
    };

    // Queue webhook deliveries
    for (const webhook of relevantWebhooks) {
      await this.queueWebhookDelivery(webhook, payload);
    }
  }

  /**
   * Queue webhook delivery
   */
  private async queueWebhookDelivery(webhook: WebhookConfig, payload: WebhookPayload): Promise<void> {
    // Add signature if secret is configured
    if (webhook.secret) {
      payload.signature = this.generateSignature(payload, webhook.secret);
    }

    // Add to queue
    await this.webhookQueue.add({
      webhookId: webhook.id,
      webhook,
      payload,
      attempt: 1
    });
  }

  /**
   * Process webhook job
   */
  private async processWebhookJob(jobData: any): Promise<WebhookDeliveryResult> {
    const { webhookId, webhook, payload, attempt } = jobData;

    try {
      // Prepare headers
      const headers = {
        'Content-Type': 'application/json',
        'X-Webhook-Event': payload.event,
        'X-Webhook-Timestamp': payload.timestamp,
        'X-Webhook-Attempt': attempt.toString(),
        ...webhook.headers
      };

      if (payload.signature) {
        headers['X-Webhook-Signature'] = payload.signature;
      }

      // Send webhook
      const response = await axios.post(webhook.url, payload, {
        headers,
        timeout: 30000, // 30 second timeout
        validateStatus: () => true // Don't throw on non-2xx status
      });

      // Check if successful
      const success = response.status >= 200 && response.status < 300;

      const result: WebhookDeliveryResult = {
        webhookId,
        success,
        statusCode: response.status,
        response: response.data,
        attempts: attempt,
        timestamp: new Date().toISOString()
      };

      // Log delivery
      await this.logDelivery(result);

      if (!success && attempt < (webhook.retryConfig?.maxRetries || 3)) {
        throw new Error(`Webhook delivery failed with status ${response.status}`);
      }

      return result;
    } catch (error: any) {
      const result: WebhookDeliveryResult = {
        webhookId,
        success: false,
        error: error.message,
        attempts: attempt,
        timestamp: new Date().toISOString()
      };

      // Log failure
      await this.logDelivery(result);

      // Retry if configured
      if (attempt < (webhook.retryConfig?.maxRetries || 3)) {
        const delay = this.calculateRetryDelay(attempt, webhook.retryConfig);
        
        await this.webhookQueue.add(
          {
            webhookId,
            webhook,
            payload,
            attempt: attempt + 1
          },
          { delay }
        );
      }

      throw error;
    }
  }

  /**
   * Calculate retry delay with exponential backoff
   */
  private calculateRetryDelay(attempt: number, retryConfig?: any): number {
    const baseDelay = retryConfig?.retryDelay || 2000;
    const multiplier = retryConfig?.backoffMultiplier || 2;
    return baseDelay * Math.pow(multiplier, attempt - 1);
  }

  /**
   * Generate webhook signature
   */
  private generateSignature(payload: WebhookPayload, secret: string): string {
    const crypto = require('crypto');
    const hmac = crypto.createHmac('sha256', secret);
    hmac.update(JSON.stringify(payload));
    return hmac.digest('hex');
  }

  /**
   * Validate webhook URL
   */
  private async validateWebhookUrl(url: string): Promise<void> {
    try {
      const response = await axios.head(url, { timeout: 5000 });
      if (response.status >= 400) {
        throw new Error(`Webhook URL returned status ${response.status}`);
      }
    } catch (error: any) {
      if (error.code === 'ENOTFOUND') {
        throw new Error('Webhook URL host not found');
      }
      if (error.code === 'ETIMEDOUT') {
        throw new Error('Webhook URL validation timed out');
      }
      // Allow other errors to pass (URL might not support HEAD)
    }
  }

  /**
   * Log webhook delivery
   */
  private async logDelivery(result: WebhookDeliveryResult): Promise<void> {
    if (!this.client) await this.initializeClient();

    try {
      await this.client.request(
        createItem('webhook_delivery_logs', {
          webhook_id: result.webhookId,
          success: result.success,
          status_code: result.statusCode,
          response: result.response ? JSON.stringify(result.response) : null,
          error: result.error,
          attempts: result.attempts,
          delivered_at: result.timestamp
        })
      );
    } catch (error) {
      console.error('Failed to log webhook delivery:', error);
    }
  }

  /**
   * Log webhook delivery failure
   */
  private async logDeliveryFailure(webhookId: string, error: Error): Promise<void> {
    await this.logDelivery({
      webhookId,
      success: false,
      error: error.message,
      attempts: 0,
      timestamp: new Date().toISOString()
    });
  }

  /**
   * Get webhook delivery history
   */
  async getDeliveryHistory(webhookId: string, limit: number = 100): Promise<any[]> {
    if (!this.client) await this.initializeClient();

    try {
      const logs = await this.client.request(
        readItems('webhook_delivery_logs', {
          filter: { webhook_id: { _eq: webhookId } },
          limit,
          sort: ['-delivered_at']
        })
      );

      return logs;
    } catch (error: any) {
      throw new Error(`Failed to fetch delivery history: ${error.message}`);
    }
  }

  /**
   * Test webhook endpoint
   */
  async testWebhook(webhookId: string): Promise<WebhookDeliveryResult> {
    const webhook = this.webhooks.get(webhookId);
    if (!webhook) {
      throw new Error('Webhook not found');
    }

    const testPayload: WebhookPayload = {
      event: 'test',
      taskId: 'test-task-id',
      timestamp: new Date().toISOString(),
      data: {
        currentStatus: 'test',
        task: {
          id: 'test-task-id',
          title: 'Test Task',
          description: 'This is a test webhook delivery'
        }
      }
    };

    return await this.processWebhookJob({
      webhookId,
      webhook,
      payload: testPayload,
      attempt: 1
    });
  }

  /**
   * Get all registered webhooks
   */
  async getWebhooks(filter?: { active?: boolean; event?: string }): Promise<WebhookConfig[]> {
    let webhooks = Array.from(this.webhooks.values());

    if (filter) {
      if (filter.active !== undefined) {
        webhooks = webhooks.filter(w => w.active === filter.active);
      }
      if (filter.event) {
        webhooks = webhooks.filter(w => w.events.includes(filter.event));
      }
    }

    return webhooks;
  }

  /**
   * Clean up service
   */
  async cleanup(): Promise<void> {
    await this.webhookQueue.close();
    this.webhooks.clear();
  }
}

// Export singleton instance
export const webhookService = new WebhookService();