import { Transporter, SentMessageInfo } from 'nodemailer';
import sgMail from '@sendgrid/mail';
import {
  EmailOptions,
  EmailResponse,
  SmtpConfig,
  SendGridConfig,
  SesConfig,
  EmailProviderType,
  EmailProviderConfig,
} from '../interfaces/email-options.interface';
import { EmailRequest } from '../interfaces/email-request.interface';
import { ConfigurationError, ProviderError, ValidationError, EmailServiceError } from '../errors';
import { IEmailService } from '../interfaces/email-service.interface';
import { ObservabilityService } from './observability.service';
import { ObservabilityConfig } from '../interfaces/observability.interface';
import { ProviderFactory } from '../config/provider-factory';
import { SesService } from './ses.service';
import { EMAIL_CONSTANTS } from '../config/constants';
import { validateEmailRequest } from '../utils/email-validator';

/**
 * EmailService - A reusable service for sending emails
 *
 * This service can be used in both NodeJS/JavaScript and NestJS/TypeScript projects.
 */
export class EmailService implements IEmailService {
  private transporter?: Transporter;
  private defaultFrom?: { email: string; name?: string };
  private providerType!: EmailProviderType;
  private sendGridConfigured: boolean = false;
  private sesService?: SesService;
  private observability: ObservabilityService;

  /**
   * Create an instance of EmailService
   *
   * @param providerConfig - Email provider configuration (SMTP, SendGrid, or SES)
   * @param defaultFrom - Default sender email and name
   * @param observabilityConfig - Optional observability configuration
   */
  constructor(
    providerConfig: EmailProviderConfig | SmtpConfig, // Support legacy constructor
    defaultFrom?: { email: string; name?: string },
    observabilityConfig?: ObservabilityConfig
  ) {
    this.defaultFrom = defaultFrom || { email: 'noreply@example.com' };
    this.observability = ObservabilityService.getInstance(observabilityConfig);
    this.configureProvider(providerConfig);
  }

  /**
   * Configure email provider based on configuration
   * @private
   */
  private configureProvider(providerConfig: EmailProviderConfig | SmtpConfig): void {
    const config = ProviderFactory.createProviderConfig(providerConfig);
    ProviderFactory.validateProviderConfig(config);
    
    this.providerType = config.type;

    if (this.providerType === EmailProviderType.SMTP) {
      this.transporter = ProviderFactory.createSmtpTransporter(config.config as SmtpConfig);
    } else if (this.providerType === EmailProviderType.SENDGRID) {
      ProviderFactory.configureSendGrid(config.config as SendGridConfig);
      this.sendGridConfigured = true;
    } else if (this.providerType === EmailProviderType.SES) {
      this.sesService = new SesService(config.config as SesConfig);
    }
  }

  /**
   * Verify email provider connection
   *
   * @returns Promise<boolean> - True if connection is successful
   */
  async verifyConnection(): Promise<boolean> {
    const startTime = Date.now();
    try {
      let success = false;

      if (this.providerType === EmailProviderType.SMTP && this.transporter) {
        await this.transporter.verify();
        success = true;
      } else if (this.providerType === EmailProviderType.SENDGRID && this.sendGridConfigured) {
        // SendGrid doesn't have a direct verification method
        // We could send a test email, but for now we'll just check if it's configured
        success = this.sendGridConfigured;
      } else if (this.providerType === EmailProviderType.SES && this.sesService) {
        success = await this.sesService.verifyConnection();
      }

      const duration = Date.now() - startTime;
      this.observability.trackConnectionVerification(this.providerType, success, duration);

      return success;
    } catch (error) {
      const duration = Date.now() - startTime;
      this.observability.trackConnectionVerification(this.providerType, false, duration);
      console.error(EMAIL_CONSTANTS.LOG_MESSAGES.CONNECTION_FAILED, error);
      return false;
    }
  }

