/**
 * Email Routes
 * Email account management and sorting
 */

import express from 'express'
import { asyncHandler, NotFoundError, AuthorizationError, ValidationError } from '../middleware/errorHandler.mjs'
import { validate, rules } from '../middleware/validate.mjs'
import { limiters } from '../middleware/rateLimit.mjs'
import { respond } from '../utils/response.mjs'
import { emailAccounts, emailStats, emailDigests, userPreferences, emailUsage, subscriptions } from '../services/database.mjs'
import { config, features, isAdmin } from '../config/index.mjs'
import { log } from '../middleware/logger.mjs'
import { requireAuthUnlessEmailWebhook } from '../middleware/auth.mjs'
import { encryptImapSecret, decryptImapSecret } from '../utils/crypto.mjs'

const router = express.Router()

router.use(requireAuthUnlessEmailWebhook)

// Lazy load heavy services
let gmailServiceClass = null
let outlookServiceClass = null
let aiSorterInstance = null

async function getGmailService(accessToken, refreshToken) {
  if (!gmailServiceClass) {
    const { GmailService } = await import('../services/gmail.mjs')
    gmailServiceClass = GmailService
  }
  return new gmailServiceClass(accessToken, refreshToken)
}

async function getOutlookService(accessToken) {
  if (!outlookServiceClass) {
    const { OutlookService } = await import('../services/outlook.mjs')
    outlookServiceClass = OutlookService
  }
  return new outlookServiceClass(accessToken)
}

async function getAISorter() {
  if (!aiSorterInstance) {
    const { AISorterService } = await import('../services/ai-sorter.mjs')
    aiSorterInstance = new AISorterService()
  }
  return aiSorterInstance
}

// ═══════════════════════════════════════════════════════════════════════════
// DEMO DATA - Realistic Test Emails
// ═══════════════════════════════════════════════════════════════════════════

const DEMO_EMAILS = [
  { from: 'boss@company.com', subject: 'Urgent: Meeting tomorrow 9 AM', snippet: 'Hi, we need to discuss the new project urgently...' },
  { from: 'billing@amazon.com', subject: 'Your invoice for Order #US123456', snippet: 'Thank you for your order. Please find your invoice attached...' },
  { from: 'newsletter@techcrunch.com', subject: 'TechCrunch Weekly: AI Revolution 2026', snippet: 'This week in Tech: OpenAI announces new model...' },
  { from: 'noreply@linkedin.com', subject: '5 new connection requests', snippet: 'You have new connection requests from: John Smith, Jane Doe...' },
  { from: 'support@stripe.com', subject: 'Payment confirmation: $49.00', snippet: 'Your payment was successfully processed...' },
  { from: 'client@acme-corp.com', subject: 'RE: Web development proposal', snippet: 'Thank you for your proposal. We have a few questions...' },
  { from: 'noreply@google.com', subject: 'Security alert: New sign-in detected', snippet: 'We detected a new sign-in from Windows Chrome...' },
  { from: 'marketing@shopify.com', subject: '50% off today only!', snippet: 'Exclusive offer for our premium customers...' },
  { from: 'team@slack.com', subject: 'New message in #general', snippet: '@Max posted a message: Hey team, how\'s it going...' },
  { from: 'calendar@gmail.com', subject: 'Invitation: Project meeting (Monday 2 PM)', snippet: 'You have been invited to a meeting...' },
  { from: 'john.doe@gmail.com', subject: 'Important: Documents needed by tomorrow', snippet: 'Hi, could you please send the documents by tomorrow morning...' },
  { from: 'billing@verizon.com', subject: 'Your phone bill January 2026', snippet: 'Dear customer, your bill is ready for download...' },
  { from: 'deals@ebay.com', subject: 'Your Watchlist: Price drops!', snippet: '3 items on your watchlist are now cheaper...' },
  { from: 'no-reply@github.com', subject: '[GitHub] Security alert: Dependabot', snippet: 'We found a potential security vulnerability in...' },
  { from: 'newsletter@wired.com', subject: 'Wired Weekly: Cloud Computing Trends', snippet: 'The most important tech news of the week...' },
  { from: 'support@bank.com', subject: 'Account statement January 2026', snippet: 'Your monthly account statement is ready...' },
  { from: 'noreply@twitter.com', subject: '@TechNews replied to your tweet', snippet: 'TechNews: "That\'s an interesting perspective..."' },
  { from: 'info@ups.com', subject: 'Your package is on the way!', snippet: 'Tracking number: 1Z999AA10123456784, Expected delivery...' },
  { from: 'team@notion.so', subject: 'New comments in your workspace', snippet: 'Anna added a comment to "Project Plan"...' },
  { from: 'service@insurance.com', subject: 'Your policy has been updated', snippet: 'Dear customer, we have made changes to your policy...' },
]

/**
 * POST /api/email/connect
 * Connect a new email account
 */
router.post('/connect',
  validate({
    body: {
      provider: [rules.required('provider'), rules.isIn('provider', ['gmail', 'outlook', 'demo', 'imap'])],
      email: [rules.required('email'), rules.email()],
    },
  }),
  asyncHandler(async (req, res) => {
    const userId = req.appwriteUser.id
    const { provider, email, accessToken, refreshToken, expiresAt, password, imapHost, imapPort, imapSecure } = req.body

    // IMAP: require password (or accessToken as password)
    if (provider === 'imap') {
      const imapPassword = password || accessToken
      if (!imapPassword) {
        throw new ValidationError('IMAP account requires a password or app password', { password: ['Required for IMAP'] })
      }
    }

    // Check if account already exists
    const existingAccounts = await emailAccounts.getByUser(userId)
    const alreadyConnected = existingAccounts.find(a => a.email === email)

    if (alreadyConnected) {
      throw new ValidationError('This email account is already connected', {
        email: ['Already connected'],
      })
    }

    // IMAP: verify connection before saving
    if (provider === 'imap') {
      const { ImapService } = await import('../services/imap.mjs')
      const imapPassword = password || accessToken
      const imap = new ImapService({
        host: imapHost || 'imap.porkbun.com',
        port: imapPort != null ? Number(imapPort) : 993,
        secure: imapSecure !== false,
        user: email,
        password: imapPassword,
      })
      try {
        await imap.connect()
        await imap.listEmails(1)
        await imap.close()
      } catch (err) {
        log.warn('IMAP connection test failed', { email, error: err.message })
        throw new ValidationError('IMAP connection failed. Check email and password (use app password if 2FA is on).', { password: [err.message || 'Connection failed'] })
      }
    }

    // Create account
    const rawImapSecret = provider === 'imap' ? (password || accessToken) : ''
    const accountData = {
      userId,
      provider,
      email,
      accessToken: provider === 'imap' ? encryptImapSecret(rawImapSecret) : (accessToken || ''),
      refreshToken: provider === 'imap' ? '' : (refreshToken || ''),
      expiresAt: provider === 'imap' ? 0 : (expiresAt || 0),
      isActive: true,
      lastSync: null,
    }
    if (provider === 'imap') {
      if (imapHost != null) accountData.imapHost = String(imapHost)
      if (imapPort != null) accountData.imapPort = Number(imapPort)
      if (imapSecure !== undefined) accountData.imapSecure = Boolean(imapSecure)
    }
    const account = await emailAccounts.create(accountData)

    log.success(`Email account connected: ${email} (${provider})`)

    respond.created(res, {
      accountId: account.$id,
      email: account.email,
      provider: account.provider,
    })
  })
)

