import net from 'node:net';
import tls from 'node:tls';
import { Buffer } from 'node:buffer';

type SocketLike = net.Socket | tls.TLSSocket;

const DEFAULT_TIMEOUT_MS = 10000;

function parseBooleanEnv(value: string | undefined, fallback: boolean): boolean {
  if (!value) return fallback;
  const normalized = value.trim().toLowerCase();
  if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
  if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
  return fallback;
}

function parsePort(value: string | undefined, fallback: number): number {
  if (!value) return fallback;
  const parsed = Number(value);
  if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
  return Math.floor(parsed);
}

function buildMessage(input: { from: string; to: string; subject: string; text: string }): string {
  const headers = [
    `From: ${input.from}`,
    `To: ${input.to}`,
    `Subject: ${input.subject}`,
    'MIME-Version: 1.0',
    'Content-Type: text/plain; charset=UTF-8',
  ];

  return `${headers.join('\r\n')}\r\n\r\n${input.text}\r\n`;
}

class SmtpConnection {
  private socket: SocketLike;
  private buffer = '';

  constructor(socket: SocketLike) {
    this.socket = socket;
    this.socket.setEncoding('utf8');
    this.socket.on('data', (chunk: string) => {
      this.buffer += chunk;
    });
  }

  private waitForResponse(expectedCodes: number[]): Promise<string> {
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        cleanup();
        reject(new Error('SMTP response timeout'));
      }, DEFAULT_TIMEOUT_MS);

      const onError = (error: Error) => {
        cleanup();
        reject(error);
      };

      const checkBuffer = () => {
        const lines = this.buffer.split(/\r?\n/).filter(Boolean);
        if (lines.length === 0) return;
        const lastLine = lines[lines.length - 1];
        const match = lastLine.match(/^(\d{3})([\s-])/);
        if (!match) return;
        if (match[2] === '-') return;

        this.buffer = '';
        cleanup();
        const code = Number(match[1]);
        if (!expectedCodes.includes(code)) {
          reject(new Error(`SMTP error ${code}: ${lastLine}`));
          return;
        }
        resolve(lastLine);
      };

      const interval = setInterval(checkBuffer, 20);

      const cleanup = () => {
        clearTimeout(timeout);
        clearInterval(interval);
        this.socket.off('error', onError);
      };

      this.socket.on('error', onError);
      checkBuffer();
    });
  }

  async expect(expectedCodes: number[]) {
    await this.waitForResponse(expectedCodes);
  }

  async command(commandText: string, expectedCodes: number[]) {
    this.socket.write(`${commandText}\r\n`);
    await this.waitForResponse(expectedCodes);
  }

  async writeRaw(data: string, expectedCodes: number[]) {
    this.socket.write(data);
    await this.waitForResponse(expectedCodes);
  }

  end() {
    this.socket.end();
  }
}

function connectPlain(host: string, port: number): Promise<net.Socket> {
  return new Promise((resolve, reject) => {
    const socket = net.connect({ host, port });
    socket.setTimeout(DEFAULT_TIMEOUT_MS);

    const onError = (error: Error) => {
      socket.off('timeout', onTimeout);
      socket.destroy();
      reject(error);
    };

    const onTimeout = () => {
      socket.off('error', onError);
      socket.destroy();
      reject(new Error('SMTP connection timeout'));
    };

    socket.once('error', onError);
    socket.once('timeout', onTimeout);
    socket.once('connect', () => {
      socket.off('error', onError);
      socket.off('timeout', onTimeout);
      socket.setTimeout(0);
      resolve(socket);
    });
  });
}

