/**
 * Email Webhook Routes
 *
 * Handles webhooks from email providers (Brevo, Zoho Campaigns, Mailchimp).
 * Updates campaign stats and contact status in real-time.
 * Also bridges webhook events to the automation TriggerService.
 */

import express, { Request, Response } from 'express';
import crypto from 'crypto';
import { getModels } from '../models';
import { webhookTriggerBridge } from '../services/automation/WebhookTriggerBridge';
import { emailIntegrationService } from '../services/email/EmailIntegrationService';
import { applyDeliveryEventByMessageId, DeliveryEvent } from '../services/email/emailDeliveryStatus';

const router = express.Router();

/**
 * Map a raw Brevo event name to our normalised DeliveryEvent, or null if it
 * doesn't affect an EmailDispatch's delivery status.
 */
function brevoEventToDelivery(event: string): DeliveryEvent | null {
  switch (event) {
    case 'delivered': return 'delivered';
    case 'opened':
    case 'uniqueOpened':
    case 'unique_opened': return 'opened';
    case 'click':
    case 'clicked': return 'clicked';
    case 'hardBounce':
    case 'hard_bounce': return 'hard_bounce';
    case 'softBounce':
    case 'soft_bounce': return 'soft_bounce';
    case 'blocked':
    case 'error': return 'blocked';
    case 'spam':
    case 'complaint': return 'spam';
    default: return null;
  }
}

/**
 * Constant-time string comparison to avoid leaking the secret via timing.
 */
function timingSafeEqualStr(a: string, b: string): boolean {
  const ab = Buffer.from(a);
  const bb = Buffer.from(b);
  if (ab.length !== bb.length) return false;
  return crypto.timingSafeEqual(ab, bb);
}

/**
 * Verify an inbound provider webhook.
 *
 * Provider webhooks (Brevo/Zoho/Mailchimp) can't be signed with an HMAC, so we
 * register the callback URL with a per-company secret token (?token=...) and
 * verify it here. Rules:
 *   - No companyId in the URL  → allow, but no tenant attribution (legacy
 *     stats-only path; the automation bridge downstream needs a companyId, so
 *     this can't fire cross-tenant automations).
 *   - companyId + secret provisioned → token MUST match, else 401.
 *   - companyId but no secret yet → allow with a warning (graceful migration
 *     for webhooks registered before this check existed; re-activating the
 *     workflow provisions the secret and flips enforcement on).
 *
 * Returns { ok } — when ok is false the HTTP response has already been sent.
 */
async function verifyWebhookToken(
  provider: string,
  req: Request,
  res: Response
): Promise<{ ok: boolean; companyId?: string }> {
  const companyId = req.query.companyId as string | undefined;
  const token = req.query.token as string | undefined;

  if (!companyId) {
    return { ok: true, companyId: undefined };
  }

  const secret = await emailIntegrationService.getWebhookSecret(companyId, provider);

  if (!secret) {
    console.warn(
      `[${provider} Webhook] No verification secret for company ${companyId} — accepting unverified (legacy). Re-activate the workflow to enforce verification.`
    );
    return { ok: true, companyId };
  }

  if (!token || !timingSafeEqualStr(token, secret)) {
    console.warn(`[${provider} Webhook] Rejected: invalid or missing token for company ${companyId}`);
    res.status(401).send('Unauthorized');
    return { ok: false };
  }

  return { ok: true, companyId };
}

/**
 * Brevo Webhook Events:
 * - delivered: Email delivered successfully
 * - opened: Email opened by recipient
 * - clicked: Link clicked in email
 * - uniqueOpened: Unique open event
 * - hardBounce: Permanent delivery failure
 * - softBounce: Temporary delivery failure
 * - unsubscribe: Recipient unsubscribed
 * - spam: Marked as spam
 * - complaint: Spam complaint
 * - blocked: Email blocked
 */

interface BrevoWebhookPayload {
  event: string;
  email: string;
  id: number;
  campaign_id?: number;
  message_id?: string;
  ts?: number;
  date?: string;
  subject?: string;
  tags?: string[];
  link?: string;
  sending_ip?: string;
  ts_event?: number;
  listId?: number;  // Brevo sends this for listAddition events
}

