import Imap from 'imap';
import { simpleParser } from 'mailparser';
import { requireAuth } from '~/server/utils/auth';
import { getCredentialsFromSession, decryptCredentials } from '~/server/utils/encryption';
import { listFiles, getFileStats, getMinioClient, uploadFile } from '~/server/utils/minio';
import { getIMAPPool } from '~/server/utils/imap-pool';

interface EmailMessage {
  id: string;
  from: string;
  to: string | string[];
  subject: string;
  body: string;
  html?: string;
  timestamp: string;
  direction: 'sent' | 'received';
  threadId?: string;
  attachments?: any[];
}

export default defineEventHandler(async (event) => {
  // Check authentication (x-tag header OR Keycloak session)
  await requireAuth(event);

  try {
    const body = await readBody(event);
    // Limit IMAP emails but load all cached emails
    const { clientEmail, interestId, sessionId, limit = 50 } = body;

    if (!clientEmail || !sessionId) {
      throw createError({ 
        statusCode: 400, 
        statusMessage: "Client email and sessionId are required" 
      });
    }

    // Get encrypted credentials from session
    const encryptedCredentials = getCredentialsFromSession(sessionId);
    if (!encryptedCredentials) {
      // Return empty results instead of throwing error
      return { 
        success: true, 
        emails: [],
        threads: []
      };
    }

    // Decrypt credentials
    let userEmail: string;
    let password: string;
    
    try {
      const decrypted = decryptCredentials(encryptedCredentials);
      userEmail = decrypted.email;
      password = decrypted.password;
    } catch (decryptError) {
      console.error('Failed to decrypt credentials - session may be invalid after restart:', decryptError);
      // Return empty results for invalid session
      return { 
        success: true, 
        emails: [],
        threads: []
      };
    }

    // First, get emails from MinIO cache if available
    const cachedEmails: EmailMessage[] = [];
    if (interestId) {
      try {
        // List files from the client-emails bucket
        const client = getMinioClient();
        const stream = client.listObjectsV2('client-emails', `interest-${interestId}/`, true);
        const files: any[] = [];
        
        await new Promise((resolve, reject) => {
          stream.on('data', (obj) => {
            if (obj && obj.name) {
              files.push({ name: obj.name, size: obj.size || 0 });
            }
          });
          stream.on('error', reject);
          stream.on('end', resolve);
        });
        
        console.log('Found cached email files:', files.length);
        
        // Load ALL cached emails (no limit) - just filter JSON files
        const jsonFiles = files.filter(f => f.name.endsWith('.json'));
        
        for (const file of jsonFiles) {
          try {
            // Read file directly on server using MinIO client (works with private buckets)
            const client = getMinioClient();
            // Use the client-emails bucket directly
            const bucketName = 'client-emails';
            
            // The file.name is already the correct path within the bucket
            const fileName = file.name;
            
            // Get object as stream
            const stream = await client.getObject(bucketName, fileName);
            
            // Convert stream to string
            let data = '';
            stream.on('data', (chunk) => {
              data += chunk;
            });
            
            await new Promise((resolve, reject) => {
              stream.on('end', () => resolve(data));
              stream.on('error', reject);
            });
            
            const emailData = JSON.parse(data);
            cachedEmails.push(emailData);
          } catch (err) {
            console.error('Failed to read cached email:', file.name, err);
          }
        }
      } catch (err) {
        console.error('Failed to list cached emails:', err);
      }
    }

    // Configure IMAP
    const imapConfig = {
      user: userEmail,
      password: password,
      host: process.env.NUXT_EMAIL_IMAP_HOST || 'mail.portnimara.com',
      port: parseInt(process.env.NUXT_EMAIL_IMAP_PORT || '993'),
      tls: true,
      tlsOptions: { 
        rejectUnauthorized: false
      },
      connTimeout: 10000, // 10 seconds connection timeout
      authTimeout: 5000   // 5 seconds auth timeout
    };

    // Fetch emails from IMAP with timeout (increased to 30 seconds)
    let imapEmails: EmailMessage[] = [];
    const timeoutPromise = new Promise<EmailMessage[]>((_, reject) => 
      setTimeout(() => reject(new Error('IMAP connection timeout')), 30000)
    );

    try {
      imapEmails = await Promise.race([
        fetchImapEmailsWithPool(sessionId, userEmail, clientEmail, limit, interestId),
        timeoutPromise
      ]);
    } catch (imapError) {
      console.error('IMAP fetch failed:', imapError);
      // Continue with cached emails only
    }

    // Combine cached and IMAP emails, remove duplicates
    const allEmails = [...cachedEmails, ...imapEmails];
    const uniqueEmails = Array.from(
      new Map(allEmails.map(email => [email.id, email])).values()
    );

    // Sort by timestamp (newest first)
    uniqueEmails.sort((a, b) => 
      new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
    );

    // Group into threads
    const threads = groupIntoThreads(uniqueEmails);

    return { 
      success: true, 
      emails: uniqueEmails,
      threads: threads
    };
  } catch (error) {
    console.error('Failed to fetch email thread:', error);
    if (error instanceof Error) {
      throw createError({ 
        statusCode: 500, 
        statusMessage: `Failed to fetch emails: ${error.message}` 
      });
    } else {
      throw createError({
        statusCode: 500,
        statusMessage: "An unexpected error occurred",
      });
    }
  }
});