  /**
   * Validate email options
   * @private
   */
  private validateEmailOptions(options: EmailOptions): void {
    if (!options.from || !options.from.email) {
      throw new ValidationError(EMAIL_CONSTANTS.VALIDATION_MESSAGES.SENDER_REQUIRED);
    }
    if (!options.to) {
      throw new ValidationError(EMAIL_CONSTANTS.VALIDATION_MESSAGES.RECIPIENT_REQUIRED);
    }
    if (!options.subject) {
      throw new ValidationError(EMAIL_CONSTANTS.VALIDATION_MESSAGES.SUBJECT_REQUIRED);
    }
  }

  /**
   * Centralized error handler for email operations
   * @private
   */
  private handleError(error: any, operation: string): EmailResponse {
    console.error(`Failed to ${operation}:`, error);
    return {
      success: false,
      error: {
        message: error instanceof Error ? error.message : String(error),
        code: error instanceof EmailServiceError ? error.code : 'UNKNOWN_ERROR',
        provider: error instanceof ProviderError ? (error.provider as EmailProviderType) : undefined,
        details: error instanceof EmailServiceError ? error.details : undefined,
      },
    };
  }

  /**
   * Send email using the new EmailRequest interface
   *
   * @param request - Email request
   * @returns Promise<EmailResponse> - Response with success status and message ID
   */
  async sendEmail(request: EmailRequest): Promise<EmailResponse> {
    const startTime = Date.now();
    let emailOptions: EmailOptions | undefined;
    
    try {
      // Validate the request
      const validation = validateEmailRequest(request);
      if (!validation.isValid) {
        return this.createErrorResponse(
          new ValidationError(`Email validation failed: ${validation.errors.join(', ')}`),
          this.providerType
        );
      }

      // Convert to EmailOptions format
      emailOptions = {
        to: Array.isArray(request.to) 
          ? request.to.map(email => ({ email }))
          : { email: request.to },
        subject: request.subject,
        text: request.text,
        html: request.html,
        from: request.from ? { email: request.from } : (this.defaultFrom || { email: 'noreply@example.com' }),
        cc: request.cc ? (Array.isArray(request.cc) 
          ? request.cc.map(email => ({ email }))
          : [{ email: request.cc }]) : undefined,
        bcc: request.bcc ? (Array.isArray(request.bcc)
          ? request.bcc.map(email => ({ email }))
          : [{ email: request.bcc }]) : undefined,
        replyTo: request.replyTo,
        attachments: request.attachments,
        headers: request.headers,
      };

      const eventId = this.observability.trackEmailAttempt(emailOptions, this.providerType, startTime);

      let response: EmailResponse;

      if (this.providerType === EmailProviderType.SMTP && this.transporter) {
        response = await this.sendWithSmtp(emailOptions);
      } else if (this.providerType === EmailProviderType.SENDGRID && this.sendGridConfigured) {
        response = await this.sendWithSendGrid(emailOptions);
      } else if (this.providerType === EmailProviderType.SES && this.sesService) {
        response = await this.sendWithSes(emailOptions);
      } else {
        throw new ConfigurationError(EMAIL_CONSTANTS.VALIDATION_MESSAGES.NO_PROVIDER_CONFIGURED);
      }

      const duration = Date.now() - startTime;
      this.observability.trackEmailSuccess(eventId, response, duration, this.providerType);

      return response;
    } catch (error) {
      const duration = Date.now() - startTime;
      if (emailOptions) {
        const eventId = this.observability.trackEmailAttempt(emailOptions, this.providerType, startTime);
        this.observability.trackEmailFailure(eventId, error, duration, this.providerType);
      }

      return this.handleError(error, 'send email');
    }
  }

