/**
 * IMAP Service
 * Generic IMAP (e.g. Porkbun, Nextcloud mail backend) – connect, list, fetch, move to folder
 */

import { ImapFlow } from 'imapflow'
import { log } from '../middleware/logger.mjs'

const INBOX = 'INBOX'
const FOLDER_PREFIX = 'MailFlow'

/** Map category key to IMAP folder name */
export function getFolderNameForCategory(category) {
  const map = {
    vip: 'VIP',
    customers: 'Clients',
    invoices: 'Invoices',
    newsletters: 'Newsletters',
    promotions: 'Promotions',
    social: 'Social',
    security: 'Security',
    calendar: 'Calendar',
    review: 'Review',
    archive: 'Archive',
  }
  return map[category] || 'Review'
}

/**
 * IMAP Service – same conceptual interface as GmailService/OutlookService
 */
export class ImapService {
  /**
   * @param {object} opts
   * @param {string} opts.host - e.g. imap.porkbun.com
   * @param {number} opts.port - e.g. 993
   * @param {boolean} opts.secure - true for SSL/TLS
   * @param {string} opts.user - email address
   * @param {string} opts.password - app password
   */
  constructor(opts) {
    const { host, port = 993, secure = true, user, password } = opts
    this.client = new ImapFlow({
      host: host || 'imap.porkbun.com',
      port: port || 993,
      secure: secure !== false,
      auth: { user, pass: password },
      logger: false,
    })
    this.lock = null
  }

  async connect() {
    await this.client.connect()
  }

  async close() {
    try {
      if (this.lock) await this.lock.release().catch(() => {})
      await this.client.logout()
    } catch {
      this.client.close()
    }
  }

  /**
   * List messages from INBOX (returns ids = UIDs for use with getEmail/batchGetEmails)
   * @param {number} maxResults
   * @param {string|null} _pageToken - reserved for future pagination
   */
  async listEmails(maxResults = 50, _pageToken = null) {
    let lock = null
    try {
      lock = await this.client.getMailboxLock(INBOX)
      this.lock = lock
      const uids = await this.client.search({ all: true }, { uid: true })
      const slice = uids.slice(0, maxResults)
      const nextPageToken = uids.length > maxResults ? String(slice[slice.length - 1]) : null
      return {
        messages: slice.map((uid) => ({ id: String(uid) })),
        nextPageToken,
      }
    } finally {
      if (lock) {
        lock.release()
        this.lock = null
      }
    }
  }

  /** Normalize ImapFlow message to same shape as Gmail/Outlook (id, headers.from, headers.subject, snippet) */
  _normalize(msg) {
    if (!msg || !msg.envelope) return null
    const from = msg.envelope.from && msg.envelope.from[0] ? (msg.envelope.from[0].address || msg.envelope.from[0].name || '') : ''
    const subject = msg.envelope.subject || ''
    return {
      id: String(msg.uid),
      headers: { from, subject },
      snippet: subject.slice(0, 200),
    }
  }

  /**
   * Get one message by id (UID string)
   */
  async getEmail(messageId) {
    let lock = null
    try {
      lock = await this.client.getMailboxLock(INBOX)
      this.lock = lock
      const list = await this.client.fetchAll(String(messageId), { envelope: true }, { uid: true })
      return this._normalize(list && list[0])
    } finally {
      if (lock) {
        lock.release()
        this.lock = null
      }
    }
  }

  /**
   * Batch get multiple messages by id (UID strings) – single lock, one fetch
   */
  async batchGetEmails(messageIds) {
    if (!messageIds.length) return []
    let lock = null
    try {
      lock = await this.client.getMailboxLock(INBOX)
      this.lock = lock
      const uids = messageIds.map((id) => (typeof id === 'string' ? Number(id) : id)).filter((n) => !Number.isNaN(n))
      if (!uids.length) return []
      const list = await this.client.fetchAll(uids, { envelope: true }, { uid: true })
      return (list || []).map((m) => this._normalize(m)).filter(Boolean)
    } catch (e) {
      log.warn('IMAP batchGetEmails failed', { error: e.message })
      return []
    } finally {
      if (lock) {
        lock.release()
        this.lock = null
      }
    }
  }

  /**
   * Ensure folder exists (create if not). Use subfolder under MailFlow to avoid clutter.
   */
  async ensureFolder(folderName) {
    const path = `${FOLDER_PREFIX}/${folderName}`
    try {
      await this.client.mailboxCreate(path)
      log.info(`IMAP folder created: ${path}`)
    } catch (err) {
      if (err.code !== 'ALREADYEXISTS' && !/already exists/i.test(err.message)) {
        throw err
      }
    }
    return path
  }

  /**
   * Move message (by UID) from INBOX to folder name (under MailFlow/)
   */
  async moveToFolder(messageId, folderName) {
    const path = `${FOLDER_PREFIX}/${folderName}`
    await this.ensureFolder(folderName)
    let lock = null
    try {
      lock = await this.client.getMailboxLock(INBOX)
      this.lock = lock
      await this.client.messageMove(String(messageId), path, { uid: true })
    } finally {
      if (lock) {
        lock.release()
        this.lock = null
      }
    }
  }

  /**
   * Mark message as read (\\Seen)
   */
  async markAsRead(messageId) {
    let lock = null
    try {
      lock = await this.client.getMailboxLock(INBOX)
      this.lock = lock
      await this.client.messageFlagsAdd(String(messageId), ['\\Seen'], { uid: true })
    } finally {
      if (lock) {
        lock.release()
        this.lock = null
      }
    }
  }
}