// Function for IMAP fetching using connection pool
async function fetchImapEmailsWithPool(
  sessionId: string,
  userEmail: string, 
  clientEmail: string, 
  limit: number,
  interestId?: string
): Promise<EmailMessage[]> {
  const pool = getIMAPPool();
  const imap = await pool.getConnection(sessionId);
  
  return new Promise((resolve, reject) => {
    const allEmails: EmailMessage[] = [];
    
    // Search in both INBOX and Sent folders
    const foldersToSearch = ['INBOX', 'Sent', 'Sent Items', 'Sent Mail'];
    let currentFolderIndex = 0;

    const searchNextFolder = () => {
      if (currentFolderIndex >= foldersToSearch.length) {
        resolve(allEmails);
        return;
      }

      const folderName = foldersToSearch[currentFolderIndex];
      currentFolderIndex++;

      imap.openBox(folderName, true, (err: any, box: any) => {
        if (err) {
          console.log(`[IMAPPool] Folder ${folderName} not found, trying next...`);
          searchNextFolder();
          return;
        }

        console.log(`[IMAPPool] Searching in folder: ${folderName}`);

        if (!clientEmail || clientEmail.trim() === '') {
          console.log('[IMAPPool] No client email provided, skipping search');
          searchNextFolder();
          return;
        }

        imap.search(['ALL'], (err: any, results: number[]) => {
          if (err) {
            console.error(`[IMAPPool] Search error in ${folderName}:`, err);
            searchNextFolder();
            return;
          }

          if (!results || results.length === 0) {
            console.log(`[IMAPPool] No emails found in ${folderName}`);
            searchNextFolder();
            return;
          }

          console.log(`[IMAPPool] Found ${results.length} emails in ${folderName}`);
          const messagesToFetch = results.slice(-limit);
          let messagesProcessed = 0;
          
          const fetch = imap.fetch(messagesToFetch, {
            bodies: '',
            struct: true,
            envelope: true
          });

          fetch.on('message', (msg: any, seqno: number) => {
            msg.on('body', (stream: any, info: any) => {
              simpleParser(stream as any, async (err: any, parsed: any) => {
                if (err) {
                  console.error('[IMAPPool] Parse error:', err);
                  messagesProcessed++;
                  if (messagesProcessed === messagesToFetch.length) {
                    searchNextFolder();
                  }
                  return;
                }

                // Check if this email involves the client
                const fromEmail = parsed.from?.text || '';
                const toEmails = Array.isArray(parsed.to) 
                  ? parsed.to.map((addr: any) => addr.text).join(', ')
                  : parsed.to?.text || '';
                const ccEmails = Array.isArray(parsed.cc)
                  ? parsed.cc.map((addr: any) => addr.text).join(', ')
                  : parsed.cc?.text || '';
                  
                // Filter to only include emails to/from the client
                const involvesClient = 
                  fromEmail.toLowerCase().includes(clientEmail.toLowerCase()) ||
                  toEmails.toLowerCase().includes(clientEmail.toLowerCase()) ||
                  ccEmails.toLowerCase().includes(clientEmail.toLowerCase());
                  
                if (!involvesClient) {
                  messagesProcessed++;
                  if (messagesProcessed === messagesToFetch.length) {
                    searchNextFolder();
                  }
                  return;
                }

                // Process attachments
                const attachments: any[] = [];
                if (parsed.attachments && parsed.attachments.length > 0) {
                  for (const attachment of parsed.attachments) {
                    try {
                      // Save attachment to MinIO
                      const attachmentId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
                      const fileName = attachment.filename || `attachment-${attachmentId}`;
                      const sanitizedFileName = fileName.replace(/[^a-zA-Z0-9.-]/g, '_');
                      const objectName = `interest-${interestId}/attachments/${attachmentId}-${sanitizedFileName}`;
                      
                      const client = getMinioClient();
                      await client.putObject('client-emails', objectName, attachment.content, attachment.content?.length || 0, {
                        'Content-Type': attachment.contentType || 'application/octet-stream',
                        'Content-Disposition': `attachment; filename="${sanitizedFileName}"`
                      });
                      
                      attachments.push({
                        id: attachmentId,
                        filename: sanitizedFileName,
                        originalName: attachment.filename,
                        contentType: attachment.contentType,
                        size: attachment.content?.length || attachment.size || 0,
                        path: objectName,
                        bucket: 'client-emails'
                      });
                      
                      console.log(`[IMAPPool] Saved attachment: ${sanitizedFileName} (${attachment.content?.length || 0} bytes)`);
                    } catch (attachmentError) {
                      console.error('[IMAPPool] Failed to save attachment:', attachment.filename, attachmentError);
                      // Still include attachment info even if save failed
                      attachments.push({
                        filename: attachment.filename || 'Unknown',
                        originalName: attachment.filename,
                        contentType: attachment.contentType,
                        size: attachment.content?.length || attachment.size || 0,
                        error: 'Failed to save'
                      });
                    }
                  }
                }

                const email: EmailMessage = {
                  id: parsed.messageId || `${Date.now()}-${seqno}`,
                  from: fromEmail,
                  to: toEmails,
                  subject: parsed.subject || '',
                  body: parsed.text || '',
                  html: parsed.html || undefined,
                  timestamp: parsed.date?.toISOString() || new Date().toISOString(),
                  direction: fromEmail.toLowerCase().includes(userEmail.toLowerCase()) ? 'sent' : 'received',
                  attachments: attachments.length > 0 ? attachments : undefined
                };

                if (parsed.headers.has('in-reply-to')) {
                  email.threadId = parsed.headers.get('in-reply-to') as string;
                }

                allEmails.push(email);
                
                // Cache this email if we have an interestId
                if (interestId && involvesClient) {
                  try {
                    const emailData = {
                      ...email,
                      interestId: interestId
                    };
                    
                    const objectName = `interest-${interestId}/${Date.now()}-${email.direction}.json`;
                    const buffer = Buffer.from(JSON.stringify(emailData, null, 2));
                    
                    // Upload to the client-emails bucket
                    const client = getMinioClient();
                    client.putObject('client-emails', objectName, buffer, buffer.length, {
                      'Content-Type': 'application/json',
                    }).catch(err => {
                      console.error('[IMAPPool] Failed to cache email:', err);
                    });
                  } catch (cacheError) {
                    console.error('[IMAPPool] Failed to cache email:', cacheError);
                  }
                }
                
                messagesProcessed++;
                
                if (messagesProcessed === messagesToFetch.length) {
                  searchNextFolder();
                }
              });
            });
          });

          fetch.once('error', (err: any) => {
            console.error('[IMAPPool] Fetch error:', err);
            searchNextFolder();
          });

          fetch.once('end', () => {
            if (messagesProcessed === 0) {
              searchNextFolder();
            }
          });
        });
      });
    };

    searchNextFolder();
  });
}