/**
 * Zoho Campaigns Webhook Events:
 * - email_delivered: Email delivered successfully
 * - bounce: Hard/soft bounce
 * - spam: Marked as spam
 * - email_open: Email opened
 * - email_click: Link clicked
 * - unsubscribed: Recipient unsubscribed
 */

interface ZohoWebhookPayload {
  event: string;
  email: string;
  campaign_key?: string;
  campaign_name?: string;
  list_key?: string;
  contact_key?: string;
  timestamp?: string;
  bounce_type?: 'hard' | 'soft';
  link_url?: string;
  ip_address?: string;
  user_agent?: string;
}

/**
 * POST /webhooks/brevo
 * Handle incoming Brevo webhook events.
 */
router.post('/brevo', async (req: Request, res: Response) => {
  try {
    const auth = await verifyWebhookToken('brevo', req, res);
    if (!auth.ok) return;
    const companyId = auth.companyId;

    const payload: BrevoWebhookPayload = req.body;
    const eventType = payload.event;

    console.log(`[Brevo Webhook] Received event: ${eventType}`, {
      email: payload.email,
      campaignId: payload.campaign_id,
      listId: payload.listId,
      companyId,
      timestamp: payload.ts || payload.ts_event,
    });

    // Handle different event types
    switch (eventType) {
      case 'delivered':
        await handleDelivered(payload);
        break;
      case 'opened':
      case 'uniqueOpened':
        await handleOpened(payload);
        break;
      case 'clicked':
        await handleClicked(payload);
        break;
      case 'hardBounce':
        await handleHardBounce(payload);
        break;
      case 'softBounce':
        await handleSoftBounce(payload);
        break;
      case 'unsubscribe':
        await handleUnsubscribe(payload);
        break;
      case 'spam':
      case 'complaint':
        await handleSpam(payload);
        break;
      case 'blocked':
        await handleBlocked(payload);
        break;
      default:
        console.log(`[Brevo Webhook] Unhandled event type: ${eventType}`);
    }

    // Correlate to the automation EmailDispatch (transactional sends) by the
    // provider message id and advance its delivery status forward-only. Brevo
    // sends the transactional id as `message-id` (hyphen); older/marketing
    // payloads use `message_id`. This is what makes Email Logs reflect the
    // provider's ACTUAL delivery outcome instead of only "sent".
    const deliveryEvent = brevoEventToDelivery(eventType);
    if (deliveryEvent) {
      const msgId = (payload as any)['message-id'] || payload.message_id;
      const at = payload.ts_event || payload.ts ? new Date(((payload.ts_event || payload.ts)!) * 1000) : new Date();
      try {
        await applyDeliveryEventByMessageId(companyId, msgId, deliveryEvent, at);
      } catch (e) {
        console.error('[Brevo Webhook] Error updating dispatch delivery status:', e);
      }
    }

    // Bridge webhook event to automation triggers
    if (companyId) {
      try {
        await webhookTriggerBridge.handleBrevoEvent(companyId, payload);
      } catch (bridgeError) {
        console.error('[Brevo Webhook] Error bridging to automation:', bridgeError);
      }
    }

    // Always return 200 to acknowledge receipt
    res.status(200).send('OK');
  } catch (error) {
    console.error('[Brevo Webhook] Error processing webhook:', error);
    // Still return 200 to prevent Brevo from retrying
    res.status(200).send('OK');
  }
});

/**
 * Handle delivered event
 */
async function handleDelivered(payload: BrevoWebhookPayload) {
  if (!payload.campaign_id) return;

  const { EmailCampaign, EmailContact } = getModels();

  await EmailCampaign.updateOne(
    { brevoCampaignId: payload.campaign_id },
    { $inc: { 'stats.delivered': 1 } }
  );

  await EmailContact.updateOne(
    { email: payload.email.toLowerCase() },
    { $set: { status: 'active', lastSyncedAt: new Date() } }
  );

  console.log(`[Brevo Webhook] Delivered: ${payload.email} for campaign ${payload.campaign_id}`);
}

/**
 * Handle opened/uniqueOpened event
 */