  /**
   * Send email using legacy EmailOptions interface (for backward compatibility)
   *
   * @param options - Email options
   * @returns Promise<EmailResponse> - Response with success status and message ID
   */
  async sendEmailLegacy(options: EmailOptions): Promise<EmailResponse> {
    const startTime = Date.now();
    const eventId = this.observability.trackEmailAttempt(options, this.providerType, startTime);

    try {
      // Use default from if not provided
      if (!options.from && this.defaultFrom) {
        options.from = this.defaultFrom;
      }

      // Validate required options
      this.validateEmailOptions(options);

      let response: EmailResponse;

      if (this.providerType === EmailProviderType.SMTP && this.transporter) {
        response = await this.sendWithSmtp(options);
      } else if (this.providerType === EmailProviderType.SENDGRID && this.sendGridConfigured) {
        response = await this.sendWithSendGrid(options);
      } else if (this.providerType === EmailProviderType.SES && this.sesService) {
        response = await this.sendWithSes(options);
      } else {
        throw new ConfigurationError(EMAIL_CONSTANTS.VALIDATION_MESSAGES.NO_PROVIDER_CONFIGURED);
      }

      const duration = Date.now() - startTime;
      this.observability.trackEmailSuccess(eventId, response, duration, this.providerType);

      return response;
    } catch (error) {
      const duration = Date.now() - startTime;
      this.observability.trackEmailFailure(eventId, error, duration, this.providerType);

      return this.handleError(error, 'send email');
    }
  }

  /**
   * Create standardized email response
   * @private
   */
  private createSuccessResponse(messageId?: string): EmailResponse {
    return {
      success: true,
      messageId,
    };
  }



  /**
   * Create standardized error response
   * @private
   */
  private createErrorResponse(error: any, provider: EmailProviderType): EmailResponse {
    const providerError = new ProviderError(
      `${provider} error: ${error instanceof Error ? error.message : String(error)}`,
      provider
    );
    
    if (provider === EmailProviderType.SMTP) {
      const smtpError = error as SentMessageInfo;
      providerError.details = {
        rejected: smtpError.rejected,
        accepted: smtpError.accepted,
        smtpResponse: smtpError.response,
      };
    } else if (provider === EmailProviderType.SENDGRID) {
      // For SendGrid, preserve the enhanced error details if they exist
      if (error instanceof ProviderError && error.details) {
        providerError.details = error.details;
      } else {
        providerError.details = {
          sendgridResponse: error,
        };
      }
    } else if (provider === EmailProviderType.SES) {
      providerError.details = {
        sesResponse: error,
      };
    }

    return {
      success: false,
      error: {
        message: providerError.message,
        code: providerError.code,
        provider: providerError.provider as EmailProviderType,
        details: providerError.details,
      },
    };
  }

  /**
   * Send an email using SMTP
   *
   * @param options - Email options
   * @returns Promise<EmailResponse> - Response with success status and message ID
   * @private
   */
  protected async sendWithSmtp(options: EmailOptions): Promise<EmailResponse> {
    if (!this.transporter) {
      throw new ConfigurationError(EMAIL_CONSTANTS.VALIDATION_MESSAGES.SMTP_NOT_CONFIGURED);
    }

    try {
      const info = await this.transporter.sendMail({
        from: options.from ? `${options.from.name || EMAIL_CONSTANTS.DEFAULTS.FROM_NAME} <${options.from.email}>` : undefined,
        to: this.formatRecipients(options.to),
        cc: options.cc ? this.formatRecipients(options.cc) : undefined,
        bcc: options.bcc ? this.formatRecipients(options.bcc) : undefined,
        subject: options.subject,
        text: options.text,
        html: options.html,
        attachments: options.attachments,
        replyTo: options.replyTo,
        headers: options.headers,
      });

      return this.createSuccessResponse(info.messageId);
    } catch (error) {
      return this.createErrorResponse(error, EmailProviderType.SMTP);
    }
  }

