/**
 * Webhook Registration Service
 *
 * Manages webhook registrations on external email provider platforms
 * when automation workflows are published or paused/archived.
 *
 * When a workflow goes active:
 *   1. Determines which provider events to listen for based on trigger type
 *   2. Registers a webhook on the provider platform pointing to our webhook endpoint
 *   3. Stores the webhook ID in the EmailIntegration document for later cleanup
 *
 * When a workflow is paused/archived:
 *   1. Checks if any other active workflows use the same provider + event type
 *   2. If not, removes the webhook from the provider platform
 */

import { AutomationWorkflow } from '../../models/AutomationWorkflow';
import { emailIntegrationService } from '../email/EmailIntegrationService';

// Webhook base URL for our endpoints
const WEBHOOK_BASE_URL = process.env.WEBHOOK_BASE_URL || process.env.BACKEND_URL || 'http://localhost:3101';

/**
 * Whether a base URL is publicly reachable by an external provider. Providers
 * (Brevo, etc.) reject callback URLs pointing at localhost/loopback/.local, so
 * we detect that up front instead of making a guaranteed-failing API call.
 */
function isPubliclyReachableUrl(url: string): boolean {
  if (!url) return false;
  try {
    const host = new URL(url).hostname.toLowerCase();
    if (['localhost', '127.0.0.1', '0.0.0.0', '::1'].includes(host)) return false;
    if (host.endsWith('.local')) return false;
    return true;
  } catch {
    return false;
  }
}

// Map trigger types to provider webhook events
const TRIGGER_TO_EVENTS: Record<string, Record<string, string[]>> = {
  trigger_subscribed: {
    brevo: ['listAddition'],
    mailchimp: ['subscribe'],
    zoho: ['listAdded'],
  },
  trigger_email_opened: {
    brevo: ['uniqueOpened'],
    mailchimp: ['open'],
    zoho: ['email_open'],
  },
  trigger_link_clicked: {
    brevo: ['clicked'],
    mailchimp: ['click'],
    zoho: ['email_click'],
  },
  trigger_tag_added: {
    brevo: ['contactUpdated'],
    mailchimp: ['profile_update'],
    zoho: ['contact_updated'],
  },
  trigger_tag_removed: {
    brevo: ['contactUpdated'],
    mailchimp: ['profile_update'],
    zoho: ['contact_updated'],
  },
  trigger_form_submitted: {
    brevo: ['listAddition'],
    mailchimp: ['subscribe'],
    zoho: ['listAdded'],
  },
  trigger_purchase_made: {
    brevo: ['uniqueOpened', 'clicked'], // Brevo doesn't have purchase event; use email events
    mailchimp: ['open', 'click'],
    zoho: ['email_open', 'email_click'],
  },
};