async function handleOpened(payload: BrevoWebhookPayload) {
  if (!payload.campaign_id) return;

  const { EmailCampaign } = getModels();

  // uniqueOpened is counted once per recipient
  if (payload.event === 'uniqueOpened') {
    await EmailCampaign.updateOne(
      { brevoCampaignId: payload.campaign_id },
      { $inc: { 'stats.uniqueOpens': 1, 'stats.opened': 1 } }
    );
  } else {
    await EmailCampaign.updateOne(
      { brevoCampaignId: payload.campaign_id },
      { $inc: { 'stats.opened': 1 } }
    );
  }

  console.log(`[Brevo Webhook] Opened: ${payload.email} for campaign ${payload.campaign_id}`);
}

/**
 * Handle clicked event
 */
async function handleClicked(payload: BrevoWebhookPayload) {
  if (!payload.campaign_id) return;

  const { EmailCampaign } = getModels();

  await EmailCampaign.updateOne(
    { brevoCampaignId: payload.campaign_id },
    { $inc: { 'stats.clicked': 1 } }
  );

  console.log(`[Brevo Webhook] Clicked: ${payload.email} for campaign ${payload.campaign_id}`);
}

/**
 * Handle hardBounce event
 */
async function handleHardBounce(payload: BrevoWebhookPayload) {
  if (!payload.campaign_id) return;

  const { EmailCampaign, EmailContact } = getModels();

  await EmailCampaign.updateOne(
    { brevoCampaignId: payload.campaign_id },
    { $inc: { 'stats.hardBounces': 1 } }
  );

  await EmailContact.updateOne(
    { email: payload.email.toLowerCase() },
    { $set: { status: 'bounced' } }
  );

  console.log(`[Brevo Webhook] Hard bounce: ${payload.email} for campaign ${payload.campaign_id}`);
}

/**
 * Handle softBounce event
 */
async function handleSoftBounce(payload: BrevoWebhookPayload) {
  if (!payload.campaign_id) return;

  const { EmailCampaign } = getModels();

  await EmailCampaign.updateOne(
    { brevoCampaignId: payload.campaign_id },
    { $inc: { 'stats.softBounces': 1 } }
  );

  console.log(`[Brevo Webhook] Soft bounce: ${payload.email} for campaign ${payload.campaign_id}`);
}

/**
 * Handle unsubscribe event
 */
async function handleUnsubscribe(payload: BrevoWebhookPayload) {
  if (!payload.campaign_id) return;

  const { EmailCampaign, EmailContact } = getModels();

  await EmailCampaign.updateOne(
    { brevoCampaignId: payload.campaign_id },
    { $inc: { 'stats.unsubscribes': 1 } }
  );

  await EmailContact.updateOne(
    { email: payload.email.toLowerCase() },
    { $set: { status: 'unsubscribed' } }
  );

  console.log(`[Brevo Webhook] Unsubscribe: ${payload.email} for campaign ${payload.campaign_id}`);
}

/**
 * Handle spam/complaint event
 */
async function handleSpam(payload: BrevoWebhookPayload) {
  if (!payload.campaign_id) return;

  const { EmailCampaign } = getModels();

  await EmailCampaign.updateOne(
    { brevoCampaignId: payload.campaign_id },
    { $inc: { 'stats.complaints': 1 } }
  );

  console.log(`[Brevo Webhook] Spam complaint: ${payload.email} for campaign ${payload.campaign_id}`);
}

/**
 * Handle blocked event
 */
async function handleBlocked(payload: BrevoWebhookPayload) {
  const { EmailContact } = getModels();

  await EmailContact.updateOne(
    { email: payload.email.toLowerCase() },
    { $set: { status: 'bounced' } }
  );

  console.log(`[Brevo Webhook] Blocked: ${payload.email}`);
}

/**
 * GET /webhooks/brevo/health
 * Health check endpoint for webhook service.
 */
router.get('/brevo/health', (req: Request, res: Response) => {
  res.json({ status: 'ok', service: 'brevo-webhook' });
});

// ============================================
// ZOHO CAMPAIGNS WEBHOOKS
// ============================================

/**
 * POST /webhooks/zoho
 * Handle incoming Zoho Campaigns webhook events.
 *
 * Zoho webhook events:
 * - email_delivered: Email delivered successfully
 * - bounce: Email bounced (hard/soft)
 * - spam: Marked as spam
 * - email_open: Email opened by recipient
 * - email_click: Link clicked in email
 * - unsubscribed: Recipient unsubscribed
 */
