/**
 * ProviderGateway
 *
 * Internal service that provides a focused, simplified API for the automation
 * engine to interact with email providers. Sits alongside the existing
 * EmailProviderService / EmailIntegrationService — not replacing them.
 *
 * Key responsibilities:
 *   1. Resolve which provider to use for a given company
 *   2. Delegate to the appropriate adapter (Brevo, Mailchimp, Zoho)
 *   3. Apply retry with exponential backoff for transient failures
 *   4. Normalize all errors into ProviderError
 *   5. Add structured logging for all operations
 *
 * Usage:
 *   import { providerGateway } from './ProviderGateway';
 *   const result = await providerGateway.addContact('companyId123', { email: 'a@b.com', listIds: [5] });
 *   if (result.success) { ... } else { console.error(result.error); }
 */

import { emailIntegrationService } from '../../email/EmailIntegrationService';
import { BrevoAdapter } from './BrevoAdapter';
import { MailchimpAdapter } from './MailchimpAdapter';
import { ZohoAdapter } from './ZohoAdapter';
import { ProviderError } from './errorNormalizer';
import { normalizeProviderError } from './errorNormalizer';
import type {
  ProviderGatewayInterface,
  ProviderName,
  ProviderAdapter,
  AddContactInput,
  SendCampaignInput,
  TriggerAutomationInput,
  ProviderResult,
  AddContactOutput,
  SendCampaignOutput,
  TriggerAutomationOutput,
  GetStatusOutput,
} from './types';

// ============================================
// PROVIDER GATEWAY CLASS
// ============================================

export class ProviderGateway implements ProviderGatewayInterface {

  // ===========================================
  // ADD CONTACT
  // ===========================================

  async addContact(
    companyId: string,
    input: AddContactInput,
    providerName?: ProviderName
  ): Promise<ProviderResult<AddContactOutput>> {
    const logCtx = `[ProviderGateway] addContact companyId=${companyId}`;
    console.log(`${logCtx} provider=${providerName || 'auto'} email=${input.email}`);

    try {
      const { adapter, name } = await this.resolveAdapter(companyId, providerName);

      const result = await adapter.addContact(input);

      if (result.success) {
        console.log(`${logCtx} SUCCESS provider=${name} contactId=${result.data?.contactId}`);
      } else {
        console.error(`${logCtx} FAILED provider=${name} code=${result.error?.code} message=${result.error?.message}`);
      }

      return result;
    } catch (error) {
      const providerError = this.normalizeCaughtError(error, providerName);
      console.error(`${logCtx} FAILED code=${providerError.code} message=${providerError.message}`);
      return { success: false, error: providerError };
    }
  }

  // ===========================================
  // SEND CAMPAIGN
  // ===========================================

  async sendCampaign(
    companyId: string,
    input: SendCampaignInput,
    providerName?: ProviderName
  ): Promise<ProviderResult<SendCampaignOutput>> {
    const logCtx = `[ProviderGateway] sendCampaign companyId=${companyId}`;
    console.log(`${logCtx} provider=${providerName || 'auto'} name="${input.name}"`);

    try {
      const { adapter, name } = await this.resolveAdapter(companyId, providerName);

      const result = await adapter.sendCampaign(input);

      if (result.success) {
        console.log(`${logCtx} SUCCESS provider=${name} campaignId=${result.data?.campaignId}`);
      } else {
        console.error(`${logCtx} FAILED provider=${name} code=${result.error?.code} message=${result.error?.message}`);
      }

      return result;
    } catch (error) {
      const providerError = this.normalizeCaughtError(error, providerName);
      console.error(`${logCtx} FAILED code=${providerError.code} message=${providerError.message}`);
      return { success: false, error: providerError };
    }
  }

  // ===========================================
  // TRIGGER AUTOMATION
  // ===========================================

  async triggerAutomation(
    companyId: string,
    input: TriggerAutomationInput,
    providerName?: ProviderName
  ): Promise<ProviderResult<TriggerAutomationOutput>> {
    const logCtx = `[ProviderGateway] triggerAutomation companyId=${companyId}`;
    console.log(`${logCtx} provider=${providerName || 'auto'} identifier=${input.identifier}`);

    try {
      const { adapter, name } = await this.resolveAdapter(companyId, providerName);

      const result = await adapter.triggerAutomation(input);

      if (result.success) {
        console.log(`${logCtx} SUCCESS provider=${name} triggered=${result.data?.triggered}`);
      } else {
        console.error(`${logCtx} FAILED provider=${name} code=${result.error?.code} message=${result.error?.message}`);
      }

      return result;
    } catch (error) {
      const providerError = this.normalizeCaughtError(error, providerName);
      console.error(`${logCtx} FAILED code=${providerError.code} message=${providerError.message}`);
      return { success: false, error: providerError };
    }
  }

  // ===========================================
  // GET STATUS
  // ===========================================

