import { Injectable, Inject } from "@nestjs/common";
import type { OnModuleInit } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Logger } from "nestjs-pino";
import sgMail from "@sendgrid/mail";
import type { MailDataRequired, ResponseError } from "@sendgrid/mail";

export interface ProviderSendOptions {
  to: string | string[];
  from?: string;
  /** BCC recipients - useful for Email-to-Case integration */
  bcc?: string | string[];
  subject: string;
  text?: string;
  html?: string;
  templateId?: string;
  dynamicTemplateData?: Record<string, unknown>;
  /** Optional category for tracking/analytics */
  category?: string;
}

export interface SendGridErrorDetail {
  message: string;
  field?: string | undefined;
  help?: string | undefined;
}

export interface ParsedSendGridError {
  statusCode: number;
  message: string;
  errors: SendGridErrorDetail[];
  isRetryable: boolean;
}

/** HTTP status codes that indicate transient failures worth retrying */
const RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);

@Injectable()
export class SendGridEmailProvider implements OnModuleInit {
  private isConfigured = false;
  private readonly sandboxMode: boolean;
  private readonly defaultFrom: string | undefined;
  private readonly defaultFromName: string | undefined;

  constructor(
    private readonly config: ConfigService,
    @Inject(Logger) private readonly logger: Logger
  ) {
    this.sandboxMode = this.config.get("SENDGRID_SANDBOX", "false") === "true";
    this.defaultFrom = this.config.get<string>("EMAIL_FROM");
    this.defaultFromName = this.config.get<string>("EMAIL_FROM_NAME");
  }

  onModuleInit(): void {
    const apiKey = this.config.get<string>("SENDGRID_API_KEY");

    if (!apiKey) {
      this.logger.warn("SendGrid API key not configured - email sending will fail", {
        provider: "sendgrid",
      });
      return;
    }

    sgMail.setApiKey(apiKey);
    this.isConfigured = true;

    this.logger.log("SendGrid email provider initialized", {
      provider: "sendgrid",
      sandboxMode: this.sandboxMode,
      defaultFrom: this.defaultFrom,
    });
  }

  async send(options: ProviderSendOptions): Promise<void> {
    const emailContext = {
      provider: "sendgrid",
      to: this.maskEmail(options.to),
      subject: options.subject,
      category: options.category,
      hasTemplate: !!options.templateId,
      templateId: options.templateId,
    };

    if (!this.isConfigured) {
      this.logger.error("SendGrid not configured - cannot send email", emailContext);
      throw new Error("SendGrid API key not configured");
    }

    const from = this.buildFromAddress(options.from);
    if (!from) {
      this.logger.error("No sender address configured - cannot send email", emailContext);
      throw new Error("EMAIL_FROM is not configured");
    }

    const message = this.buildMessage(options, from);

    this.logger.log("Sending email via SendGrid", emailContext);

    try {
      const [response] = await sgMail.send(message);

      this.logger.log("Email sent successfully via SendGrid", {
        ...emailContext,
        statusCode: response.statusCode,
        messageId: (response.headers as Record<string, string>)["x-message-id"],
      });
    } catch (error) {
      const parsed = this.parseError(error);

      this.logger.error("Failed to send email via SendGrid", {
        ...emailContext,
        statusCode: parsed.statusCode,
        errorMessage: parsed.message,
        errors: parsed.errors,
        isRetryable: parsed.isRetryable,
      });

      // Attach metadata to error for upstream handling
      const enrichedError = new Error(parsed.message);
      (enrichedError as Error & { sendgrid: ParsedSendGridError }).sendgrid = parsed;
      throw enrichedError;
    }
  }

  private buildFromAddress(
    overrideFrom?: string
  ): string | { email: string; name: string } | undefined {
    const email = overrideFrom || this.defaultFrom;
    if (!email) return undefined;

    // Always include sender name when configured (regardless of email override)
    if (this.defaultFromName) {
      return { email, name: this.defaultFromName };
    }
    return email;
  }

  private buildMessage(
    options: ProviderSendOptions,
    from: string | { email: string; name: string }
  ): MailDataRequired {
    const message: MailDataRequired = {
      to: options.to,
      from,
      subject: options.subject,
      mailSettings: {
        sandboxMode: { enable: this.sandboxMode },
      },
    } as MailDataRequired;

    // Add BCC if provided (useful for Email-to-Case integration)
    if (options.bcc) {
      message.bcc = options.bcc;
    }

    // Content: template or direct HTML/text
    if (options.templateId) {
      message.templateId = options.templateId;
      if (options.dynamicTemplateData) {
        message.dynamicTemplateData = options.dynamicTemplateData;
      }
    } else {
      if (options.html) message.html = options.html;
      if (options.text) message.text = options.text;
    }

    // Categories for SendGrid analytics
    if (options.category) {
      message.categories = [options.category];
    }

    return message;
  }

  private parseError(error: unknown): ParsedSendGridError {
    // SendGrid errors have a specific structure
    if (this.isSendGridError(error)) {
      const statusCode = error.code || 500;
      const body = error.response?.body;
      const responseErrors = this.extractErrorsFromBody(body);

      return {
        statusCode,
        message: responseErrors[0]?.message || error.message || "SendGrid error",
        errors: responseErrors,
        isRetryable: RETRYABLE_STATUS_CODES.has(statusCode),
      };
    }

    // Generic error fallback
    const message = error instanceof Error ? error.message : String(error);
    return {
      statusCode: 500,
      message,
      errors: [{ message }],
      isRetryable: false,
    };
  }

  private extractErrorsFromBody(body: unknown): SendGridErrorDetail[] {
    if (!body || typeof body !== "object") {
      return [{ message: "Unknown error" }];
    }

    // SendGrid returns { errors: [...] } in the body
    const bodyObj = body as { errors?: Array<{ message?: string; field?: string; help?: string }> };
    if (Array.isArray(bodyObj.errors)) {
      return bodyObj.errors.map(e => ({
        message: e.message || "Unknown error",
        field: e.field,
        help: e.help,
      }));
    }

    return [{ message: "Unknown error" }];
  }

  private isSendGridError(error: unknown): error is ResponseError {
    return typeof error === "object" && error !== null && "code" in error && "response" in error;
  }

  private maskEmail(email: string | string[]): string | string[] {
    const mask = (e: string): string => {
      const [local, domain] = e.split("@");
      if (!domain || !local) return "***";
      const maskedLocal = local.length > 2 ? `${local[0]}***${local.at(-1)}` : "***";
      return `${maskedLocal}@${domain}`;
    };

    return Array.isArray(email) ? email.map(mask) : mask(email);
  }
}
