import { Injectable, OnModuleInit, OnModuleDestroy, Logger, Inject, forwardRef } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose'
import { Model } from 'mongoose'
import { ImapFlow } from 'imapflow'
import { simpleParser } from 'mailparser'
import { EmailServerSettings, EmailServerSettingsDocument } from '../common/schemas'
import { EmailService } from './email.service'

/**
 * IMAP Polling Service
 *
 * Connects to the configured IMAP mailbox and imports new messages on a
 * configurable interval. Messages are marked \Seen after ingestion so they
 * are not imported twice.
 *
 * Polling only starts when:
 *   - imapEnabled = true in EmailServerSettings
 *   - imapHost, imapUser, imapPassword are all set
 *
 * The service polls on a timer. Call `reload()` after saving settings to
 * restart the polling loop with new credentials.
 */
@Injectable()
export class ImapPollingService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(ImapPollingService.name)
  private timer: ReturnType<typeof setTimeout> | null = null
  private running = false
  private destroyed = false

  constructor(
    @InjectModel(EmailServerSettings.name) private settingsModel: Model<EmailServerSettingsDocument>,
    @Inject(forwardRef(() => EmailService)) private readonly emailService: EmailService,
  ) {}

  async onModuleInit(): Promise<void> {
    // Delay slightly to let MongoDB connection settle
    setTimeout(() => this.scheduleNext(), 5000)
  }

  /**
   * Called from EmailService.saveSettings() so the loop restarts immediately
   * when an admin saves new IMAP credentials.
   */
  async reload(): Promise<void> {
    this.stop()
    await this.scheduleNext()
  }

  onModuleDestroy() {
    this.destroyed = true
    this.stop()
  }

  private stop() {
    if (this.timer) { clearTimeout(this.timer); this.timer = null }
  }

  private async scheduleNext(): Promise<void> {
    if (this.destroyed) return
    const settings = await this.loadSettings()
    if (!settings?.imapEnabled || !settings.imapHost || !settings.imapUser || !settings.imapPassword) {
      this.logger.debug('IMAP polling disabled or not configured — skipping')
      return
    }

    const intervalMs = (settings.imapPollIntervalSeconds ?? 60) * 1000
    this.timer = setTimeout(async () => {
      await this.poll(settings)
      await this.scheduleNext()
    }, intervalMs)

    this.logger.log(`IMAP poll scheduled in ${settings.imapPollIntervalSeconds}s (${settings.imapUser}@${settings.imapHost})`)
  }

  private async poll(settings: EmailServerSettingsDocument): Promise<void> {
    if (this.running) return
    this.running = true

    const client = new ImapFlow({
      host:   settings.imapHost!,
      port:   settings.imapPort ?? 993,
      secure: settings.imapSsl ?? true,
      auth: {
        user: settings.imapUser!,
        pass: settings.imapPassword!,
      },
      logger: false,
    })

    let imported = 0

    try {
      await client.connect()
      const folder = settings.imapFolder ?? 'INBOX'

      const lock = await client.getMailboxLock(folder)
      try {
        // Fetch all unseen messages
        for await (const msg of client.fetch('1:*', { envelope: true, flags: true, source: true })) {
          if (msg.flags?.has('\\Seen')) continue
          if (!msg.source) continue

          try {
            const parsed = await simpleParser(msg.source as any)
            await this.emailService.ingestInbound(parsed, settings.imapUser ?? '')

            // Mark as seen so we don't re-import
            await client.messageFlagsAdd(String(msg.uid), ['\\Seen'], { uid: true })
            imported++
          } catch (err: any) {
            this.logger.warn(`Failed to ingest message uid=${msg.uid}: ${err.message}`)
          }
        }
      } finally {
        lock.release()
      }

      if (imported > 0) {
        this.logger.log(`IMAP poll complete — imported ${imported} new message(s)`)
      }
    } catch (err: any) {
      this.logger.error(`IMAP connection failed (${settings.imapHost}): ${err.message}`)
    } finally {
      try { await client.logout() } catch {}
      this.running = false
    }
  }

  private async loadSettings(): Promise<EmailServerSettingsDocument | null> {
    return this.settingsModel.findOne({ key: 'email' }).exec()
  }
}