router.post('/zoho', async (req: Request, res: Response) => {
  try {
    const auth = await verifyWebhookToken('zoho', req, res);
    if (!auth.ok) return;
    const companyId = auth.companyId;

    const payload: ZohoWebhookPayload = req.body;
    const eventType = payload.event;

    console.log(`[Zoho Webhook] Received event: ${eventType}`, {
      email: payload.email,
      campaignKey: payload.campaign_key,
      timestamp: payload.timestamp,
    });

    // Handle different event types
    switch (eventType) {
      case 'email_delivered':
        await handleZohoDelivered(payload);
        break;
      case 'email_open':
        await handleZohoOpened(payload);
        break;
      case 'email_click':
        await handleZohoClicked(payload);
        break;
      case 'bounce':
        await handleZohoBounce(payload);
        break;
      case 'unsubscribed':
        await handleZohoUnsubscribed(payload);
        break;
      case 'spam':
        await handleZohoSpam(payload);
        break;
      default:
        console.log(`[Zoho Webhook] Unhandled event type: ${eventType}`);
    }

    // Bridge webhook event to automation triggers
    if (companyId) {
      try {
        await webhookTriggerBridge.handleZohoEvent(companyId, payload);
      } catch (bridgeError) {
        console.error('[Zoho Webhook] Error bridging to automation:', bridgeError);
      }
    }

    // Always return 200 to acknowledge receipt
    res.status(200).send('OK');
  } catch (error) {
    console.error('[Zoho Webhook] Error processing webhook:', error);
    // Still return 200 to prevent retries
    res.status(200).send('OK');
  }
});

/**
 * Handle Zoho email_delivered event
 */
async function handleZohoDelivered(payload: ZohoWebhookPayload) {
  if (!payload.campaign_key) return;

  const { EmailCampaign, EmailContact } = getModels();

  await EmailCampaign.updateOne(
    { brevoCampaignId: payload.campaign_key },
    { $inc: { 'stats.delivered': 1 } }
  );

  await EmailContact.updateOne(
    { email: payload.email.toLowerCase() },
    { $set: { status: 'active', lastSyncedAt: new Date() } }
  );

  console.log(`[Zoho Webhook] Delivered: ${payload.email} for campaign ${payload.campaign_key}`);
}

/**
 * Handle Zoho email_open event
 */
async function handleZohoOpened(payload: ZohoWebhookPayload) {
  if (!payload.campaign_key) return;

  const { EmailCampaign } = getModels();

  await EmailCampaign.updateOne(
    { brevoCampaignId: payload.campaign_key },
    { $inc: { 'stats.opened': 1, 'stats.uniqueOpens': 1 } }
  );

  console.log(`[Zoho Webhook] Opened: ${payload.email} for campaign ${payload.campaign_key}`);
}

/**
 * Handle Zoho email_click event
 */
async function handleZohoClicked(payload: ZohoWebhookPayload) {
  if (!payload.campaign_key) return;

  const { EmailCampaign } = getModels();

  await EmailCampaign.updateOne(
    { brevoCampaignId: payload.campaign_key },
    { $inc: { 'stats.clicked': 1 } }
  );

  console.log(`[Zoho Webhook] Clicked: ${payload.email} for campaign ${payload.campaign_key}`);
}

/**
 * Handle Zoho bounce event
 */
async function handleZohoBounce(payload: ZohoWebhookPayload) {
  if (!payload.campaign_key) return;

  const { EmailCampaign, EmailContact } = getModels();

  const isHardBounce = payload.bounce_type === 'hard';

  if (isHardBounce) {
    await EmailCampaign.updateOne(
      { brevoCampaignId: payload.campaign_key },
      { $inc: { 'stats.hardBounces': 1 } }
    );

    await EmailContact.updateOne(
      { email: payload.email.toLowerCase() },
      { $set: { status: 'bounced' } }
    );
  } else {
    await EmailCampaign.updateOne(
      { brevoCampaignId: payload.campaign_key },
      { $inc: { 'stats.softBounces': 1 } }
    );
  }

  console.log(`[Zoho Webhook] ${isHardBounce ? 'Hard' : 'Soft'} bounce: ${payload.email} for campaign ${payload.campaign_key}`);
}