function connectSecure(host: string, port: number): Promise<tls.TLSSocket> {
  return new Promise((resolve, reject) => {
    const socket = tls.connect({ host, port, servername: host });
    socket.setTimeout(DEFAULT_TIMEOUT_MS);

    const onError = (error: Error) => {
      socket.off('timeout', onTimeout);
      socket.destroy();
      reject(error);
    };

    const onTimeout = () => {
      socket.off('error', onError);
      socket.destroy();
      reject(new Error('SMTP connection timeout'));
    };

    socket.once('error', onError);
    socket.once('timeout', onTimeout);
    socket.once('secureConnect', () => {
      socket.off('error', onError);
      socket.off('timeout', onTimeout);
      socket.setTimeout(0);
      resolve(socket);
    });
  });
}

function upgradeToStartTls(socket: net.Socket, host: string): Promise<tls.TLSSocket> {
  return new Promise((resolve, reject) => {
    const secureSocket = tls.connect({ socket, servername: host });
    secureSocket.setTimeout(DEFAULT_TIMEOUT_MS);

    const onError = (error: Error) => {
      secureSocket.off('timeout', onTimeout);
      secureSocket.destroy();
      reject(error);
    };

    const onTimeout = () => {
      secureSocket.off('error', onError);
      secureSocket.destroy();
      reject(new Error('SMTP STARTTLS timeout'));
    };

    secureSocket.once('error', onError);
    secureSocket.once('timeout', onTimeout);
    secureSocket.once('secureConnect', () => {
      secureSocket.off('error', onError);
      secureSocket.off('timeout', onTimeout);
      secureSocket.setTimeout(0);
      resolve(secureSocket);
    });
  });
}

export async function sendVerificationEmail(input: {
  to: string;
  code: string;
  expiresInMinutes: number;
}) {
  const host = process.env.MAIL_SMTP_HOST || '127.0.0.1';
  const port = parsePort(process.env.MAIL_SMTP_PORT, 587);
  const secure = parseBooleanEnv(process.env.MAIL_SMTP_SECURE, false);
  const useStartTls = parseBooleanEnv(process.env.MAIL_SMTP_STARTTLS, !secure);
  const user = process.env.MAIL_SMTP_USER || process.env.MAIL_FROM;
  const pass = process.env.MAIL_SMTP_PASS;
  const from = process.env.MAIL_FROM;

  if (!from) throw new Error('MAIL_FROM is required');
  if (!user || !pass) throw new Error('MAIL_SMTP_USER and MAIL_SMTP_PASS are required');

  const subject = 'Your Nxtgauge Verification Code';
  const text = [
    'Use this code to complete your verification:',
    '',
    input.code,
    '',
    `This code expires in ${input.expiresInMinutes} minutes.`,
    'If you did not request this code, please ignore this email.',
  ].join('\n');

  const message = buildMessage({ from, to: input.to, subject, text });

  let socket: SocketLike | null = null;
  let client: SmtpConnection | null = null;

  try {
    if (secure) {
      socket = await connectSecure(host, port);
      client = new SmtpConnection(socket);
    } else {
      const plainSocket = await connectPlain(host, port);
      socket = plainSocket;
      client = new SmtpConnection(plainSocket);
    }

    await client.expect([220]);
    await client.command('EHLO nxtgauge.com', [250]);

    if (!secure && useStartTls && socket instanceof net.Socket && !(socket instanceof tls.TLSSocket)) {
      await client.command('STARTTLS', [220]);
      const tlsSocket = await upgradeToStartTls(socket, host);
      socket = tlsSocket;
      client = new SmtpConnection(tlsSocket);
      await client.command('EHLO nxtgauge.com', [250]);
    }

    await client.command('AUTH LOGIN', [334]);
    await client.command(Buffer.from(user).toString('base64'), [334]);
    await client.command(Buffer.from(pass).toString('base64'), [235]);
    await client.command(`MAIL FROM:<${from}>`, [250]);
    await client.command(`RCPT TO:<${input.to}>`, [250, 251]);
    await client.command('DATA', [354]);
    await client.writeRaw(`${message}\r\n.\r\n`, [250]);
    await client.command('QUIT', [221]);
  } finally {
    if (client) client.end();
    else if (socket) socket.end();
  }
}