/**
 * POST /api/email/connect-demo
 * Connect a demo email account for testing
 */
router.post('/connect-demo',
  asyncHandler(async (req, res) => {
    const userId = req.appwriteUser.id
    const demoEmail = `demo-${userId.slice(0, 8)}@mailflow.demo`

    // Check if demo account already exists
    const existingAccounts = await emailAccounts.getByUser(userId)
    const alreadyConnected = existingAccounts.find(a => a.provider === 'demo')

    if (alreadyConnected) {
      return respond.success(res, {
        accountId: alreadyConnected.$id,
        email: alreadyConnected.email,
        provider: 'demo',
        message: 'Demo account already connected',
      })
    }

    // Create demo account
    const account = await emailAccounts.create({
      userId,
      provider: 'demo',
      email: demoEmail,
      accessToken: 'demo-token',
      refreshToken: '',
      expiresAt: 0,
      isActive: true,
      lastSync: null,
    })

    log.success(`Demo account created for user ${userId}`)

    respond.created(res, {
      accountId: account.$id,
      email: account.email,
      provider: 'demo',
      message: 'Demo account successfully created',
    })
  })
)

/**
 * GET /api/email/accounts
 * Get user's connected email accounts
 */
router.get('/accounts', asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id

  const accounts = await emailAccounts.getByUser(userId)

  // Don't expose tokens
  const safeAccounts = accounts.map(acc => ({
    id: acc.$id,
    email: acc.email,
    provider: acc.provider,
    connected: true,
    lastSync: acc.lastSync,
    isDemo: acc.provider === 'demo',
  }))

  respond.success(res, safeAccounts)
}))

/**
 * DELETE /api/email/accounts/:accountId
 * Disconnect an email account
 */
router.delete('/accounts/:accountId', asyncHandler(async (req, res) => {
  const { accountId } = req.params
  const userId = req.appwriteUser.id

  // Verify ownership
  const account = await emailAccounts.get(accountId)

  if (account.userId !== userId) {
    throw new AuthorizationError('No permission for this account')
  }

  await emailAccounts.deactivate(accountId)

  log.info(`Email account disconnected: ${account.email}`)

  respond.success(res, null, 'Account successfully disconnected')
}))

/**
 * GET /api/email/stats
 * Get email sorting statistics
 */
router.get('/stats', asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id

  const stats = await emailStats.getByUser(userId)

  if (!stats) {
    // Return empty stats for new users
    return respond.success(res, {
      totalSorted: 0,
      todaySorted: 0,
      weekSorted: 0,
      categories: {},
      timeSaved: 0,
    })
  }

  respond.success(res, {
    totalSorted: stats.totalSorted || 0,
    todaySorted: stats.todaySorted || 0,
    weekSorted: stats.weekSorted || 0,
    categories: stats.categoriesJson ? JSON.parse(stats.categoriesJson) : {},
    timeSaved: stats.timeSavedMinutes || 0,
  })
}))

/**
 * POST /api/email/sort
 * Trigger email sorting for an account
 * 
 * Options:
 * - maxEmails: Maximum emails to process (default: 500, max: 2000)
 * - processAll: If true, process entire inbox with pagination
 */