/**
 * Handle Zoho unsubscribed event
 */
async function handleZohoUnsubscribed(payload: ZohoWebhookPayload) {
  if (!payload.campaign_key) return;

  const { EmailCampaign, EmailContact } = getModels();

  await EmailCampaign.updateOne(
    { brevoCampaignId: payload.campaign_key },
    { $inc: { 'stats.unsubscribes': 1 } }
  );

  await EmailContact.updateOne(
    { email: payload.email.toLowerCase() },
    { $set: { status: 'unsubscribed' } }
  );

  console.log(`[Zoho Webhook] Unsubscribed: ${payload.email} for campaign ${payload.campaign_key}`);
}

/**
 * Handle Zoho spam event
 */
async function handleZohoSpam(payload: ZohoWebhookPayload) {
  if (!payload.campaign_key) return;

  const { EmailCampaign } = getModels();

  await EmailCampaign.updateOne(
    { brevoCampaignId: payload.campaign_key },
    { $inc: { 'stats.complaints': 1 } }
  );

  console.log(`[Zoho Webhook] Spam complaint: ${payload.email} for campaign ${payload.campaign_key}`);
}

/**
 * GET /webhooks/zoho/health
 * Health check endpoint for Zoho webhook service.
 */
router.get('/zoho/health', (req: Request, res: Response) => {
  res.json({ status: 'ok', service: 'zoho-webhook' });
});

// ============================================
// MAILCHIMP WEBHOOKS
// ============================================

/**
 * Mailchimp Webhook Events:
 * - subscribe: New subscriber added to list
 * - unsubscribe: Recipient unsubscribed
 * - profile: Profile updated
 * - cleaned: Email bounced/hard bounce
 * - upemail: Email address changed
 * - campaign: Campaign status update (sent, etc.)
 *
 * Note: Mailchimp webhooks are list-specific and must be configured
 * per list in the Mailchimp dashboard or via API.
 */

interface MailchimpWebhookPayload {
  type: string;
  fired_at: string;
  data: {
    id?: string;
    list_id?: string;
    email?: string;
    email_type?: string;
    ip_signup?: string;
    ip_opt?: string;
    reason?: string;
    campaign_id?: string;
    merge_fields?: Record<string, string>;
    old_email?: string;
    new_email?: string;
    action?: string;
    url?: string;
  };
}

/**
 * POST /webhooks/mailchimp
 * Handle incoming Mailchimp webhook events.
 */
router.post('/mailchimp', async (req: Request, res: Response) => {
  try {
    const auth = await verifyWebhookToken('mailchimp', req, res);
    if (!auth.ok) return;
    const companyId = auth.companyId;

    const payload: MailchimpWebhookPayload = req.body;
    const eventType = payload.type;

    console.log(`[Mailchimp Webhook] Received event: ${eventType}`, {
      email: payload.data?.email || payload.data?.old_email,
      listId: payload.data?.list_id,
      campaignId: payload.data?.campaign_id,
      timestamp: payload.fired_at,
    });

    // Handle different event types
    switch (eventType) {
      case 'subscribe':
        await handleMailchimpSubscribe(payload);
        break;
      case 'unsubscribe':
        await handleMailchimpUnsubscribe(payload);
        break;
      case 'profile':
        await handleMailchimpProfile(payload);
        break;
      case 'cleaned':
        await handleMailchimpCleaned(payload);
        break;
      case 'upemail':
        await handleMailchimpEmailChange(payload);
        break;
      case 'campaign':
        await handleMailchimpCampaignStatus(payload);
        break;
      default:
        console.log(`[Mailchimp Webhook] Unhandled event type: ${eventType}`);
    }

    // Bridge webhook event to automation triggers
    if (companyId) {
      try {
        await webhookTriggerBridge.handleMailchimpEvent(companyId, payload);
      } catch (bridgeError) {
        console.error('[Mailchimp Webhook] Error bridging to automation:', bridgeError);
      }
    }

    // Always return 200 to acknowledge receipt
    res.status(200).send('OK');
  } catch (error) {
    console.error('[Mailchimp Webhook] Error processing webhook:', error);
    // Still return 200 to prevent retries
    res.status(200).send('OK');
  }
});

