import {
  Injectable,
  Logger,
  ServiceUnavailableException,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import {
  SESv2Client,
  SendEmailCommand,
  SendEmailCommandInput,
  SendEmailCommandOutput,
} from "@aws-sdk/client-sesv2";

/**
 * Outbound mail transport. Amazon SES — the channel the office already uses
 * for bulk notification, per docs/INSURANCE_FEATURES_SPEC.md §1.3 (the
 * renewal-notice spec settled on SES for the same reason: established sender
 * reputation, existing IAM, negligible incremental cost at our volume).
 *
 * Mirrors `StorageService` exactly: env-driven config, null client when
 * unconfigured, `ServiceUnavailableException` on use, never blocks API boot.
 * When the env vars are missing AND we're in dev/test we fall back to a
 * console-logging transport so the NotificationsService can be exercised
 * end-to-end without SES credentials — a missing mail setup in production
 * still throws, so a real deployment can't accidentally no-op its sends.
 *
 * Env:
 *   SES_REGION                — required when client is configured
 *   SES_ACCESS_KEY / SES_SECRET_KEY — required
 *   SES_FROM                  — verified sending identity (e.g. mail@jorgecuadros.com)
 *   SES_FROM_NAME             — display name, optional
 *   SES_CONFIGURATION_SET     — optional, for bounce/complaint event publishing
 */

export interface SendArgs {
  to: string;
  /** Optional display name; SES will not display it for "to" but we keep it on
   *  the log row so customer-facing audit reads naturally. */
  toName?: string;
  subject: string;
  /** HTML body. The four notification jobs all produce HTML. */
  html: string;
  /** Optional override of the configured From; rare but useful for the
   *  trust-payment test mail to a different identity. */
  from?: string;
  fromName?: string;
  /** Marker header kept on every send so a downstream mail-log search for
   *  "X-Tracking: 1" surfaces only this app's outbound traffic. The legacy
   *  PHP sendEmail() always set it; we keep the convention. */
  xTracking?: string;
}

export interface SendResult {
  /** SES MessageId (or our mock prefix in dev). Stored verbatim on the
   *  notification log row so a SES bounce/complaint webhook can be matched
   *  back to the exact send. */
  messageId: string;
  /** Truncated SES response payload (or empty in dev). 4k cap matches the
   *  notification log column width. */
  response: string;
}

@Injectable()
export class MailService {
  private readonly logger = new Logger(MailService.name);
  private readonly client: SESv2Client | null;
  private readonly fromAddress: string | null;
  private readonly fromName: string;
  private readonly configurationSet: string | undefined;
  private readonly devMode: boolean;

  constructor(config: ConfigService) {
    const region = config.get<string>("SES_REGION");
    const accessKeyId = config.get<string>("SES_ACCESS_KEY");
    const secretAccessKey = config.get<string>("SES_SECRET_KEY");
    this.fromAddress =
      config.get<string>("SES_FROM") ??
      config.get<string>("MAIL_FROM") ??
      null;
    this.fromName =
      config.get<string>("SES_FROM_NAME") ??
      config.get<string>("MAIL_FROM_NAME") ??
      "Information Server";
    this.configurationSet = config.get<string>("SES_CONFIGURATION_SET");
    // Dev fallback: when nothing is configured, log sends to stdout instead
    // of throwing. Lets the API boot in a fresh checkout and lets the
    // notifications UI show "0 sent" meaningfully on `debug=1`. Production
    // (NODE_ENV !== development) still requires real config.
    this.devMode = process.env.NODE_ENV !== "production";

    if (!region || !accessKeyId || !secretAccessKey || !this.fromAddress) {
      if (!this.devMode) {
        this.logger.warn(
          "SES not configured (SES_REGION / SES_ACCESS_KEY / SES_SECRET_KEY / SES_FROM). " +
            "Outbound mail will throw ServiceUnavailableException.",
        );
      }
      this.client = null;
      return;
    }

    this.client = new SESv2Client({
      region,
      credentials: { accessKeyId, secretAccessKey },
    });
    this.logger.log(
      `SES mail client configured (region=${region}, from=${this.fromAddress}).`,
    );
  }

  /** Whether the deployment has a real mail transport. Callers use this to
   *  refuse work up front — a mass-notification job that throws on its
   *  first send half-completes and the log is unrecoverable, so we fail
   *  fast at the controller. */
  get available(): boolean {
    return this.client !== null || this.devMode;
  }

  /** True when the underlying transport is the dev console-log fallback. */
  get isDevFallback(): boolean {
    return this.client === null && this.devMode;
  }

  private require(): SESv2Client {
    if (!this.client) {
      throw new ServiceUnavailableException(
        "El envío de correo no está configurado.",
      );
    }
    return this.client;
  }

  /**
   * Send a single HTML email. The dev fallback logs to stdout and returns a
   * synthetic `dev-<timestamp>` message id; the real transport talks to SES
   * and returns the SES MessageId.
   *
   * Throws `ServiceUnavailableException` when no transport is configured and
   * we are not in dev — the caller (NotificationsService) catches and records
   * it on the log row so a failed sweep produces a coherent audit trail
   * instead of an aborted one.
   */
  async send(args: SendArgs): Promise<SendResult> {
    const from = `${args.fromName ?? this.fromName} <${
      args.from ?? this.fromAddress ?? ""
    }>`.trim();

    if (!this.client) {
      if (!this.devMode) this.require();
      const fakeId = `dev-${Date.now().toString(36)}-${Math.random()
        .toString(36)
        .slice(2, 8)}`;
      this.logger.log(
        `[dev-mail] to=${args.to} subject="${args.subject}" id=${fakeId} ` +
          `len=${args.html.length}`,
      );
      return { messageId: fakeId, response: "" };
    }

    const input: SendEmailCommandInput = {
      FromEmailAddress: from,
      Destination: { ToAddresses: [args.to] },
      Content: {
        Simple: {
          Subject: { Data: args.subject, Charset: "UTF-8" },
          Body: { Html: { Data: args.html, Charset: "UTF-8" } },
        },
      },
      ...(this.configurationSet
        ? { ConfigurationSetName: this.configurationSet }
        : {}),
      ...(args.xTracking
        ? {
            EmailTags: [
              { Name: "X-Tracking", Value: args.xTracking },
            ],
          }
        : {}),
    };

    const out: SendEmailCommandOutput = await this.client.send(
      new SendEmailCommand(input),
    );
    return {
      messageId: out.MessageId ?? "",
      response: JSON.stringify({ MessageId: out.MessageId ?? null }).slice(0, 4096),
    };
  }
}