router.post('/sort',
  limiters.emailSort,
  validate({
    body: {
      accountId: [rules.required('accountId')],
    },
  }),
  asyncHandler(async (req, res) => {
    const userId = req.appwriteUser.id
    const { accountId, maxEmails = 500, processAll = true } = req.body
    
    // Check subscription status and free tier limits
    const subscription = await subscriptions.getByUser(userId, req.appwriteUser?.email)
    const isFreeTier = subscription?.isFreeTier || false
    const adminUser = isAdmin(req.appwriteUser?.email)

    // Check free tier limit (admins: unlimited)
    if (isFreeTier && !adminUser) {
      const usage = await emailUsage.getUsage(userId)
      const limit = subscription?.emailsLimit || config.freeTier.emailsPerMonth
      
      if (usage.emailsProcessed >= limit) {
        return respond.error(res, {
          code: 'LIMIT_REACHED',
          message: `You've processed ${limit} emails this month. Upgrade for unlimited sorting.`,
          limit,
          used: usage.emailsProcessed,
        }, 403)
      }
    }
    
    // Check if this is first run (no stats exist)
    const existingStats = await emailStats.getByUser(userId)
    const isFirstRun = !existingStats || existingStats.totalSorted === 0
    
    // For first run, limit to 50 emails for speed
    const effectiveMax = isFirstRun 
      ? Math.min(maxEmails, 50) 
      : Math.min(maxEmails, 2000) // Cap at 2000 emails

    // Get account
    const account = await emailAccounts.get(accountId)

    if (account.userId !== userId) {
      throw new AuthorizationError('No permission for this account')
    }

    // Get user preferences
    const prefs = await userPreferences.getByUser(userId)
    const preferences = prefs?.preferences || {}

    // Get AI sorter
    const sorter = await getAISorter()
    let sortedCount = 0
    const results = { byCategory: {} }
    let emailSamples = [] // For suggested rules generation

    // ═══════════════════════════════════════════════════════════════════════
    // DEMO MODE - Sorting with simulated emails
    // ═══════════════════════════════════════════════════════════════════════
    if (account.provider === 'demo') {
      log.info(`Demo sorting started for user ${userId}`)

      // Select random emails from the demo pool
      const emailCount = Math.min(maxEmails, DEMO_EMAILS.length)
      const shuffled = [...DEMO_EMAILS].sort(() => Math.random() - 0.5)
      const emailsToSort = shuffled.slice(0, emailCount)

      // Check if AI is available
      if (features.ai()) {
        // Real AI sorting with demo data
        const classified = await sorter.batchCategorize(emailsToSort, preferences)

        for (const { email, classification } of classified) {
          const category = classification.category
          sortedCount++
          results.byCategory[category] = (results.byCategory[category] || 0) + 1
          
          // Collect samples for suggested rules (first run only, max 50)
          if (isFirstRun && emailSamples.length < 50) {
            emailSamples.push({
              from: email.from,
              subject: email.subject,
              snippet: email.snippet,
              category,
            })
          }
        }

        log.success(`AI sorting completed: ${sortedCount} demo emails`)
      } else {
        // Fallback without AI - simulated categorization
        const categoryDistribution = {
          vip: 0.1,
          customers: 0.15,
          invoices: 0.15,
          newsletters: 0.2,
          social: 0.15,
          promotions: 0.1,
          security: 0.05,
          calendar: 0.05,
          review: 0.05,
        }

        for (const email of emailsToSort) {
          // Simple rule-based categorization
          let category = 'review'
          const from = email.from.toLowerCase()
          const subject = email.subject.toLowerCase()

          if (from.includes('boss') || from.includes('john.doe')) {
            category = 'vip'
          } else if (from.includes('billing') || subject.includes('invoice') || subject.includes('bill')) {
            category = 'invoices'
          } else if (from.includes('newsletter') || subject.includes('weekly') || subject.includes('news')) {
            category = 'newsletters'
          } else if (from.includes('linkedin') || from.includes('twitter') || from.includes('slack')) {
            category = 'social'
          } else if (from.includes('client') || subject.includes('proposal') || subject.includes('project')) {
            category = 'customers'
          } else if (from.includes('security') || subject.includes('security') || subject.includes('sign-in')) {
            category = 'security'
          } else if (subject.includes('off') || subject.includes('deal') || from.includes('marketing')) {
            category = 'promotions'
          } else if (subject.includes('invitation') || subject.includes('meeting') || subject.includes('calendar')) {
            category = 'calendar'
          }

          sortedCount++
          results.byCategory[category] = (results.byCategory[category] || 0) + 1
          
          // Collect samples for suggested rules (first run only, max 50)
          if (isFirstRun && emailSamples.length < 50) {
            emailSamples.push({
              from: email.from,
              subject: email.subject,
              snippet: email.snippet || '',
              category,
            })
          }
        }

        log.success(`Rule-based sorting completed: ${sortedCount} demo emails`)
      }
    }
    // ═══════════════════════════════════════════════════════════════════════
    // GMAIL - Real Gmail sorting with native categories
    // ═══════════════════════════════════════════════════════════════════════
    else if (account.provider === 'gmail') {
      if (!features.ai()) {
        throw new ValidationError('AI sorting is not configured. Please set MISTRAL_API_KEY.')
      }

      if (!account.accessToken) {
        throw new ValidationError('Gmail account needs to be reconnected')
      }

      log.info(`Gmail sorting started for ${account.email}`)

      try {
        const gmail = await getGmailService(account.accessToken, account.refreshToken)

        // FIRST: Clean up old "MailFlow/..." labels
        const deletedLabels = await gmail.cleanupOldLabels()
        if (deletedLabels > 0) {
          log.success(`${deletedLabels} old labels cleaned up`)
        }

        // Create labels for categories and company labels
        const categories = sorter.getCategories()
        const labelMap = {}
        const companyLabelMap = {}

        // Create labels for enabled categories only
        const enabledCategories = sorter.getEnabledCategories(preferences)
        for (const [key, cat] of Object.entries(categories)) {
          // Skip disabled categories
          if (!enabledCategories.includes(key)) continue

          // Wenn Gmail-Kategorie existiert, diese verwenden
          const gmailCat = sorter.getGmailCategory(key)
          if (gmailCat) {
            labelMap[key] = gmailCat // z.B. CATEGORY_SOCIAL
          } else {
            // Create custom label (clean name without prefix)
            try {
              const label = await gmail.createLabel(cat.name, cat.color)
              if (label) {
                labelMap[key] = label.id
              }
            } catch (err) {
              log.warn(`Failed to create label: ${cat.name}`)
            }
          }
        }

        // Create company labels
        if (preferences.companyLabels?.length) {
          for (const companyLabel of preferences.companyLabels) {
            if (!companyLabel.enabled) continue

            try {
              // Use orange color for company labels
              const label = await gmail.createLabel(companyLabel.name, '#ff9800')
              if (label) {
                companyLabelMap[companyLabel.id || companyLabel.name] = label.id
              }
            } catch (err) {
              log.warn(`Failed to create company label: ${companyLabel.name}`)
            }
          }
        }

        // Create auto-detected company labels if enabled
        if (preferences.autoDetectCompanies) {
          const knownCompanies = ['Amazon', 'Google', 'Microsoft', 'Apple', 'Facebook', 'Twitter', 'LinkedIn', 'GitHub', 'Netflix', 'Spotify', 'PayPal', 'Stripe', 'Shopify', 'Uber', 'Airbnb', 'Dropbox', 'Slack', 'Zoom']
          for (const companyName of knownCompanies) {
            try {
              const label = await gmail.createLabel(companyName, '#ff9800')
              if (label) {
                companyLabelMap[companyName] = label.id
              }
            } catch (err) {
              // Label might already exist, ignore
            }
          }
        }

        // Create name labels (workers) – personal labels per team member
        const nameLabelMap = {}
        if (preferences.nameLabels?.length) {
          for (const nl of preferences.nameLabels) {
            if (!nl.enabled) continue
            try {
              const labelName = `MailFlow/Team/${nl.name}`
              const label = await gmail.createLabel(labelName, '#4a86e8')
              if (label) {
                nameLabelMap[nl.id || nl.name] = label.id
                if (nl.name !== (nl.id || nl.name)) nameLabelMap[nl.name] = label.id
              }
            } catch (err) {
              log.warn(`Failed to create name label: ${nl.name}`)
            }
          }
        }

        // Fetch and process ALL emails with pagination
        let pageToken = null
        let totalProcessed = 0
        const batchSize = 100 // Gmail API batch size

        do {
          // Fetch batch of messages
          const { messages, nextPageToken } = await gmail.listEmails(
            batchSize, 
            pageToken, 
            'in:inbox' // All inbox emails
          )
          pageToken = nextPageToken

          if (!messages?.length) break

          // Get full email details
          const emails = await gmail.batchGetEmails(messages.map(m => m.id))

          // Process each email: check company labels first, then AI categorization
          const processedEmails = []

          for (const email of emails) {
            const emailData = {
              from: email.headers?.from || '',
              subject: email.headers?.subject || '',
              snippet: email.snippet || '',
            }

            let category = null
            let companyLabel = null
            let assignedTo = null
            let skipAI = false

            // PRIORITY 1: Check custom company labels
            if (preferences.companyLabels?.length) {
              for (const companyLabelConfig of preferences.companyLabels) {
                if (!companyLabelConfig.enabled) continue

                if (sorter.matchesCompanyLabel(emailData, companyLabelConfig)) {
                  category = companyLabelConfig.category || 'promotions'
                  companyLabel = companyLabelConfig.name
                  skipAI = true
                  break
                }
              }
            }

            // PRIORITY 2: Check auto-detected companies
            if (!skipAI && preferences.autoDetectCompanies) {
              const detected = sorter.detectCompany(emailData)
              if (detected) {
                category = 'promotions' // Default category for companies
                companyLabel = detected.label
                skipAI = true
              }
            }

            // PRIORITY 3: AI categorization (if no company label matched)
            if (!skipAI) {
              const classification = await sorter.categorize(emailData, preferences)
              category = classification.category
              assignedTo = classification.assignedTo || null

              // If category is disabled, fallback to review
              if (!enabledCategories.includes(category)) {
                category = 'review'
              }
            }

            processedEmails.push({
              email,
              category,
              companyLabel,
              assignedTo,
            })

            // Collect samples for suggested rules (first run only, max 50)
            if (isFirstRun && emailSamples.length < 50) {
              emailSamples.push({
                from: emailData.from,
                subject: emailData.subject,
                snippet: emailData.snippet,
                category,
              })
            }
          }

          // Apply labels/categories and actions
          for (const { email, category, companyLabel, assignedTo } of processedEmails) {
            const action = sorter.getCategoryAction(category, preferences)

            try {
              const labelsToAdd = []
              const labelsToRemove = []
              
              // Add company label if matched
              if (companyLabel && companyLabelMap[companyLabel]) {
                labelsToAdd.push(companyLabelMap[companyLabel])
              }

              // Add name label (worker) if AI assigned email to a person
              if (assignedTo && nameLabelMap[assignedTo]) {
                labelsToAdd.push(nameLabelMap[assignedTo])
              }

              // Add category label/category
              if (labelMap[category]) {
                labelsToAdd.push(labelMap[category])
              }

              // Handle different actions
              switch (action) {
                case 'star':
                  // Keep in inbox + add star
                  labelsToAdd.push('STARRED')
                  break

                case 'archive_read':
                  // Archive + mark as read (clean inbox)
                  labelsToRemove.push('INBOX', 'UNREAD')
                  break

                case 'archive':
                  // Just archive (legacy)
                  labelsToRemove.push('INBOX')
                  break

                case 'inbox':
                default:
                  // Keep in inbox, no changes needed
                  break
              }

              // Apply label changes
              if (labelsToAdd.length > 0) {
                await gmail.addLabels(email.id, labelsToAdd)
              }
              if (labelsToRemove.length > 0) {
                await gmail.removeLabels(email.id, labelsToRemove)
              }

              sortedCount++
              results.byCategory[category] = (results.byCategory[category] || 0) + 1
            } catch (err) {
              log.warn(`Email sorting failed: ${email.id}`, { error: err.message })
            }
          }

          totalProcessed += emails.length
          log.info(`Processed ${totalProcessed} emails so far...`)

          // Stop if we've hit the limit or there's no more pages
          if (totalProcessed >= effectiveMax) {
            log.info(`Reached limit of ${effectiveMax} emails`)
            break
          }

          // Small delay between batches to avoid rate limiting
          if (pageToken) {
            await new Promise(r => setTimeout(r, 200))
          }
        } while (pageToken && processAll)

        log.success(`Gmail sorting completed: ${sortedCount} emails processed`)
      } catch (err) {
        log.error('Gmail sorting failed', { error: err.message })
        throw new ValidationError(`Gmail error: ${err.message}. Please reconnect account.`)
      }
    }
    // ═══════════════════════════════════════════════════════════════════════
    // OUTLOOK - Real Outlook sorting
    // ═══════════════════════════════════════════════════════════════════════
    else if (account.provider === 'outlook') {
      if (!features.ai()) {
        throw new ValidationError('AI sorting is not configured. Please set MISTRAL_API_KEY.')
      }

      if (!account.accessToken) {
        throw new ValidationError('Outlook account needs to be reconnected')
      }

      log.info(`Outlook sorting started for ${account.email}`)

      try {
        const outlook = await getOutlookService(account.accessToken)

        // Fetch and process ALL emails with pagination
        let skipToken = null
        let totalProcessed = 0
        const batchSize = 100

        do {
          // Fetch batch of messages
          const { messages, nextLink } = await outlook.listEmails(batchSize, skipToken)
          skipToken = nextLink

          if (!messages?.length) break

          // Process each email: check company labels first, then AI categorization
          const enabledCategories = sorter.getEnabledCategories(preferences)
          const processedEmails = []

          for (const email of messages) {
            const emailData = {
              from: email.from?.emailAddress?.address || '',
              subject: email.subject || '',
              snippet: email.bodyPreview || '',
            }

            let category = null
            let companyLabel = null
            let skipAI = false

            // PRIORITY 1: Check custom company labels
            if (preferences.companyLabels?.length) {
              for (const companyLabelConfig of preferences.companyLabels) {
                if (!companyLabelConfig.enabled) continue

                if (sorter.matchesCompanyLabel(emailData, companyLabelConfig)) {
                  category = companyLabelConfig.category || 'promotions'
                  companyLabel = companyLabelConfig.name
                  skipAI = true
                  break
                }
              }
            }

            // PRIORITY 2: Check auto-detected companies
            if (!skipAI && preferences.autoDetectCompanies) {
              const detected = sorter.detectCompany(emailData)
              if (detected) {
                category = 'promotions' // Default category for companies
                companyLabel = detected.label
                skipAI = true
              }
            }

            // PRIORITY 3: AI categorization (if no company label matched)
            if (!skipAI) {
              const classification = await sorter.categorize(emailData, preferences)
              category = classification.category

              // If category is disabled, fallback to review
              if (!enabledCategories.includes(category)) {
                category = 'review'
              }
            }

            processedEmails.push({
              email,
              category,
              companyLabel,
            })
          }

          // Apply categories and actions
          for (const { email, category, companyLabel } of processedEmails) {
            const action = sorter.getCategoryAction(category, preferences)
            const catName = sorter.getLabelName(category)

            try {
              // Add company label as category if matched
              if (companyLabel) {
                await outlook.addCategories(email.id, [companyLabel])
              }

              // Add category
              await outlook.addCategories(email.id, [catName])

              // Handle different actions
              switch (action) {
                case 'archive_read':
                  // Archive + mark as read
                  await outlook.archiveEmail(email.id)
                  await outlook.markAsRead(email.id)
                  break

                case 'archive':
                  await outlook.archiveEmail(email.id)
                  break

                case 'star':
                  await outlook.flagEmail(email.id)
                  break
              }

              sortedCount++
              results.byCategory[category] = (results.byCategory[category] || 0) + 1
            } catch (err) {
              log.warn(`Email sorting failed: ${email.id}`)
            }
          }

          totalProcessed += messages.length
          log.info(`Processed ${totalProcessed} emails so far...`)

          // Stop if we've hit the limit
          if (totalProcessed >= effectiveMax) {
            log.info(`Reached limit of ${effectiveMax} emails`)
            break
          }

          // Small delay between batches
          if (skipToken) {
            await new Promise(r => setTimeout(r, 200))
          }
        } while (skipToken && processAll)

        log.success(`Outlook sorting completed: ${sortedCount} emails processed`)
      } catch (err) {
        log.error('Outlook sorting failed', { error: err.message })
        throw new ValidationError(`Outlook error: ${err.message}. Please reconnect account.`)
      }
    }
    // ═══════════════════════════════════════════════════════════════════════
    // IMAP (Porkbun, Nextcloud mail backend, etc.)
    // ═══════════════════════════════════════════════════════════════════════
    else if (account.provider === 'imap') {
      if (!features.ai()) {
        throw new ValidationError('AI sorting is not configured. Please set MISTRAL_API_KEY.')
      }

      if (!account.accessToken) {
        throw new ValidationError('IMAP account needs to be reconnected (password missing)')
      }

      log.info(`IMAP sorting started for ${account.email}`)

      const { ImapService, getFolderNameForCategory } = await import('../services/imap.mjs')
      const imap = new ImapService({
        host: account.imapHost || 'imap.porkbun.com',
        port: account.imapPort != null ? account.imapPort : 993,
        secure: account.imapSecure !== false,
        user: account.email,
        password: decryptImapSecret(account.accessToken),
      })

      try {
        await imap.connect()

        const enabledCategories = sorter.getEnabledCategories(preferences)
        // Name labels (workers): create Team subfolders for IMAP/Nextcloud
        const nameLabelMap = {}
        if (preferences.nameLabels?.length) {
          for (const nl of preferences.nameLabels) {
            if (!nl.enabled) continue
            const folderName = `Team/${nl.name}`
            try {
              await imap.ensureFolder(folderName)
              nameLabelMap[nl.id || nl.name] = folderName
              if (nl.name !== (nl.id || nl.name)) nameLabelMap[nl.name] = folderName
            } catch (err) {
              log.warn(`IMAP name label folder failed: ${nl.name}`, { error: err.message })
            }
          }
        }

        let pageToken = null
        let totalProcessed = 0
        const batchSize = 100

        do {
          const { messages, nextPageToken } = await imap.listEmails(batchSize, pageToken)
          pageToken = nextPageToken

          if (!messages?.length) break

          const emails = await imap.batchGetEmails(messages.map((m) => m.id))
          const processedEmails = []

          for (const email of emails) {
            const emailData = {
              from: email.headers?.from || '',
              subject: email.headers?.subject || '',
              snippet: email.snippet || '',
            }

            let category = null
            let companyLabel = null
            let assignedTo = null
            let skipAI = false

            if (preferences.companyLabels?.length) {
              for (const companyLabelConfig of preferences.companyLabels) {
                if (!companyLabelConfig.enabled) continue
                if (sorter.matchesCompanyLabel(emailData, companyLabelConfig)) {
                  category = companyLabelConfig.category || 'promotions'
                  companyLabel = companyLabelConfig.name
                  skipAI = true
                  break
                }
              }
            }

            if (!skipAI && preferences.autoDetectCompanies) {
              const detected = sorter.detectCompany(emailData)
              if (detected) {
                category = 'promotions'
                companyLabel = detected.label
                skipAI = true
              }
            }

            if (!skipAI) {
              const classification = await sorter.categorize(emailData, preferences)
              category = classification.category
              assignedTo = classification.assignedTo || null
              if (!enabledCategories.includes(category)) category = 'review'
            }

            processedEmails.push({ email, category, companyLabel, assignedTo })

            if (isFirstRun && emailSamples.length < 50) {
              emailSamples.push({
                from: emailData.from,
                subject: emailData.subject,
                snippet: emailData.snippet,
                category,
              })
            }
          }

          const actionMap = sorter.getCategoryAction ? (cat) => sorter.getCategoryAction(cat, preferences) : () => 'inbox'

          for (const { email, category, companyLabel, assignedTo } of processedEmails) {
            try {
              const action = actionMap(category)
              // If AI assigned to a worker, move to Team/<Name> folder; else use category folder
              const folderName = (assignedTo && nameLabelMap[assignedTo])
                ? nameLabelMap[assignedTo]
                : getFolderNameForCategory(companyLabel ? (preferences.companyLabels?.find((c) => c.name === companyLabel)?.category || 'promotions') : category)

              await imap.moveToFolder(email.id, folderName)

              if (action === 'archive_read') {
                try {
                  await imap.markAsRead(email.id)
                } catch {
                  // already moved; mark as read optional
                }
              }

              sortedCount++
              results.byCategory[category] = (results.byCategory[category] || 0) + 1
            } catch (err) {
              log.warn(`IMAP sort failed: ${email.id}`, { error: err.message })
            }
          }

          totalProcessed += emails.length
          log.info(`IMAP processed ${totalProcessed} emails so far...`)

          if (totalProcessed >= effectiveMax) break
          if (pageToken) await new Promise((r) => setTimeout(r, 200))
        } while (pageToken && processAll)

        await imap.close()
        log.success(`IMAP sorting completed: ${sortedCount} emails processed`)
      } catch (err) {
        try {
          await imap.close()
        } catch {
          // ignore
        }
        log.error('IMAP sorting failed', { error: err.message })
        throw new ValidationError(`IMAP error: ${err.message}. Check credentials or reconnect.`)
      }
    }

    // Update last sync
    await emailAccounts.updateLastSync(accountId)

    // Update email usage (for free tier tracking; admins are "business", skip counter)
    if (isFreeTier && !adminUser) {
      await emailUsage.increment(userId, sortedCount)
    }

    // Update stats
    const timeSaved = Math.round(sortedCount * 0.1) // 6 seconds per email = 0.1 minutes
    await emailStats.increment(userId, {
      total: sortedCount,
      today: sortedCount,
      week: sortedCount,
      timeSaved,
    })

    // Update categories in stats
    try {
      const currentStats = await emailStats.getByUser(userId)
      if (currentStats) {
        const existingCategories = currentStats.categoriesJson 
          ? JSON.parse(currentStats.categoriesJson) 
          : {}
        
        for (const [cat, count] of Object.entries(results.byCategory)) {
          existingCategories[cat] = (existingCategories[cat] || 0) + count
        }

        await emailStats.updateCategories(userId, existingCategories)
      }
    } catch (err) {
      log.warn('Category update failed', { error: err.message })
    }

    // Calculate inbox cleared (archived emails)
    const archivedCategories = ['newsletters', 'promotions', 'social']
    const inboxCleared = archivedCategories.reduce((sum, cat) => 
      sum + (results.byCategory[cat] || 0), 0
    )

    // Generate digest highlights (important emails)
    const highlights = []
    if (results.byCategory.vip > 0) {
      highlights.push({ type: 'vip', count: results.byCategory.vip, message: `${results.byCategory.vip} important emails need attention` })
    }
    if (results.byCategory.security > 0) {
      highlights.push({ type: 'security', count: results.byCategory.security, message: `${results.byCategory.security} security notifications` })
    }
    if (results.byCategory.invoices > 0) {
      highlights.push({ type: 'invoices', count: results.byCategory.invoices, message: `${results.byCategory.invoices} invoices received` })
    }

    // Generate suggestions (potential unsubscribes)
    const suggestions = []
    if (results.byCategory.newsletters > 5) {
      suggestions.push({ type: 'unsubscribe', message: 'Consider unsubscribing from some newsletters to reduce inbox clutter' })
    }
    if (results.byCategory.promotions > 10) {
      suggestions.push({ type: 'unsubscribe', message: 'High volume of promotional emails - review subscriptions' })
    }

    // Update daily digest
    try {
      await emailDigests.updateToday(userId, {
        stats: results.byCategory,
        highlights,
        suggestions,
        totalSorted: sortedCount,
        inboxCleared,
        timeSavedMinutes: timeSaved,
      })
    } catch (err) {
      log.warn('Digest update failed', { error: err.message })
    }

    log.success(`Sorting completed: ${sortedCount} emails for ${account.email}`)

    // Generate suggested rules for first run
    let suggestedRules = []
    if (isFirstRun && emailSamples.length > 0 && features.ai()) {
      try {
        suggestedRules = await sorter.generateSuggestedRules(userId, emailSamples)
        log.info(`Generated ${suggestedRules.length} suggested rules for first run`)
      } catch (err) {
        log.warn('Failed to generate suggested rules', { error: err.message })
      }
    }

    respond.success(res, {
      sorted: sortedCount,
      inboxCleared,
      categories: results.byCategory,
      timeSaved: {
        minutes: timeSaved,
        formatted: timeSaved > 0 ? `${timeSaved} minutes` : '< 1 minute',
      },
      isFirstRun,
      suggestedRules: suggestedRules.length > 0 ? suggestedRules : undefined,
      highlights,
      suggestions,
      provider: account.provider,
      isDemo: account.provider === 'demo',
    })
  })
)

