/**
 * Brevo Adapter
 *
 * Implements ProviderAdapter for Brevo (formerly Sendinblue).
 * Wraps the existing BrevoProvider (EmailProviderService) and adds:
 *   - Event-based automation triggers via POST /v3/events
 *   - Retry with exponential backoff
 *   - Error normalization
 *
 * Auth: API key (passed to BrevoProvider constructor)
 */

import axios from 'axios';
import type { EmailProviderService, ContactData, SendCampaignOptions } from '../../email/EmailProviderService';
import type {
  ProviderAdapter,
  AddContactInput,
  SendCampaignInput,
  TriggerAutomationInput,
  ProviderResult,
  AddContactOutput,
  SendCampaignOutput,
  TriggerAutomationOutput,
  GetStatusOutput,
} from './types';
import { ProviderError } from './errorNormalizer';
import { normalizeProviderError } from './errorNormalizer';
import { withRetry } from './retry';

const BREVO_API_BASE = 'https://api.brevo.com/v3';

export class BrevoAdapter implements ProviderAdapter {
  private provider: EmailProviderService;
  private apiKey: string;

  /**
   * @param provider - Authenticated BrevoProvider instance
   * @param apiKey - Raw Brevo API key (needed for direct HTTP calls like POST /v3/events)
   */
  constructor(provider: EmailProviderService, apiKey: string) {
    this.provider = provider;
    this.apiKey = apiKey;
  }

  // ===========================================
  // ADD CONTACT
  // ===========================================

  async addContact(input: AddContactInput): Promise<ProviderResult<AddContactOutput>> {
    try {
      const contactData: ContactData = {
        email: input.email,
        updateEnabled: true, // Update if already exists
      };

      // Map structured fields to Brevo attributes
      const attributes: Record<string, string> = {};
      if (input.firstName) attributes.FNAME = input.firstName;
      if (input.lastName) attributes.LNAME = input.lastName;
      if (input.phone) {
        // Brevo requires country code for SMS
        if (input.phone.startsWith('+')) {
          attributes.SMS = input.phone;
        }
      }
      if (input.company) attributes.COMPANY = input.company;

      // Merge any additional attributes
      if (input.attributes) {
        Object.assign(attributes, input.attributes);
      }

      if (Object.keys(attributes).length > 0) {
        contactData.attributes = attributes;
      }

      // Convert list IDs to numbers (Brevo uses numeric IDs)
      if (input.listIds && input.listIds.length > 0) {
        contactData.listIds = input.listIds.map(id => typeof id === 'string' ? parseInt(id, 10) : id).filter(n => !isNaN(n));
      }

      const result = await withRetry(
        () => this.provider.createContact(contactData),
        'addContact',
        'brevo'
      );

      return {
        success: true,
        data: {
          contactId: result.id,
          email: result.email || input.email,
          created: result.created ?? true,
        },
      };
    } catch (error) {
      const providerError = normalizeProviderError(error, 'brevo');

      // Brevo returns 400 for duplicate contacts — normalize to DUPLICATE_CONTACT
      if (providerError.code === 'VALIDATION_ERROR' &&
          providerError.message.toLowerCase().includes('already exist')) {
        return {
          success: false,
          error: new ProviderError({
            provider: 'brevo',
            code: 'DUPLICATE_CONTACT',
            message: `Contact ${input.email} already exists`,
            originalError: error,
            retryable: false,
          }),
        };
      }

      return { success: false, error: providerError };
    }
  }

  // ===========================================
  // SEND CAMPAIGN
  // ===========================================