  /**
   * Send an email using SendGrid
   *
   * @param options - Email options
   * @returns Promise<EmailResponse> - Response with success status and message ID
   * @private
   */
  private async sendWithSendGrid(options: EmailOptions): Promise<EmailResponse> {
    try {
      // Validate required fields for SendGrid
      if (!options.from?.email) {
        throw new ValidationError('Sender email is required for SendGrid');
      }

      if (!options.to) {
        throw new ValidationError('Recipient is required for SendGrid');
      }

      if (!options.subject) {
        throw new ValidationError('Subject is required for SendGrid');
      }

      // Build content array
      const content: Array<{ type: string; value: string }> = [];

      // Add text content (SendGrid requires at least one content type)
      if (options.text) {
        content.push({
          type: 'text/plain',
          value: options.text,
        });
      }

      // Add HTML content
      if (options.html) {
        content.push({
          type: 'text/html',
          value: options.html,
        });
      }

      // Ensure at least one content type is provided
      if (content.length === 0) {
        content.push({
          type: 'text/plain',
          value: 'No content provided',
        });
      }

      // Build SendGrid message
      const msg: any = {
        from: {
          email: options.from.email,
          name: options.from.name || '',
        },
        subject: options.subject,
        content,
      };

      // Handle recipients
      if (Array.isArray(options.to)) {
        msg.personalizations = [
          {
            to: options.to.map(recipient => ({
              email: recipient.email,
              name: recipient.name || '',
            })),
          },
        ];
      } else {
        msg.personalizations = [
          {
            to: [
              {
                email: options.to.email,
                name: options.to.name || '',
              },
            ],
          },
        ];
      }

      // Handle CC recipients
      if (options.cc) {
        if (Array.isArray(options.cc)) {
          msg.personalizations[0].cc = options.cc.map(recipient => ({
            email: recipient.email,
            name: recipient.name || '',
          }));
        } else {
          msg.personalizations[0].cc = [
            {
              email: options.cc.email,
              name: options.cc.name || '',
            },
          ];
        }
      }

      // Handle BCC recipients
      if (options.bcc) {
        if (Array.isArray(options.bcc)) {
          msg.personalizations[0].bcc = options.bcc.map(recipient => ({
            email: recipient.email,
            name: recipient.name || '',
          }));
        } else {
          msg.personalizations[0].bcc = [
            {
              email: options.bcc.email,
              name: options.bcc.name || '',
            },
          ];
        }
      }

      // Handle reply-to
      if (options.replyTo) {
        msg.replyTo = {
          email: options.replyTo,
        };
      }

      // Handle custom headers
      if (options.headers) {
        msg.personalizations[0].headers = options.headers;
      }

      // Handle attachments
      if (options.attachments && options.attachments.length > 0) {
        msg.attachments = options.attachments.map(attachment => ({
          filename: attachment.filename,
          content: this.encodeAttachmentContent(attachment.content),
          type: attachment.contentType || 'application/octet-stream',
          disposition: 'attachment',
        }));
      }

      // Send the email
      const response = await sgMail.send(msg);

      return this.createSuccessResponse(response[0]?.headers['x-message-id'] || undefined);
    } catch (error) {
      // Enhanced error handling for SendGrid with specific error messages
      let errorMessage = 'SendGrid error';
      let errorDetails: any = {};

      if (error instanceof Error) {
        errorMessage = error.message;
        
        // Enhanced SendGrid error analysis
        const errorMessageLower = error.message.toLowerCase();
        
        // Check for specific SendGrid error types
        if (errorMessageLower.includes('unauthorized') || errorMessageLower.includes('401')) {
          errorMessage = 'SendGrid API key is invalid or unauthorized. Please check your API key and ensure it has the correct permissions.';
          errorDetails = {
            errorType: 'INVALID_API_KEY',
            suggestion: 'Verify your SendGrid API key in the SendGrid dashboard and ensure it has "Mail Send" permissions.',
            statusCode: 401
          };
        } else if (errorMessageLower.includes('forbidden') || errorMessageLower.includes('403')) {
          errorMessage = 'SendGrid access forbidden. This usually means the sender email is not verified or the API key lacks proper permissions.';
          errorDetails = {
            errorType: 'FORBIDDEN',
            suggestion: 'Verify your sender email in SendGrid dashboard and check API key permissions.',
            statusCode: 403
          };
        } else if (errorMessageLower.includes('bad request') || errorMessageLower.includes('400')) {
          errorMessage = 'SendGrid bad request. This could be due to invalid email format, missing required fields, or unverified sender.';
          errorDetails = {
            errorType: 'BAD_REQUEST',
            suggestion: 'Check email format, verify sender email, and ensure all required fields are provided.',
            statusCode: 400
          };
        } else if (errorMessageLower.includes('rate limit') || errorMessageLower.includes('429') || errorMessageLower.includes('too many requests')) {
          errorMessage = 'SendGrid rate limit exceeded. Please wait before sending more emails.';
          errorDetails = {
            errorType: 'RATE_LIMIT_EXCEEDED',
            suggestion: 'Implement rate limiting or upgrade your SendGrid plan.',
            statusCode: 429
          };
        } else if (errorMessageLower.includes('quota exceeded')) {
          errorMessage = 'SendGrid sending quota exceeded. Please upgrade your plan or wait until quota resets.';
          errorDetails = {
            errorType: 'QUOTA_EXCEEDED',
            suggestion: 'Check your SendGrid account limits or upgrade your plan.',
            statusCode: 429
          };
        }
        
        // Try to extract SendGrid error response details
        try {
          const errorResponse = (error as any).response;
          if (errorResponse?.body) {
            errorDetails = {
              ...errorDetails,
              sendgridError: errorResponse.body,
              statusCode: errorResponse.statusCode,
              headers: errorResponse.headers,
            };
          }
        } catch (parseError) {
          // If we can't parse the error, include the raw error
          errorDetails = { 
            ...errorDetails,
            rawError: error 
          };
        }
      }

      return this.createErrorResponse(
        new ProviderError(errorMessage, EmailProviderType.SENDGRID, errorDetails),
        EmailProviderType.SENDGRID
      );
    }
  }