/**
 * POST /api/email/sort-demo
 * Quick demo sorting without account (for testing)
 */
router.post('/sort-demo', asyncHandler(async (req, res) => {
  const { count = 10 } = req.body

  log.info(`Quick demo sorting: ${count} emails`)

  // Get AI sorter
  const sorter = await getAISorter()
  const results = { byCategory: {} }

  // Select random emails
  const emailCount = Math.min(count, DEMO_EMAILS.length)
  const shuffled = [...DEMO_EMAILS].sort(() => Math.random() - 0.5)
  const emailsToSort = shuffled.slice(0, emailCount)

  if (features.ai()) {
    // Real AI sorting
    const classified = await sorter.batchCategorize(emailsToSort, {})

    const sortedEmails = emailsToSort.map((email, i) => ({
      ...email,
      category: classified[i].classification.category,
      categoryName: sorter.getLabelName(classified[i].classification.category),
      confidence: classified[i].classification.confidence,
      reason: classified[i].classification.reason,
    }))

    for (const email of sortedEmails) {
      results.byCategory[email.category] = (results.byCategory[email.category] || 0) + 1
    }

    respond.success(res, {
      sorted: emailCount,
      emails: sortedEmails,
      categories: results.byCategory,
      aiEnabled: true,
    })
  } else {
    // Rule-based sorting
    const sortedEmails = emailsToSort.map(email => {
      let category = 'review'
      const from = email.from.toLowerCase()
      const subject = email.subject.toLowerCase()

      if (from.includes('boss') || from.includes('john.doe')) {
        category = 'vip'
      } else if (from.includes('billing') || subject.includes('invoice')) {
        category = 'invoices'
      } else if (from.includes('newsletter') || subject.includes('weekly')) {
        category = 'newsletters'
      } else if (from.includes('linkedin') || from.includes('twitter')) {
        category = 'social'
      } else if (from.includes('client') || subject.includes('proposal')) {
        category = 'customers'
      } else if (subject.includes('security') || subject.includes('sign-in')) {
        category = 'security'
      } else if (subject.includes('off') || from.includes('marketing')) {
        category = 'promotions'
      } else if (subject.includes('invitation') || subject.includes('meeting')) {
        category = 'calendar'
      }

      results.byCategory[category] = (results.byCategory[category] || 0) + 1

      return {
        ...email,
        category,
        categoryName: sorter.getLabelName(category),
      }
    })

    respond.success(res, {
      sorted: emailCount,
      emails: sortedEmails,
      categories: results.byCategory,
      aiEnabled: false,
      message: 'AI not configured - rule-based sorting used',
    })
  }
}))