class WebhookRegistrationService {
  /**
   * Register webhooks on the provider platform when a workflow is published.
   * Creates a webhook for each event type the workflow's trigger needs.
   */
  async registerWebhooks(workflow: any): Promise<void> {
    const { companyId, trigger, settings } = workflow;
    const workflowId = workflow._id?.toString();
    const provider = settings?.platform;
    if (!provider || !trigger?.type) {
      console.log('[WebhookReg] No platform or trigger type, skipping webhook registration');
      await this.setRegistrationStatus(workflowId, { status: 'not_required', provider });
      return;
    }

    const events = TRIGGER_TO_EVENTS[trigger.type]?.[provider];
    if (!events || events.length === 0) {
      console.log(`[WebhookReg] No mapped events for trigger ${trigger.type} on ${provider}`);
      await this.setRegistrationStatus(workflowId, { status: 'not_required', provider });
      return;
    }

    // A provider can't call a localhost/loopback URL. Skip the guaranteed 400
    // ("Enter valid notify url") and surface an actionable message instead.
    if (!isPubliclyReachableUrl(WEBHOOK_BASE_URL)) {
      console.warn(`[WebhookReg] WEBHOOK_BASE_URL "${WEBHOOK_BASE_URL}" is not publicly reachable — skipping ${provider} webhook registration`);
      await this.setRegistrationStatus(workflowId, {
        status: 'failed',
        provider,
        error: `Trigger webhook not registered: the callback URL "${WEBHOOK_BASE_URL}" is not publicly reachable, so ${provider} rejects it. Set WEBHOOK_BASE_URL to a public HTTPS URL (an ngrok tunnel in dev, or your API domain in production) and re-activate the workflow.`,
      });
      return;
    }

    try {
      const providerInstance = await emailIntegrationService.getProvider(companyId, provider);
      if (!providerInstance) {
        console.warn(`[WebhookReg] Provider ${provider} not connected for company ${companyId}`);
        await this.setRegistrationStatus(workflowId, {
          status: 'failed',
          provider,
          error: `Provider "${provider}" is not connected. Connect it in Settings → Email Integration, then re-activate the workflow.`,
        });
        return;
      }

      // Per-company secret token so inbound webhooks can be verified (the
      // ?companyId= param alone is spoofable). Appended to the callback URL.
      const secret = await emailIntegrationService.ensureWebhookSecret(companyId, provider);
      const webhookUrl = `${WEBHOOK_BASE_URL}/webhooks/${provider}?companyId=${companyId}&token=${secret}`;

      // Check if a webhook for this provider + company already exists
      const existingWebhooks = await providerInstance.getWebhooks();
      const existingWebhook = existingWebhooks.find(
        (w: any) => w.url === webhookUrl || w.url?.includes(`companyId=${companyId}`)
      );

      if (existingWebhook) {
        // If the existing webhook already carries a verification token, keep it.
        if (existingWebhook.url?.includes('token=')) {
          console.log(`[WebhookReg] Webhook already exists for ${provider} company ${companyId}, id: ${existingWebhook.id}`);
          await this.setRegistrationStatus(workflowId, {
            status: 'registered',
            provider,
            webhookId: String(existingWebhook.id),
            events,
          });
          return;
        }
        // Legacy token-less webhook — remove it so we can recreate a verified one.
        console.log(`[WebhookReg] Migrating legacy token-less webhook for ${provider} company ${companyId}, id: ${existingWebhook.id}`);
        try {
          const legacyId = typeof existingWebhook.id === 'string'
            ? parseInt(existingWebhook.id, 10)
            : existingWebhook.id;
          await providerInstance.deleteWebhook(legacyId);
        } catch (delErr: any) {
          console.warn(`[WebhookReg] Failed to remove legacy webhook ${existingWebhook.id}:`, delErr.message);
          // Proceed anyway — creating the verified webhook is more important.
        }
      }

      // Register new webhook
      // Use 'marketing' type for all providers since automation triggers
      // (list subscription, email opened, etc.) are marketing events.
      // Brevo separates webhooks into 'marketing' and 'transactional' types;
      // our automation events (listAddition, uniqueOpened, clicked) are marketing.
      const result = await providerInstance.createWebhook({
        url: webhookUrl,
        events,
        description: `AI-CMO Automation - Company ${companyId}`,
        type: 'marketing',
      });

      console.log(`[WebhookReg] Registered webhook for ${provider} company ${companyId}, id: ${result.id}`);

      // Store webhook ID in integration document for cleanup
      await this.storeWebhookId(companyId, provider, result.id);
      await this.setRegistrationStatus(workflowId, {
        status: 'registered',
        provider,
        webhookId: String(result.id),
        events,
      });
    } catch (error: any) {
      console.error(`[WebhookReg] Failed to register webhook for ${provider}:`, error.message);
      await this.setRegistrationStatus(workflowId, {
        status: 'failed',
        provider,
        error: error.message || 'Webhook registration failed',
      });
      // Don't throw - webhook registration failure shouldn't block workflow publish
    }
  }

  /**
   * Record the webhook registration outcome on the workflow document so a silent
   * failure (an "active" workflow whose provider webhook didn't register) is
   * visible to the API/UI instead of looking healthy.
   */
  private async setRegistrationStatus(
    workflowId: string | undefined,
    data: {
      status: 'not_required' | 'registered' | 'failed';
      provider?: string;
      webhookId?: string;
      events?: string[];
      error?: string;
    }
  ): Promise<void> {
    if (!workflowId) return;
    try {
      await AutomationWorkflow.updateOne(
        { _id: workflowId },
        {
          $set: {
            webhookRegistration: {
              status: data.status,
              provider: data.provider,
              webhookId: data.webhookId,
              events: data.events,
              error: data.error,
              updatedAt: new Date(),
            },
          },
        }
      );
    } catch (err: any) {
      console.warn('[WebhookReg] Failed to record registration status:', err.message);
    }
  }

