import { $atom, $env, $hook, $state, type Static, z } from "alepha";
import {
  EmailError,
  type EmailProvider,
  type EmailSendOptions,
} from "alepha/email";
import { $logger } from "alepha/logger";
import type { Transporter } from "nodemailer";
import nodemailer from "nodemailer";

// ---------------------------------------------------------------------------------------------------------------------

/**
 * Environment variables for nodemailer configuration
 */
const envSchema = z.object({
  EMAIL_HOST: z
    .text({
      description: "SMTP server host",
    })
    .optional(),
  EMAIL_PORT: z.number().describe("SMTP server port").default(587),
  EMAIL_USER: z
    .text({
      description: "SMTP authentication username",
    })
    .optional(),
  EMAIL_PASS: z
    .text({
      description: "SMTP authentication password",
    })
    .optional(),
  EMAIL_FROM: z
    .text({
      description: "Default from email address",
    })
    .optional(),
  EMAIL_SECURE: z
    .boolean()
    .describe("Use secure connection (TLS)")
    .default(false),
});

// ---------------------------------------------------------------------------------------------------------------------

/**
 * Nodemailer connection pooling and rate limiting options
 */
export const nodemailerEmailOptions = $atom({
  name: "alepha.email.nodemailer.options",
  schema: z.object({
    pool: z.boolean().describe("Enable connection pooling").optional(),
    maxConnections: z
      .number()
      .describe("Maximum number of connections in pool")
      .optional(),
    maxMessages: z
      .number()
      .describe("Maximum messages per connection")
      .optional(),
    rateDelta: z
      .number()
      .describe("Time in milliseconds between message sends")
      .optional(),
    rateLimit: z
      .number()
      .describe("Maximum number of messages per rateDelta")
      .optional(),
  }),
  default: {},
});

export type NodemailerEmailProviderOptions = Static<
  typeof nodemailerEmailOptions.schema
>;

declare module "alepha" {
  interface State {
    [nodemailerEmailOptions.key]: NodemailerEmailProviderOptions;
  }
}

// ---------------------------------------------------------------------------------------------------------------------

/**
 * Email provider using Nodemailer for SMTP transport.
 *
 * Configuration is provided via environment variables:
 * - EMAIL_HOST: SMTP server host
 * - EMAIL_PORT: SMTP server port (default: 587)
 * - EMAIL_USER: SMTP authentication username
 * - EMAIL_PASS: SMTP authentication password
 * - EMAIL_FROM: Default from email address
 * - EMAIL_SECURE: Use secure connection (default: false)
 *
 * Advanced pooling/rate limiting options can be configured via atom:
 * @see {@link nodemailerEmailOptions}
 *
 * @example
 * ```typescript
 * // Configure via environment variables
 * // EMAIL_HOST=smtp.example.com
 * // EMAIL_PORT=587
 * // EMAIL_USER=user@example.com
 * // EMAIL_PASS=secret
 * // EMAIL_FROM=noreply@example.com
 *
 * // Optionally configure pooling via atom
 * alepha.state.set(nodemailerEmailOptions.key, {
 *   pool: true,
 *   maxConnections: 5,
 *   rateLimit: 10,
 * });
 * ```
 */
export class NodemailerEmailProvider implements EmailProvider {
  protected readonly env = $env(envSchema);
  protected readonly log = $logger();
  protected readonly options = $state(nodemailerEmailOptions);
  protected transporter: Transporter | null = null;

  protected get host(): string {
    const host = this.env.EMAIL_HOST;
    if (!host) {
      throw new EmailError(
        "Email host not configured. Set EMAIL_HOST env var.",
      );
    }
    return host;
  }

  protected get port(): number {
    return this.env.EMAIL_PORT;
  }

  protected get secure(): boolean {
    return this.env.EMAIL_SECURE;
  }

  protected get user(): string | undefined {
    return this.env.EMAIL_USER;
  }

  protected get pass(): string | undefined {
    return this.env.EMAIL_PASS;
  }

  protected get fromAddress(): string {
    const from = this.env.EMAIL_FROM;
    if (!from) {
      throw new EmailError(
        "Email from address not configured. Set EMAIL_FROM env var.",
      );
    }
    return from;
  }

  protected getTransporter(): Transporter {
    if (!this.transporter) {
      this.transporter = this.createTransporter();
    }
    return this.transporter;
  }

  public async send(options: EmailSendOptions): Promise<void> {
    const { to, subject, body } = options;
    this.log.debug("Sending email via Nodemailer", { to, subject });

    try {
      const result = await this.getTransporter().sendMail({
        from: this.fromAddress,
        to,
        subject,
        html: body,
      });

      this.log.info("Email sent successfully", {
        to,
        subject,
        messageId: result.messageId,
        response: result.response,
      });
    } catch (error) {
      const message = `Failed to send email via Nodemailer: ${error instanceof Error ? error.message : String(error)}`;
      this.log.error(message, { to, subject });
      throw new EmailError(message, error instanceof Error ? error : undefined);
    }
  }

  protected createTransporter(): Transporter {
    const transporterConfig = {
      host: this.host,
      port: this.port,
      secure: this.secure,
      auth:
        this.user && this.pass
          ? {
              user: this.user,
              pass: this.pass,
            }
          : undefined,
      pool: this.options.pool,
      maxConnections: this.options.maxConnections,
      maxMessages: this.options.maxMessages,
      rateDelta: this.options.rateDelta,
      rateLimit: this.options.rateLimit,
    };

    this.log.debug("Creating Nodemailer transporter", {
      host: transporterConfig.host,
      port: transporterConfig.port,
      secure: transporterConfig.secure,
      user: transporterConfig.auth?.user,
      pool: transporterConfig.pool,
    });

    return nodemailer.createTransport(transporterConfig);
  }

  /**
   * Verify the connection to the email server.
   */
  public async verify(): Promise<boolean> {
    try {
      await this.getTransporter().verify();
      this.log.info("Email server connection verified");
      return true;
    } catch (error) {
      this.log.error("Email server connection failed", { error });
      return false;
    }
  }

  /**
   * Close the transporter connection.
   */
  public close(): void {
    if (this.transporter) {
      this.transporter.close();
      this.transporter = null;
    }
  }

  protected readonly onStart = $hook({
    on: "start",
    handler: () => this.verify(),
  });

  protected readonly onStop = $hook({
    on: "stop",
    handler: () => this.close(),
  });
}