/**
 * POST /api/email/cleanup/mailflow-labels
 * Cleanup old MailFlow labels from Gmail (legacy label names)
 */
router.post('/cleanup/mailflow-labels',
  validate({
    body: {
      accountId: [rules.required('accountId')],
    },
  }),
  asyncHandler(async (req, res) => {
    const userId = req.appwriteUser.id
    const { accountId } = req.body

    const account = await emailAccounts.get(accountId)

    if (account.userId !== userId) {
      throw new AuthorizationError('No permission for this account')
    }

    if (account.provider !== 'gmail') {
      return respond.success(res, { deleted: 0, message: 'Cleanup only available for Gmail' })
    }

    log.info(`Label cleanup started for ${account.email}`)

    const gmail = await getGmailService(account.accessToken, account.refreshToken)
    const deleted = await gmail.cleanupOldLabels()

    log.success(`${deleted} old labels deleted`)

    respond.success(res, { 
      deleted, 
      message: deleted > 0 
        ? `${deleted} old "MailFlow/..." labels were deleted` 
        : 'No old labels found' 
    })
  })
)

/**
 * GET /api/email/digest
 * Get today's sorting digest summary
 */
router.get('/digest', asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id

  const digest = await emailDigests.getByUserToday(userId)

  if (!digest) {
    // Return empty digest for new users
    return respond.success(res, {
      date: new Date().toISOString().split('T')[0],
      totalSorted: 0,
      inboxCleared: 0,
      timeSavedMinutes: 0,
      stats: {},
      highlights: [],
      suggestions: [],
      hasData: false,
    })
  }

  respond.success(res, {
    date: digest.date,
    totalSorted: digest.totalSorted,
    inboxCleared: digest.inboxCleared,
    timeSavedMinutes: digest.timeSavedMinutes,
    stats: digest.stats,
    highlights: digest.highlights,
    suggestions: digest.suggestions,
    hasData: true,
  })
}))