  /**
   * Remove webhooks from the provider platform when a workflow is paused/archived.
   * Only removes the webhook if no other active workflows need it.
   */
  async unregisterWebhooks(workflow: any): Promise<void> {
    const { companyId, trigger, settings } = workflow;
    const provider = settings?.platform;
    if (!provider || !trigger?.type) {
      return;
    }

    try {
      // Check if any other active workflows use the same provider
      const otherActiveWorkflows = await AutomationWorkflow.countDocuments({
        companyId,
        'settings.platform': provider,
        status: 'active',
        deletedAt: null,
        _id: { $ne: workflow._id },
      });

      if (otherActiveWorkflows > 0) {
        console.log(`[WebhookReg] ${otherActiveWorkflows} other active workflows use ${provider}, keeping webhook`);
        return;
      }

      // No other active workflows — safe to remove the webhook
      const providerInstance = await emailIntegrationService.getProvider(companyId, provider);
      if (!providerInstance) {
        console.warn(`[WebhookReg] Provider ${provider} not connected for company ${companyId}`);
        return;
      }

      // Get stored webhook ID
      const webhookId = await this.getStoredWebhookId(companyId, provider);
      if (webhookId) {
        try {
          const numericId = typeof webhookId === 'string' ? parseInt(webhookId, 10) : webhookId;
          await providerInstance.deleteWebhook(numericId);
          console.log(`[WebhookReg] Removed webhook ${webhookId} from ${provider}`);
        } catch (err: any) {
          console.warn(`[WebhookReg] Failed to delete webhook ${webhookId}:`, err.message);
        }
        await this.removeWebhookId(companyId, provider);
      } else {
        // No stored webhook ID — try to find it by URL
        const webhookUrl = `${WEBHOOK_BASE_URL}/webhooks/${provider}?companyId=${companyId}`;
        const existingWebhooks = await providerInstance.getWebhooks();
        const matching = existingWebhooks.find(
          (w: any) => w.url === webhookUrl || w.url?.includes(`companyId=${companyId}`)
        );
        if (matching) {
          await providerInstance.deleteWebhook(matching.id);
          console.log(`[WebhookReg] Removed webhook ${matching.id} from ${provider} (found by URL)`);
        }
      }
    } catch (error: any) {
      console.error(`[WebhookReg] Failed to unregister webhook for ${provider}:`, error.message);
    }
  }

  /**
   * Store a webhook ID in the EmailIntegration document for a company+provider
   */
  private async storeWebhookId(companyId: string, provider: string, webhookId: number | string): Promise<void> {
    const { EmailIntegration } = require('../../models/EmailIntegration');
    try {
      await EmailIntegration.updateOne(
        { companyId, provider },
        {
          $set: {
            webhookId: String(webhookId),
            webhookRegisteredAt: new Date(),
          },
        }
      );
    } catch (err: any) {
      console.warn('[WebhookReg] Failed to store webhook ID:', err.message);
    }
  }

  /**
   * Get stored webhook ID for a company+provider
   */
  private async getStoredWebhookId(companyId: string, provider: string): Promise<string | null> {
    const { EmailIntegration } = require('../../models/EmailIntegration');
    try {
      const integration = await EmailIntegration.findOne({ companyId, provider }).select('webhookId').lean();
      return integration?.webhookId || null;
    } catch {
      return null;
    }
  }

  /**
   * Remove stored webhook ID for a company+provider
   */
  private async removeWebhookId(companyId: string, provider: string): Promise<void> {
    const { EmailIntegration } = require('../../models/EmailIntegration');
    try {
      await EmailIntegration.updateOne(
        { companyId, provider },
        {
          $unset: {
            webhookId: '',
            webhookRegisteredAt: '',
          },
        }
      );
    } catch (err: any) {
      console.warn('[WebhookReg] Failed to remove webhook ID:', err.message);
    }
  }
}

export const webhookRegistrationService = new WebhookRegistrationService();