import nodemailer from 'nodemailer';
import { google } from 'googleapis';
import { env } from '../../config/env';

export interface MailAttachment {
  filename: string;
  content: string | Buffer;
  contentType?: string;
}

export interface SendMailOptions {
  from: string;
  to: string;
  subject: string;
  html: string;
  attachments?: MailAttachment[];
}

export interface MailSender {
  send(options: SendMailOptions): Promise<void>;
}

export class SmtpMailSender implements MailSender {
  constructor(
    private host = env.SMTP_HOST,
    private port = env.SMTP_PORT,
    private user = env.SMTP_USER,
    private pass = env.SMTP_PASS,
  ) {}

  async send(options: SendMailOptions): Promise<void> {
    if (!this.host) {
      throw new Error('SMTP_HOST is not configured.');
    }
    const transporter = nodemailer.createTransport({
      host: this.host,
      port: this.port,
      secure: this.port === 465,
      auth: this.user && this.pass ? { user: this.user, pass: this.pass } : undefined,
    });
    await transporter.sendMail({
      from: options.from,
      to: options.to,
      subject: options.subject,
      html: options.html,
      attachments: options.attachments?.map((a) => ({
        filename: a.filename,
        content: a.content,
        contentType: a.contentType,
      })),
    });
  }
}

/** Send via Gmail API (users.messages.send) using OAuth refresh token. */
export class GmailApiMailSender implements MailSender {
  async send(options: SendMailOptions): Promise<void> {
    const clientId = env.GOOGLE_CLIENT_ID;
    const clientSecret = env.GOOGLE_CLIENT_SECRET;
    const refreshToken = env.GMAIL_REFRESH_TOKEN;
    if (!clientId || !clientSecret || !refreshToken) {
      throw new Error(
        'Gmail API requires GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GMAIL_REFRESH_TOKEN.',
      );
    }

    const oauth2 = new google.auth.OAuth2(clientId, clientSecret);
    oauth2.setCredentials({ refresh_token: refreshToken });
    const gmail = google.gmail({ version: 'v1', auth: oauth2 });

    const raw = await buildRawMime(options);
    await gmail.users.messages.send({
      userId: 'me',
      requestBody: { raw },
    });
  }
}

export function createPlatformMailSender(): MailSender {
  if (env.MAIL_TRANSPORT === 'gmail_api') {
    return new GmailApiMailSender();
  }
  return new SmtpMailSender();
}

export function isPlatformMailConfigured(): boolean {
  if (env.MAIL_TRANSPORT === 'gmail_api') {
    return Boolean(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET && env.GMAIL_REFRESH_TOKEN);
  }
  return Boolean(env.SMTP_HOST);
}

async function buildRawMime(options: SendMailOptions): Promise<string> {
  const boundary = `boundary_${Date.now()}`;
  const parts: string[] = [
    `From: ${options.from}`,
    `To: ${options.to}`,
    `Subject: ${encodeSubject(options.subject)}`,
    'MIME-Version: 1.0',
  ];

  if (options.attachments?.length) {
    parts.push(`Content-Type: multipart/mixed; boundary="${boundary}"`, '', `--${boundary}`);
    parts.push('Content-Type: text/html; charset="UTF-8"', 'Content-Transfer-Encoding: 7bit', '');
    parts.push(options.html, '');
    for (const att of options.attachments) {
      const content =
        typeof att.content === 'string' ? Buffer.from(att.content) : Buffer.from(att.content);
      parts.push(`--${boundary}`);
      parts.push(
        `Content-Type: ${att.contentType ?? 'application/octet-stream'}; name="${att.filename}"`,
      );
      parts.push(`Content-Disposition: attachment; filename="${att.filename}"`);
      parts.push('Content-Transfer-Encoding: base64', '');
      parts.push(content.toString('base64').replace(/(.{76})/g, '$1\r\n'), '');
    }
    parts.push(`--${boundary}--`);
  } else {
    parts.push('Content-Type: text/html; charset="UTF-8"', 'Content-Transfer-Encoding: 7bit', '');
    parts.push(options.html);
  }

  return Buffer.from(parts.join('\r\n'))
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

function encodeSubject(subject: string): string {
  if (/^[\x20-\x7E]*$/.test(subject)) return subject;
  return `=?UTF-8?B?${Buffer.from(subject).toString('base64')}?=`;
}