/**
 * GET /api/email/digest/history
 * Get digest history for the last N days
 */
router.get('/digest/history', asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id
  const days = req.query.days ?? 7

  const digests = await emailDigests.getByUserRecent(userId, parseInt(String(days), 10))

  // Calculate totals
  const totals = {
    totalSorted: digests.reduce((sum, d) => sum + (d.totalSorted || 0), 0),
    inboxCleared: digests.reduce((sum, d) => sum + (d.inboxCleared || 0), 0),
    timeSavedMinutes: digests.reduce((sum, d) => sum + (d.timeSavedMinutes || 0), 0),
  }

  respond.success(res, {
    days: parseInt(days),
    digests: digests.map(d => ({
      date: d.date,
      totalSorted: d.totalSorted,
      inboxCleared: d.inboxCleared,
      timeSavedMinutes: d.timeSavedMinutes,
      stats: d.stats,
    })),
    totals,
  })
}))

/**
 * GET /api/email/categories
 * Get all available categories
 */
router.get('/categories', asyncHandler(async (req, res) => {
  const sorter = await getAISorter()
  const categories = sorter.getCategories()

  const formattedCategories = Object.entries(categories).map(([key, cat]) => ({
    id: key,
    name: cat.name,
    description: cat.description,
    color: cat.color,
    action: cat.action,
    priority: cat.priority,
  }))

  respond.success(res, formattedCategories)
}))

/**
 * GET /api/email/:accountId/cleanup/preview
 * Dry-run: messages that would be affected by cleanup settings (no mutations).
 *
 * curl examples:
 *   curl -s -H "Authorization: Bearer YOUR_JWT" "http://localhost:3000/api/email/ACCOUNT_DOC_ID/cleanup/preview"
 */