/**
 * Handle Mailchimp subscribe event
 */
async function handleMailchimpSubscribe(payload: MailchimpWebhookPayload) {
  const { EmailContact } = getModels();
  const email = payload.data.email?.toLowerCase();

  if (!email) return;

  await EmailContact.updateOne(
    { email },
    {
      $set: {
        status: 'active',
        mailchimpId: payload.data.id,
        listId: payload.data.list_id,
        lastSyncedAt: new Date(),
        mergeFields: payload.data.merge_fields || {},
      },
    },
    { upsert: true }
  );

  console.log(`[Mailchimp Webhook] Subscribe: ${email} added to list ${payload.data.list_id}`);
}

/**
 * Handle Mailchimp unsubscribe event
 */
async function handleMailchimpUnsubscribe(payload: MailchimpWebhookPayload) {
  const { EmailContact } = getModels();
  const email = payload.data.email?.toLowerCase();

  if (!email) return;

  await EmailContact.updateOne(
    { email },
    {
      $set: {
        status: 'unsubscribed',
        lastSyncedAt: new Date(),
        unsubscribeReason: payload.data.reason,
      },
    }
  );

  console.log(`[Mailchimp Webhook] Unsubscribe: ${email} from list ${payload.data.list_id}`);
}

/**
 * Handle Mailchimp profile update event
 */
async function handleMailchimpProfile(payload: MailchimpWebhookPayload) {
  const { EmailContact } = getModels();
  const email = payload.data.email?.toLowerCase();

  if (!email) return;

  await EmailContact.updateOne(
    { email },
    {
      $set: {
        lastSyncedAt: new Date(),
        mergeFields: payload.data.merge_fields || {},
        mailchimpId: payload.data.id,
      },
    }
  );

  console.log(`[Mailchimp Webhook] Profile updated: ${email}`);
}

/**
 * Handle Mailchimp cleaned (bounced) event
 */
async function handleMailchimpCleaned(payload: MailchimpWebhookPayload) {
  const { EmailContact } = getModels();
  const email = payload.data.email?.toLowerCase();

  if (!email) return;

  await EmailContact.updateOne(
    { email },
    {
      $set: {
        status: 'bounced',
        lastSyncedAt: new Date(),
        bounceReason: payload.data.reason,
      },
    }
  );

  console.log(`[Mailchimp Webhook] Cleaned (bounced): ${email} - Reason: ${payload.data.reason}`);
}

/**
 * Handle Mailchimp email change event
 */
async function handleMailchimpEmailChange(payload: MailchimpWebhookPayload) {
  const { EmailContact } = getModels();
  const oldEmail = payload.data.old_email?.toLowerCase();
  const newEmail = payload.data.new_email?.toLowerCase();

  if (!oldEmail || !newEmail) return;

  // Find and update the contact with the new email
  await EmailContact.updateOne(
    { email: oldEmail },
    {
      $set: {
        email: newEmail,
        previousEmail: oldEmail,
        lastSyncedAt: new Date(),
      },
    }
  );

  console.log(`[Mailchimp Webhook] Email changed: ${oldEmail} -> ${newEmail}`);
}

/**
 * Handle Mailchimp campaign status event
 */
async function handleMailchimpCampaignStatus(payload: MailchimpWebhookPayload) {
  const { EmailCampaign } = getModels();
  const campaignId = payload.data.campaign_id;
  const action = payload.data.action;

  if (!campaignId) return;

  // Update campaign status based on action
  // Mailchimp sends 'sent' action when campaign is delivered
  if (action === 'sent') {
    await EmailCampaign.updateOne(
      { mailchimpCampaignId: campaignId },
      {
        $set: {
          status: 'sent',
          sentAt: new Date(),
        },
      }
    );
    console.log(`[Mailchimp Webhook] Campaign ${campaignId} sent`);
  } else {
    console.log(`[Mailchimp Webhook] Campaign ${campaignId} action: ${action}`);
  }
}

/**
 * GET /webhooks/mailchimp/health
 * Health check endpoint for Mailchimp webhook service.
 */
router.get('/mailchimp/health', (req: Request, res: Response) => {
  res.json({ status: 'ok', service: 'mailchimp-webhook' });
});

export default router;