// Separate function for IMAP fetching with proper cleanup (legacy)
async function fetchImapEmails(
  imapConfig: any, 
  userEmail: string, 
  clientEmail: string, 
  limit: number,
  interestId?: string
): Promise<EmailMessage[]> {
  return new Promise((resolve, reject) => {
    const emails: EmailMessage[] = [];
    const imap = new Imap(imapConfig);
    let isResolved = false;

    const cleanup = () => {
      if (!isResolved) {
        isResolved = true;
        try {
          imap.end();
        } catch (e) {
          console.error('Error closing IMAP connection:', e);
        }
      }
    };

    imap.once('ready', () => {
      // Search in both INBOX and Sent folders
      const foldersToSearch = ['INBOX', 'Sent', 'Sent Items', 'Sent Mail'];
      let currentFolderIndex = 0;
      const allEmails: EmailMessage[] = [];

      const searchNextFolder = () => {
        if (currentFolderIndex >= foldersToSearch.length) {
          cleanup();
          resolve(allEmails);
          return;
        }

        const folderName = foldersToSearch[currentFolderIndex];
        currentFolderIndex++;

        imap.openBox(folderName, true, (err, box) => {
          if (err) {
            console.log(`Folder ${folderName} not found, trying next...`);
            searchNextFolder();
            return;
          }

          console.log(`Searching in folder: ${folderName}`);

          // Search for emails both sent and received with this client
          // Note: BCC search might not be supported by all IMAP servers
          if (!clientEmail || clientEmail.trim() === '') {
            console.log('No client email provided, skipping search');
            searchNextFolder();
            return;
          }

          // For specific client searches, don't use date filter to ensure we get complete threads
          // Just get ALL emails and filter manually
          imap.search(['ALL'], (err, results) => {
            if (err) {
              console.error(`Search error in ${folderName}:`, err);
              searchNextFolder();
              return;
            }

            if (!results || results.length === 0) {
              console.log(`No emails found in ${folderName}`);
              searchNextFolder();
              return;
            }

            console.log(`Found ${results.length} emails in ${folderName}`);
            const messagesToFetch = results.slice(-limit);
            let messagesProcessed = 0;
            
            const fetch = imap.fetch(messagesToFetch, {
              bodies: '',
              struct: true,
              envelope: true
            });

            fetch.on('message', (msg, seqno) => {
              msg.on('body', (stream, info) => {
                simpleParser(stream as any, async (err: any, parsed: any) => {
                  if (err) {
                    console.error('Parse error:', err);
                    messagesProcessed++;
                    if (messagesProcessed === messagesToFetch.length) {
                      searchNextFolder();
                    }
                    return;
                  }

                  // Check if this email involves the client
                  const fromEmail = parsed.from?.text || '';
                  const toEmails = Array.isArray(parsed.to) 
                    ? parsed.to.map((addr: any) => addr.text).join(', ')
                    : parsed.to?.text || '';
                  const ccEmails = Array.isArray(parsed.cc)
                    ? parsed.cc.map((addr: any) => addr.text).join(', ')
                    : parsed.cc?.text || '';
                    
                  // Filter to only include emails to/from the client
                  const involvesClient = 
                    fromEmail.toLowerCase().includes(clientEmail.toLowerCase()) ||
                    toEmails.toLowerCase().includes(clientEmail.toLowerCase()) ||
                    ccEmails.toLowerCase().includes(clientEmail.toLowerCase());
                    
                  if (!involvesClient) {
                    messagesProcessed++;
                    if (messagesProcessed === messagesToFetch.length) {
                      searchNextFolder();
                    }
                    return;
                  }

                  // Process attachments
                  const attachments: any[] = [];
                  if (parsed.attachments && parsed.attachments.length > 0) {
                    for (const attachment of parsed.attachments) {
                      try {
                        // Save attachment to MinIO
                        const attachmentId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
                        const fileName = attachment.filename || `attachment-${attachmentId}`;
                        const sanitizedFileName = fileName.replace(/[^a-zA-Z0-9.-]/g, '_');
                        const objectName = `interest-${interestId}/attachments/${attachmentId}-${sanitizedFileName}`;
                        
                        const client = getMinioClient();
                        await client.putObject('client-emails', objectName, attachment.content, attachment.content?.length || 0, {
                          'Content-Type': attachment.contentType || 'application/octet-stream',
                          'Content-Disposition': `attachment; filename="${sanitizedFileName}"`
                        });
                        
                        attachments.push({
                          id: attachmentId,
                          filename: sanitizedFileName,
                          originalName: attachment.filename,
                          contentType: attachment.contentType,
                          size: attachment.content?.length || attachment.size || 0,
                          path: objectName,
                          bucket: 'client-emails'
                        });
                        
                        console.log(`Saved attachment: ${sanitizedFileName} (${attachment.content?.length || 0} bytes)`);
                      } catch (attachmentError) {
                        console.error('Failed to save attachment:', attachment.filename, attachmentError);
                        // Still include attachment info even if save failed
                        attachments.push({
                          filename: attachment.filename || 'Unknown',
                          originalName: attachment.filename,
                          contentType: attachment.contentType,
                          size: attachment.content?.length || attachment.size || 0,
                          error: 'Failed to save'
                        });
                      }
                    }
                  }

                  const email: EmailMessage = {
                    id: parsed.messageId || `${Date.now()}-${seqno}`,
                    from: fromEmail,
                    to: toEmails,
                    subject: parsed.subject || '',
                    body: parsed.text || '',
                    html: parsed.html || undefined,
                    timestamp: parsed.date?.toISOString() || new Date().toISOString(),
                    direction: fromEmail.toLowerCase().includes(userEmail.toLowerCase()) ? 'sent' : 'received',
                    attachments: attachments.length > 0 ? attachments : undefined
                  };

                  if (parsed.headers.has('in-reply-to')) {
                    email.threadId = parsed.headers.get('in-reply-to') as string;
                  }

                  allEmails.push(email);
                  
                  // Cache this email if we have an interestId
                  if (interestId && involvesClient) {
                    try {
                      const emailData = {
                        ...email,
                        interestId: interestId
                      };
                      
                      const objectName = `interest-${interestId}/${Date.now()}-${email.direction}.json`;
                      const buffer = Buffer.from(JSON.stringify(emailData, null, 2));
                      
                      // Upload to the client-emails bucket
                      const client = getMinioClient();
                      client.putObject('client-emails', objectName, buffer, buffer.length, {
                        'Content-Type': 'application/json',
                      }).catch(err => {
                        console.error('Failed to cache email:', err);
                      });
                    } catch (cacheError) {
                      console.error('Failed to cache email:', cacheError);
                    }
                  }
                  
                  messagesProcessed++;
                  
                  if (messagesProcessed === messagesToFetch.length) {
                    searchNextFolder();
                  }
                });
              });
            });

            fetch.once('error', (err) => {
              console.error('Fetch error:', err);
              searchNextFolder();
            });

            fetch.once('end', () => {
              if (messagesProcessed === 0) {
                searchNextFolder();
              }
            });
          });
        });
      };

      searchNextFolder();
    });

    imap.once('error', (err: any) => {
      cleanup();
      reject(err);
    });

    imap.once('end', () => {
      if (!isResolved) {
        isResolved = true;
        resolve(emails);
      }
    });

    imap.connect();
  });
}