router.get('/:accountId/cleanup/preview', asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id
  const { accountId } = req.params

  const account = await emailAccounts.get(accountId)
  if (account.userId !== userId) {
    throw new AuthorizationError('No permission for this account')
  }

  const prefs = await userPreferences.getByUser(userId)
  const cleanup = prefs?.preferences?.cleanup || userPreferences.getDefaults().cleanup
  const maxList = cleanup.safety?.maxDeletesPerRun ?? 100

  const messages = []

  if (cleanup.readItems?.enabled) {
    const readList = await listReadCleanupPreviewMessages(account, cleanup.readItems.gracePeriodDays, maxList)
    for (const m of readList) {
      if (messages.length >= maxList) break
      messages.push({ ...m, reason: 'read' })
    }
  }

  if (cleanup.promotions?.enabled && messages.length < maxList) {
    const promoList = await listPromotionCleanupPreviewMessages(
      account,
      cleanup.promotions.deleteAfterDays,
      cleanup.promotions.matchCategoriesOrLabels || [],
      maxList - messages.length
    )
    for (const m of promoList) {
      if (messages.length >= maxList) break
      messages.push({ ...m, reason: 'promotion' })
    }
  }

  respond.success(res, { messages, count: messages.length })
}))

/**
 * GET /api/email/:accountId/cleanup/status
 *
 * curl examples:
 *   curl -s -H "Authorization: Bearer YOUR_JWT" "http://localhost:3000/api/email/ACCOUNT_DOC_ID/cleanup/status"
 */
router.get('/:accountId/cleanup/status', asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id
  const { accountId } = req.params

  const account = await emailAccounts.get(accountId)
  if (account.userId !== userId) {
    throw new AuthorizationError('No permission for this account')
  }

  const prefs = await userPreferences.getByUser(userId)
  const meta = prefs?.preferences?.cleanupMeta || {}

  respond.success(res, {
    lastRun: meta.lastRun,
    lastRunCounts: meta.lastRunCounts,
    lastErrors: meta.lastErrors,
  })
}))

/**
 * POST /api/email/webhook/gmail
 * Gmail push notification webhook
 */
router.post('/webhook/gmail', asyncHandler(async (req, res) => {
  // Acknowledge immediately
  res.status(200).send()

  const { message } = req.body
  if (!message?.data) return

  try {
    const data = JSON.parse(Buffer.from(message.data, 'base64').toString())
    log.info(`Gmail Webhook: ${data.emailAddress}`, { historyId: data.historyId })
    // Queue processing in production
  } catch (err) {
    log.error('Gmail Webhook Error', { error: err.message })
  }
}))

/**
 * POST /api/email/webhook/outlook
 * Microsoft Graph webhook
 */
router.post('/webhook/outlook', asyncHandler(async (req, res) => {
  // Handle validation request
  if (req.query.validationToken) {
    return res.status(200).send(req.query.validationToken)
  }

  res.status(202).send()

  const { value } = req.body
  if (!value?.length) return

  for (const notification of value) {
    log.info(`Outlook Webhook: ${notification.changeType}`, { resource: notification.resource })
    // Queue processing in production
  }
}))

/**
 * POST /api/email/cleanup
 * Run auto-cleanup for read items and promotions
 * Can be called manually or by cron job
 */
router.post('/cleanup', asyncHandler(async (req, res) => {
  const userId = req.appwriteUser.id

  log.info('Cleanup job started', { userId })

  const results = {
    usersProcessed: 0,
    emailsProcessed: {
      readItems: 0,
      promotions: 0,
    },
    errors: [],
  }

  try {
    const prefs = await userPreferences.getByUser(userId)
    if (!prefs?.preferences?.cleanup?.enabled) {
      return respond.success(res, { ...results, message: 'Cleanup not enabled for this user' })
    }

    const accounts = await emailAccounts.getByUser(userId)
    if (!accounts || accounts.length === 0) {
      return respond.success(res, { ...results, message: 'No email accounts found' })
    }

    for (const account of accounts) {
      if (!account.isActive || !account.accessToken) continue

      try {
        const cleanup = prefs.preferences.cleanup

        if (cleanup.readItems?.enabled) {
          const readItemsCount = await processReadItemsCleanup(
            account,
            cleanup.readItems.action,
            cleanup.readItems.gracePeriodDays
          )
          results.emailsProcessed.readItems += readItemsCount
        }

        if (cleanup.promotions?.enabled) {
          const promotionsCount = await processPromotionsCleanup(
            account,
            cleanup.promotions.action,
            cleanup.promotions.deleteAfterDays,
            cleanup.promotions.matchCategoriesOrLabels || []
          )
          results.emailsProcessed.promotions += promotionsCount
        }

        results.usersProcessed = 1
      } catch (error) {
        log.error(`Cleanup failed for account ${account.email}`, { error: error.message })
        results.errors.push({ userId, accountId: account.$id, error: error.message })
      }
    }

    const lastRun = new Date().toISOString()
    await userPreferences.upsert(userId, {
      cleanupMeta: {
        lastRun,
        lastRunCounts: {
          readItems: results.emailsProcessed.readItems,
          promotions: results.emailsProcessed.promotions,
        },
        lastErrors: results.errors.map((e) => e.error),
      },
    })

    log.success('Cleanup job completed', results)
    respond.success(res, results, 'Cleanup completed')
  } catch (error) {
    log.error('Cleanup job failed', { error: error.message })
    throw error
  }
}))

/**
 * Process read items cleanup for an account
 */
async function processReadItemsCleanup(account, action, gracePeriodDays) {
  const cutoffDate = new Date()
  cutoffDate.setDate(cutoffDate.getDate() - gracePeriodDays)
  
  let processedCount = 0

  try {
    if (account.provider === 'gmail') {
      const gmail = await getGmailService(account.accessToken, account.refreshToken)
      
      // Find read emails older than grace period
      // Query: -is:unread AND before:YYYY/MM/DD
      const query = `-is:unread before:${cutoffDate.getFullYear()}/${String(cutoffDate.getMonth() + 1).padStart(2, '0')}/${String(cutoffDate.getDate()).padStart(2, '0')}`
      
      const response = await gmail.gmail.users.messages.list({
        userId: 'me',
        q: query,
        maxResults: 500, // Limit to avoid rate limits
      })

      const messages = response.data.messages || []
      
      for (const message of messages) {
        try {
          if (action === 'archive_read') {
            await gmail.archiveEmail(message.id)
            await gmail.markAsRead(message.id)
          } else if (action === 'trash') {
            await gmail.trashEmail(message.id)
          }
          processedCount++
        } catch (err) {
          log.error(`Failed to process message ${message.id}`, { error: err.message })
        }
      }
    } else if (account.provider === 'outlook') {
      const outlook = await getOutlookService(account.accessToken)
      
      // Find read emails older than grace period
      const filter = `isRead eq true and receivedDateTime lt ${cutoffDate.toISOString()}`
      const messages = await outlook._request(`/me/messages?$filter=${encodeURIComponent(filter)}&$top=500`)
      
      for (const message of messages.value || []) {
        try {
          if (action === 'archive_read') {
            await outlook.archiveEmail(message.id)
            await outlook.markAsRead(message.id)
          } else if (action === 'trash') {
            await outlook.deleteEmail(message.id) // Outlook uses deleteEmail for trash
          }
          processedCount++
        } catch (err) {
          log.error(`Failed to process message ${message.id}`, { error: err.message })
        }
      }
    }
  } catch (error) {
    log.error(`Read items cleanup failed for ${account.email}`, { error: error.message })
    throw error
  }

  return processedCount
}