  async sendCampaign(input: SendCampaignInput): Promise<ProviderResult<SendCampaignOutput>> {
    try {
      // Step 1: Create the campaign
      const campaignOptions: SendCampaignOptions = {
        name: input.name,
        subject: input.subject,
        sender: {
          email: input.sender.email,
          name: input.sender.name,
          ...(input.sender.id ? { id: input.sender.id } : {}),
        },
      };

      if (input.htmlContent) campaignOptions.htmlContent = input.htmlContent;
      if (input.htmlUrl) campaignOptions.htmlUrl = input.htmlUrl;
      if (input.templateId) campaignOptions.templateId = input.templateId;
      if (input.replyTo) campaignOptions.replyTo = input.replyTo;
      if (input.tags && input.tags.length > 0) campaignOptions.tags = input.tags;
      if (input.scheduledAt) campaignOptions.scheduledAt = input.scheduledAt;

      if (input.listIds && input.listIds.length > 0) {
        campaignOptions.recipients = {
          listIds: input.listIds.map(id => typeof id === 'string' ? parseInt(id, 10) : id).filter(n => !isNaN(n)),
        };
      }

      const createResult = await withRetry(
        () => this.provider.createCampaign(campaignOptions),
        'sendCampaign.create',
        'brevo'
      );

      // Step 2: Send the campaign (if not scheduled for later)
      if (!input.scheduledAt) {
        await withRetry(
          () => this.provider.sendCampaign(createResult.campaignId),
          'sendCampaign.send',
          'brevo'
        );
      }

      return {
        success: true,
        data: {
          campaignId: createResult.campaignId,
          status: input.scheduledAt ? 'scheduled' : 'sent',
        },
      };
    } catch (error) {
      return { success: false, error: normalizeProviderError(error, 'brevo') };
    }
  }

  // ===========================================
  // TRIGGER AUTOMATION
  // ===========================================

  async triggerAutomation(input: TriggerAutomationInput): Promise<ProviderResult<TriggerAutomationOutput>> {
    try {
      // Brevo supports two automation trigger mechanisms:
      // 1. Adding a contact to a workflow (numeric identifier)
      // 2. Triggering a custom event (string identifier via POST /v3/events)

      const isNumericId = /^\d+$/.test(input.identifier);

      if (isNumericId) {
        // Workflow-based trigger: add contact to an existing automation workflow
        const workflowId = parseInt(input.identifier, 10);
        const listIds = input.listIds?.map(id => typeof id === 'string' ? parseInt(id, 10) : id).filter(n => !isNaN(n));

        const result = await withRetry(
          () => this.provider.addContactToAutomation(workflowId, input.email, input.attributes, listIds),
          'triggerAutomation.workflow',
          'brevo'
        );

        return {
          success: result.success,
          data: {
            triggered: result.success,
            message: result.message || `Contact ${input.email} added to workflow ${input.identifier}`,
          },
        };
      } else {
        // Event-based trigger: POST /v3/events with event name
        // This is NOT covered by the existing EmailProviderService interface,
        // so we make a direct HTTP call to the Brevo Events API.
        const result = await withRetry(
          () => this.triggerEvent(input.identifier, input.email, input.attributes),
          'triggerAutomation.event',
          'brevo'
        );

        return {
          success: true,
          data: {
            triggered: true,
            message: `Event "${input.identifier}" triggered for ${input.email}`,
          },
        };
      }
    } catch (error) {
      return { success: false, error: normalizeProviderError(error, 'brevo') };
    }
  }

  // ===========================================
  // GET STATUS
  // ===========================================

  async getStatus(): Promise<ProviderResult<GetStatusOutput>> {
    try {
      const accountInfo = await this.provider.getAccountInfo();

      return {
        success: true,
        data: {
          connected: true,
          provider: 'brevo',
          accountEmail: accountInfo.email,
          accountName: [accountInfo.firstName, accountInfo.lastName].filter(Boolean).join(' ') || undefined,
          plan: accountInfo.plan,
          credits: accountInfo.credits,
        },
      };
    } catch (error) {
      return { success: false, error: normalizeProviderError(error, 'brevo') };
    }
  }

  // ===========================================
  // PRIVATE: Event-based trigger via Brevo API
  // ===========================================

  /**
   * Trigger a Brevo custom event via POST /v3/events.
   * This is used for event-based automation triggers (as opposed to
   * workflow-based triggers that add a contact to an existing workflow).
   *
   * Brevo Events API docs:
   * https://developers.brevo.com/docs/send-a-custom-event
   */
  private async triggerEvent(
    eventName: string,
    email: string,
    attributes?: Record<string, any>
  ): Promise<void> {
    const payload: Record<string, any> = {
      eventName,
      email,
    };

    if (attributes && Object.keys(attributes).length > 0) {
      payload.attributes = attributes;
    }

    await axios.post(`${BREVO_API_BASE}/events`, payload, {
      headers: {
        'api-key': this.apiKey,
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
      timeout: 30000,
    });
  }
}