  /**
   * Send an email using SES
   *
   * @param options - Email options
   * @returns Promise<EmailResponse> - Response with success status and message ID
   * @private
   */
  private async sendWithSes(options: EmailOptions): Promise<EmailResponse> {
    if (!this.sesService) {
      throw new ConfigurationError('SES service is not configured');
    }

    try {
      return await this.sesService.sendEmail(options);
    } catch (error) {
      return this.createErrorResponse(error, EmailProviderType.SES);
    }
  }

  /**
   * Encode attachment content for SendGrid
   * @private
   */
  private encodeAttachmentContent(content: any): string {
    if (content instanceof Buffer) {
      return content.toString('base64');
    } else if (typeof content === 'string') {
      return Buffer.from(content).toString('base64');
    } else {
      return Buffer.from('').toString('base64');
    }
  }

  /**
   * Send a plain text email
   *
   * @param to - Recipient(s)
   * @param subject - Email subject
   * @param text - Plain text content
   * @param options - Additional email options
   * @returns Promise<EmailResponse> - Response with success status and message ID
   */
  async sendPlainText(
    to: EmailOptions['to'],
    subject: string,
    text: string,
    options?: Partial<EmailOptions>
  ): Promise<EmailResponse> {
    return this.sendEmailLegacy({
      to,
      subject,
      text,
      ...options,
      from: options?.from || this.defaultFrom || { email: 'noreply@example.com' },
    });
  }

  /**
   * Send an HTML email
   *
   * @param to - Recipient(s)
   * @param subject - Email subject
   * @param html - HTML content
   * @param options - Additional email options
   * @returns Promise<EmailResponse> - Response with success status and message ID
   */
  async sendHtml(
    to: EmailOptions['to'],
    subject: string,
    html: string,
    options?: Partial<EmailOptions>
  ): Promise<EmailResponse> {
    return this.sendEmailLegacy({
      to,
      subject,
      html,
      ...options,
      from: options?.from || this.defaultFrom || { email: 'noreply@example.com' },
    });
  }

  /**
   * Format recipients to the format expected by nodemailer
   *
   * @param recipients - Single recipient or array of recipients
   * @returns string - Formatted recipients string
   */
  private formatRecipients(recipients: EmailOptions['to']): string {
    if (Array.isArray(recipients)) {
      return recipients
        .map(recipient =>
          recipient.name ? `${recipient.name} <${recipient.email}>` : recipient.email
        )
        .join(', ');
    }

    return recipients.name ? `${recipients.name} <${recipients.email}>` : recipients.email;
  }
}