/**
 * Process promotions cleanup for an account
 */
async function processPromotionsCleanup(account, action, deleteAfterDays, matchCategories) {
  const cutoffDate = new Date()
  cutoffDate.setDate(cutoffDate.getDate() - deleteAfterDays)
  
  let processedCount = 0

  try {
    if (account.provider === 'gmail') {
      const gmail = await getGmailService(account.accessToken, account.refreshToken)
      
      // Find emails with matching categories/labels older than deleteAfterDays
      // Look for emails with MailFlow labels matching the categories
      const labelQueries = matchCategories.map(cat => `label:MailFlow/${cat}`).join(' OR ')
      const query = `(${labelQueries}) before:${cutoffDate.getFullYear()}/${String(cutoffDate.getMonth() + 1).padStart(2, '0')}/${String(cutoffDate.getDate()).padStart(2, '0')}`
      
      const response = await gmail.gmail.users.messages.list({
        userId: 'me',
        q: query,
        maxResults: 500,
      })

      const messages = response.data.messages || []
      
      for (const message of messages) {
        try {
          if (action === 'archive_read') {
            await gmail.archiveEmail(message.id)
            await gmail.markAsRead(message.id)
          } else if (action === 'trash') {
            await gmail.trashEmail(message.id)
          }
          processedCount++
        } catch (err) {
          log.error(`Failed to process promotion message ${message.id}`, { error: err.message })
        }
      }
    } else if (account.provider === 'outlook') {
      const outlook = await getOutlookService(account.accessToken)
      
      // For Outlook, we'd need to check categories or use a different approach
      // This is a simplified version - in production, you might store category info
      const filter = `receivedDateTime lt ${cutoffDate.toISOString()}`
      const messages = await outlook._request(`/me/messages?$filter=${encodeURIComponent(filter)}&$top=500`)
      
      // Filter by category if available (would need to be stored during sorting)
      for (const message of messages.value || []) {
        // TODO: Check if message category matches matchCategories
        // This requires storing category info during sorting
        try {
          if (action === 'archive_read') {
            await outlook.archiveEmail(message.id)
            await outlook.markAsRead(message.id)
          } else if (action === 'trash') {
            await outlook.deleteEmail(message.id) // Outlook uses deleteEmail for trash
          }
          processedCount++
        } catch (err) {
          log.error(`Failed to process promotion message ${message.id}`, { error: err.message })
        }
      }
    }
  } catch (error) {
    log.error(`Promotions cleanup failed for ${account.email}`, { error: error.message })
    throw error
  }

  return processedCount
}

async function listReadCleanupPreviewMessages(account, gracePeriodDays, cap) {
  const out = []
  const cutoffDate = new Date()
  cutoffDate.setDate(cutoffDate.getDate() - gracePeriodDays)
  const before = `${cutoffDate.getFullYear()}/${String(cutoffDate.getMonth() + 1).padStart(2, '0')}/${String(cutoffDate.getDate()).padStart(2, '0')}`

  try {
    if (account.provider === 'gmail') {
      const gmail = await getGmailService(account.accessToken, account.refreshToken)
      const query = `-is:unread before:${before}`
      const response = await gmail.gmail.users.messages.list({
        userId: 'me',
        q: query,
        maxResults: Math.min(cap, 500),
      })
      const ids = (response.data.messages || []).map((m) => m.id).slice(0, cap)
      const emails = await gmail.batchGetEmails(ids)
      for (const email of emails) {
        out.push({
          id: email.id,
          subject: email.headers?.subject || '',
          from: email.headers?.from || '',
          date: email.headers?.date || email.internalDate || '',
        })
      }
    } else if (account.provider === 'outlook') {
      const outlook = await getOutlookService(account.accessToken)
      const filter = `isRead eq true and receivedDateTime lt ${cutoffDate.toISOString()}`
      const data = await outlook._request(`/me/messages?$filter=${encodeURIComponent(filter)}&$top=${Math.min(cap, 500)}`)
      for (const message of data.value || []) {
        out.push({
          id: message.id,
          subject: message.subject || '',
          from: message.from?.emailAddress?.address || '',
          date: message.receivedDateTime || '',
        })
        if (out.length >= cap) break
      }
    }
  } catch (err) {
    log.warn('listReadCleanupPreviewMessages failed', { error: err.message })
  }
  return out.slice(0, cap)
}

async function listPromotionCleanupPreviewMessages(account, deleteAfterDays, matchCategories, cap) {
  const out = []
  const cutoffDate = new Date()
  cutoffDate.setDate(cutoffDate.getDate() - deleteAfterDays)
  const before = `${cutoffDate.getFullYear()}/${String(cutoffDate.getMonth() + 1).padStart(2, '0')}/${String(cutoffDate.getDate()).padStart(2, '0')}`

  try {
    if (account.provider === 'gmail' && matchCategories.length > 0) {
      const gmail = await getGmailService(account.accessToken, account.refreshToken)
      const labelQueries = matchCategories.map((cat) => `label:MailFlow/${cat}`).join(' OR ')
      const query = `(${labelQueries}) before:${before}`
      const response = await gmail.gmail.users.messages.list({
        userId: 'me',
        q: query,
        maxResults: Math.min(cap, 500),
      })
      const ids = (response.data.messages || []).map((m) => m.id).slice(0, cap)
      const emails = await gmail.batchGetEmails(ids)
      for (const email of emails) {
        out.push({
          id: email.id,
          subject: email.headers?.subject || '',
          from: email.headers?.from || '',
          date: email.headers?.date || email.internalDate || '',
        })
      }
    } else if (account.provider === 'outlook' && cap > 0) {
      const outlook = await getOutlookService(account.accessToken)
      const filter = `receivedDateTime lt ${cutoffDate.toISOString()}`
      const data = await outlook._request(`/me/messages?$filter=${encodeURIComponent(filter)}&$top=${Math.min(cap, 500)}`)
      for (const message of data.value || []) {
        const cats = message.categories || []
        const match = matchCategories.length === 0 || cats.some((c) => matchCategories.includes(c))
        if (!match) continue
        out.push({
          id: message.id,
          subject: message.subject || '',
          from: message.from?.emailAddress?.address || '',
          date: message.receivedDateTime || '',
        })
        if (out.length >= cap) break
      }
    }
  } catch (err) {
    log.warn('listPromotionCleanupPreviewMessages failed', { error: err.message })
  }
  return out.slice(0, cap)
}

export default router
