import { ImapFlow } from 'imapflow';
import { encryptionService } from './encryptionService';
import { UserMailAccount } from './userMailAccountService';

interface PoolEntry {
  client: ImapFlow;
  accountId: number;
  lastUsed: number;
  busy: boolean;
  queue: Array<{ fn: (client: ImapFlow) => Promise<any>; resolve: (v: any) => void; reject: (e: any) => void }>;
}

class ImapConnectionPool {
  private pool = new Map<number, PoolEntry>();
  private readonly maxIdleMs = 300_000;

  constructor() {
    setInterval(() => this.cleanupIdle(), 60_000);
    process.on('SIGTERM', () => this.destroyAll());
    process.on('SIGINT', () => this.destroyAll());
  }

  async execute<T>(account: UserMailAccount, fn: (client: ImapFlow) => Promise<T>): Promise<T> {
    const decryptedPassword = encryptionService.decrypt(account.password);
    let entry = this.pool.get(account.id);

    if (entry && !entry.client.usable) {
      this.pool.delete(account.id);
      entry = undefined;
    }

    if (!entry) {
      const client = new ImapFlow({
        host: account.host,
        port: account.port,
        secure: account.useTls,
        auth: { user: account.username, pass: decryptedPassword },
        logger: false as any,
        tls: { rejectUnauthorized: false },
      });
      await client.connect();
      entry = { client, accountId: account.id, lastUsed: Date.now(), busy: false, queue: [] };
      this.pool.set(account.id, entry);

      client.on('close', () => {
        const e = this.pool.get(account.id);
        if (e && e.client === client) {
          this.pool.delete(account.id);
          for (const pending of e.queue) pending.reject(new Error('IMAP 연결이 끊겼습니다'));
          e.queue = [];
        }
      });
    }

    if (entry.busy) {
      return new Promise<T>((resolve, reject) => {
        entry!.queue.push({ fn: fn as any, resolve, reject });
      });
    }

    return this.runWithEntry(entry, fn);
  }

  private async runWithEntry<T>(entry: PoolEntry, fn: (client: ImapFlow) => Promise<T>): Promise<T> {
    entry.busy = true;
    entry.lastUsed = Date.now();
    try {
      return await fn(entry.client);
    } catch (err) {
      if (!entry.client.usable) {
        this.pool.delete(entry.accountId);
      }
      throw err;
    } finally {
      entry.busy = false;
      if (entry.queue.length > 0) {
        const next = entry.queue.shift()!;
        this.runWithEntry(entry, next.fn).then(next.resolve).catch(next.reject);
      }
    }
  }

  private cleanupIdle() {
    const now = Date.now();
    for (const [id, entry] of this.pool.entries()) {
      if (!entry.busy && entry.queue.length === 0 && now - entry.lastUsed > this.maxIdleMs) {
        try { entry.client.logout(); } catch {}
        this.pool.delete(id);
      }
    }
  }

  destroyByAccount(accountId: number) {
    const entry = this.pool.get(accountId);
    if (entry) {
      try { entry.client.logout(); } catch {}
      this.pool.delete(accountId);
    }
  }

  destroyAll() {
    for (const entry of this.pool.values()) {
      try { entry.client.logout(); } catch {}
    }
    this.pool.clear();
  }
}

export const imapConnectionPool = new ImapConnectionPool();