// Group emails into threads based on subject and references
function groupIntoThreads(emails: EmailMessage[]): any[] {
  const threads = new Map<string, EmailMessage[]>();
  const emailById = new Map<string, EmailMessage>();
  
  // First pass: index all emails by ID
  emails.forEach(email => {
    emailById.set(email.id, email);
  });
  
  // Second pass: group emails into threads
  emails.forEach(email => {
    // Normalize subject by removing Re:, Fwd:, etc. and extra whitespace
    const normalizedSubject = email.subject
      .replace(/^(Re:|Fwd:|Fw:|RE:|FW:|FWD:)\s*/gi, '')
      .replace(/\s+/g, ' ')
      .trim()
      .toLowerCase();
    
    // Check if this email belongs to an existing thread
    let threadFound = false;
    
    // First, check if it has a threadId (in-reply-to header)
    if (email.threadId) {
      // Look for the parent email
      const parentEmail = emailById.get(email.threadId);
      if (parentEmail) {
        // Find which thread the parent belongs to
        for (const [threadId, threadEmails] of threads.entries()) {
          if (threadEmails.some(e => e.id === parentEmail.id)) {
            threadEmails.push(email);
            threadFound = true;
            break;
          }
        }
      }
    }
    
    // If not found by threadId, try to match by subject
    if (!threadFound) {
      for (const [threadId, threadEmails] of threads.entries()) {
        const threadSubject = threadEmails[0].subject
          .replace(/^(Re:|Fwd:|Fw:|RE:|FW:|FWD:)\s*/gi, '')
          .replace(/\s+/g, ' ')
          .trim()
          .toLowerCase();
        
        // Check if subjects match (case-insensitive)
        if (threadSubject === normalizedSubject) {
          threadEmails.push(email);
          threadFound = true;
          break;
        }
      }
    }
    
    // If still not found, create a new thread
    if (!threadFound) {
      threads.set(email.id, [email]);
    }
  });
  
  // Convert to array format and sort emails within each thread
  return Array.from(threads.entries())
    .map(([threadId, threadEmails]) => {
      // Sort emails within thread by timestamp (oldest first for chronological order)
      threadEmails.sort((a, b) => 
        new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
      );
      
      return {
        id: threadId,
        subject: threadEmails[0].subject,
        emailCount: threadEmails.length,
        latestTimestamp: threadEmails[threadEmails.length - 1].timestamp, // Latest email
        emails: threadEmails
      };
    })
    // Sort threads by latest activity (newest first)
    .sort((a, b) => new Date(b.latestTimestamp).getTime() - new Date(a.latestTimestamp).getTime());
}