  async getStatus(
    companyId: string,
    providerName?: ProviderName
  ): Promise<ProviderResult<GetStatusOutput>> {
    const logCtx = `[ProviderGateway] getStatus companyId=${companyId}`;
    console.log(`${logCtx} provider=${providerName || 'auto'}`);

    try {
      const { adapter, name } = await this.resolveAdapter(companyId, providerName);

      // Status checks don't need retry (they're idempotent health checks)
      const result = await adapter.getStatus();

      if (result.success) {
        console.log(`${logCtx} SUCCESS provider=${name} connected=${result.data?.connected}`);
      } else {
        console.error(`${logCtx} FAILED provider=${name} code=${result.error?.code} message=${result.error?.message}`);
      }

      return result;
    } catch (error) {
      const providerError = this.normalizeCaughtError(error, providerName);
      console.error(`${logCtx} FAILED code=${providerError.code} message=${providerError.message}`);
      return { success: false, error: providerError };
    }
  }

  // ===========================================
  // PRIVATE: ADAPTER RESOLUTION
  // ===========================================

  /**
   * Resolve the provider adapter for a given company.
   *
   * This method:
   * 1. Gets an authenticated EmailProviderService from EmailIntegrationService
   * 2. Determines the provider type from the EmailIntegration document
   * 3. Instantiates the appropriate adapter
   * 4. For Brevo, also retrieves the raw API key for event-based triggers
   */
  private async resolveAdapter(
    companyId: string,
    providerName?: ProviderName
  ): Promise<{ adapter: ProviderAdapter; name: ProviderName }> {
    // Step 1: Get the authenticated provider from EmailIntegrationService
    const provider = await emailIntegrationService.getProvider(companyId, providerName || undefined);

    if (!provider) {
      throw new ProviderError({
        provider: providerName || 'brevo',
        code: 'AUTH_FAILED',
        message: `No email provider connected for company ${companyId}${providerName ? ` (${providerName})` : ''}. Connect one in Settings > Email Integration.`,
        retryable: false,
      });
    }

    // Step 2: Determine the provider type from the integration document
    const integrationType = await this.getIntegrationType(companyId, providerName);
    const name: ProviderName = integrationType;

    // Step 3: Instantiate the appropriate adapter
    let adapter: ProviderAdapter;
    switch (name) {
      case 'brevo': {
        // Brevo adapter needs the raw API key for event-based triggers (POST /v3/events)
        const apiKey = await this.getBrevoApiKey(companyId);
        adapter = new BrevoAdapter(provider, apiKey);
        break;
      }
      case 'mailchimp':
        adapter = new MailchimpAdapter(provider);
        break;
      case 'zoho':
        adapter = new ZohoAdapter(provider);
        break;
      default:
        throw new ProviderError({
          provider: name,
          code: 'PROVIDER_ERROR',
          message: `Unsupported provider: ${name}`,
          retryable: false,
        });
    }

    return { adapter, name };
  }

  /**
   * Get the integration provider type for a company.
   * Queries the EmailIntegration model to determine which provider is connected.
   */
  private async getIntegrationType(
    companyId: string,
    providerName?: ProviderName
  ): Promise<ProviderName> {
    const { EmailIntegration } = require('../../../models/EmailIntegration');

    const query: any = { companyId, status: 'connected' };
    if (providerName) {
      query.provider = providerName;
    }

    const integration = await EmailIntegration.findOne(query)
      .select('provider')
      .lean();

    if (!integration) {
      throw new ProviderError({
        provider: providerName || 'brevo',
        code: 'NOT_FOUND',
        message: `No connected email integration found for company ${companyId}${providerName ? ` (${providerName})` : ''}`,
        retryable: false,
      });
    }

    return (integration.provider || 'brevo') as ProviderName;
  }

  /**
   * Get the decrypted Brevo API key for a company.
   * Needed for direct HTTP calls (POST /v3/events) that aren't
   * covered by the @getbrevo/brevo SDK.
   */
  private async getBrevoApiKey(companyId: string): Promise<string> {
    try {
      const { EmailIntegration } = require('../../../models/EmailIntegration');
      const { decryptApiKey } = require('../../utils/encryption');

      const integration = await EmailIntegration.findOne({
        companyId,
        provider: 'brevo',
        status: 'connected',
      }).select('+encryptedApiKey +encryptionIV');

      if (!integration || !integration.encryptedApiKey || !integration.encryptionIV) {
        throw new ProviderError({
          provider: 'brevo',
          code: 'AUTH_FAILED',
          message: 'Brevo API key not found for company ' + companyId,
          retryable: false,
        });
      }

      return decryptApiKey(integration.encryptedApiKey, integration.encryptionIV);
    } catch (error) {
      if (error instanceof ProviderError) throw error;
      throw new ProviderError({
        provider: 'brevo',
        code: 'AUTH_FAILED',
        message: 'Failed to retrieve Brevo API key',
        originalError: error,
        retryable: false,
      });
    }
  }

  /**
   * Normalize caught errors into ProviderError.
   * If the error is already a ProviderError, return it as-is.
   * Otherwise, wrap it in an UNKNOWN ProviderError.
   */
  private normalizeCaughtError(error: unknown, providerName?: ProviderName): ProviderError {
    if (error instanceof ProviderError) {
      return error;
    }

    // Use normalizeProviderError for raw errors
    return normalizeProviderError(error, providerName || 'brevo');
  }
}

// Singleton export
export const providerGateway = new ProviderGateway();