import { ImapFlow } from 'imapflow';
import { simpleParser } from 'mailparser';
import { encryptionService } from './encryptionService';
import { UserMailAccount } from './userMailAccountService';
import { imapConnectionPool } from './imapConnectionPool';
import { mailCache } from './mailCache';

export interface ReceivedMail {
  id: string;
  messageId: string;
  from: string;
  to: string;
  subject: string;
  date: Date;
  preview: string;
  isRead: boolean;
  hasAttachments: boolean;
}

export interface MailDetail extends ReceivedMail {
  htmlBody: string;
  textBody: string;
  cc?: string;
  bcc?: string;
  attachments: Array<{
    filename: string;
    contentType: string;
    size: number;
  }>;
}

class UserMailImapService {
  async fetchMailList(account: UserMailAccount, limit: number = 50): Promise<ReceivedMail[]> {
    const cacheKey = `mailList:${account.id}:INBOX:${limit}`;
    const cached = mailCache.get<ReceivedMail[]>(cacheKey);
    if (cached) return cached;

    const mails = await imapConnectionPool.execute(account, async (client) => {
      const mailbox = await client.getMailboxLock('INBOX');
      try {
        const status = await client.status('INBOX', { messages: true });
        const total = status.messages || 0;
        if (total === 0) return [];

        const start = Math.max(1, total - limit + 1);
        const range = `${start}:${total}`;
        const result: ReceivedMail[] = [];

        for await (const msg of client.fetch(range, {
          uid: true,
          flags: true,
          envelope: true,
          bodyStructure: true,
        })) {
          const hasAttachments = msg.bodyStructure
            ? JSON.stringify(msg.bodyStructure).toLowerCase().includes('"attachment"')
            : false;

          result.push({
            id: `${account.id}-imap-${msg.seq}`,
            messageId: msg.envelope?.messageId || `${msg.seq}`,
            from: msg.envelope?.from?.[0]
              ? `${msg.envelope.from[0].name || ''} <${msg.envelope.from[0].address}>`.trim()
              : 'Unknown',
            to: msg.envelope?.to?.[0]?.address || '',
            subject: msg.envelope?.subject || '(제목 없음)',
            date: msg.envelope?.date ? new Date(msg.envelope.date) : new Date(),
            preview: '',
            isRead: msg.flags?.has('\\Seen') || false,
            hasAttachments,
          });
        }

        result.sort((a, b) => b.date.getTime() - a.date.getTime());
        return result;
      } finally {
        mailbox.release();
      }
    });

    mailCache.set(cacheKey, mails, 60_000);
    return mails;
  }

  async fetchMailListStream(
    account: UserMailAccount,
    limit: number = 20,
    beforeSeqno: number | null = null,
    onMail: (mail: ReceivedMail) => void,
    onDone: () => void,
    onError: (err: Error) => void
  ): Promise<void> {
    try {
      await imapConnectionPool.execute(account, async (client) => {
        const mailbox = await client.getMailboxLock('INBOX');
        try {
          const status = await client.status('INBOX', { messages: true });
          const total = status.messages || 0;
          if (total === 0) { onDone(); return; }

          let start: number, end: number;
          if (beforeSeqno !== null) {
            end = beforeSeqno - 1;
            start = Math.max(1, beforeSeqno - limit);
          } else {
            start = Math.max(1, total - limit + 1);
            end = total;
          }

          if (end < 1 || start > end) { onDone(); return; }

          for await (const msg of client.fetch(`${start}:${end}`, {
            uid: true,
            flags: true,
            envelope: true,
            bodyStructure: true,
          })) {
            const hasAttachments = msg.bodyStructure
              ? JSON.stringify(msg.bodyStructure).toLowerCase().includes('"attachment"')
              : false;

            onMail({
              id: `${account.id}-imap-${msg.seq}`,
              messageId: msg.envelope?.messageId || `${msg.seq}`,
              from: msg.envelope?.from?.[0]
                ? `${msg.envelope.from[0].name || ''} <${msg.envelope.from[0].address}>`.trim()
                : 'Unknown',
              to: msg.envelope?.to?.[0]?.address || '',
              subject: msg.envelope?.subject || '(제목 없음)',
              date: msg.envelope?.date ? new Date(msg.envelope.date) : new Date(),
              preview: '',
              isRead: msg.flags?.has('\\Seen') || false,
              hasAttachments,
            });
          }
          onDone();
        } finally {
          mailbox.release();
        }
      });
    } catch (err) {
      onError(err instanceof Error ? err : new Error(String(err)));
    }
  }

  async getMailDetail(account: UserMailAccount, seqno: number): Promise<MailDetail | null> {
    const cacheKey = `mailDetail:${account.id}:${seqno}`;
    const cached = mailCache.get<MailDetail>(cacheKey);
    if (cached) return cached;

    const detail = await imapConnectionPool.execute(account, async (client) => {
      const mailbox = await client.getMailboxLock('INBOX');
      try {
        const msg = await client.fetchOne(`${seqno}`, {
          uid: true,
          flags: true,
          envelope: true,
          bodyStructure: true,
          source: true,
        });
        if (!msg) return null;

        const parsed = await simpleParser(msg.source as Buffer);

        const fromAddress = Array.isArray(parsed.from) ? parsed.from[0] : parsed.from;
        const toAddress = Array.isArray(parsed.to) ? parsed.to[0] : parsed.to;
        const ccAddress = Array.isArray(parsed.cc) ? parsed.cc[0] : parsed.cc;

        return {
          id: `${account.id}-imap-${seqno}`,
          messageId: parsed.messageId || `${seqno}`,
          from: fromAddress?.text || 'Unknown',
          to: toAddress?.text || '',
          cc: ccAddress?.text,
          subject: parsed.subject || '(제목 없음)',
          date: parsed.date || new Date(),
          htmlBody: parsed.html || '',
          textBody: parsed.text || '',
          preview: '',
          isRead: msg.flags?.has('\\Seen') || false,
          hasAttachments: (parsed.attachments?.length || 0) > 0,
          attachments: (parsed.attachments || []).map((att: any) => ({
            filename: att.filename || 'unnamed',
            contentType: att.contentType || 'application/octet-stream',
            size: att.size || 0,
          })),
        } as MailDetail;
      } finally {
        mailbox.release();
      }
    });

    if (detail) mailCache.set(cacheKey, detail, 300_000);
    return detail;
  }

  async markAsRead(account: UserMailAccount, seqno: number): Promise<{ success: boolean; message: string }> {
    try {
      await imapConnectionPool.execute(account, async (client) => {
        const mailbox = await client.getMailboxLock('INBOX');
        try {
          await client.messageFlagsAdd(`${seqno}`, ['\\Seen']);
        } finally {
          mailbox.release();
        }
      });
      mailCache.invalidateByPrefix(`mailList:${account.id}:`);
      return { success: true, message: '읽음 처리 완료' };
    } catch (err) {
      return { success: false, message: err instanceof Error ? err.message : '오류' };
    }
  }

  async deleteMail(account: UserMailAccount, seqno: number): Promise<{ success: boolean; message: string }> {
    try {
      await imapConnectionPool.execute(account, async (client) => {
        // \Trash 특수 폴더 탐색 (Gmail: [Gmail]/휴지통 등)
        const folders = await client.list();
        const trashFolder = folders.find(f => f.specialUse === '\\Trash');

        const mailbox = await client.getMailboxLock('INBOX');
        try {
          if (trashFolder) {
            await client.messageMove(`${seqno}`, trashFolder.path);
          } else {
            await client.messageDelete(`${seqno}`);
          }
        } finally {
          mailbox.release();
        }
      });
      mailCache.invalidateByPrefix(`mailList:${account.id}:`);
      mailCache.invalidateByPrefix(`mailDetail:${account.id}:${seqno}`);
      return { success: true, message: '휴지통으로 이동 완료' };
    } catch (err) {
      return { success: false, message: err instanceof Error ? err.message : '오류' };
    }
  }

  async listFolders(account: UserMailAccount): Promise<Array<{ path: string; name: string; unseen: number; }>> {
    return imapConnectionPool.execute(account, async (client) => {
      const folders = await client.list({ statusQuery: { unseen: true } });
      return folders
        .filter(f => f.listed)
        .map(f => ({
          path: f.path,
          name: f.name,
          unseen: (f as any).status?.unseen ?? 0,
        }));
    });
  }

  async streamMailsByFolder(
    account: UserMailAccount,
    folder: string,
    limit: number = 20,
    beforeSeqno: number | null = null,
    onMail: (mail: ReceivedMail) => void,
    onDone: () => void,
    onError: (err: Error) => void
  ): Promise<void> {
    try {
      await imapConnectionPool.execute(account, async (client) => {
        const mailbox = await client.getMailboxLock(folder);
        try {
          const status = await client.status(folder, { messages: true });
          const total = status.messages || 0;
          if (total === 0) { onDone(); return; }

          let start: number, end: number;
          if (beforeSeqno !== null) {
            end = beforeSeqno - 1;
            start = Math.max(1, beforeSeqno - limit);
          } else {
            start = Math.max(1, total - limit + 1);
            end = total;
          }
          if (end < 1 || start > end) { onDone(); return; }

          for await (const msg of client.fetch(`${start}:${end}`, {
            uid: true, flags: true, envelope: true, bodyStructure: true,
          })) {
            const hasAttachments = msg.bodyStructure
              ? JSON.stringify(msg.bodyStructure).toLowerCase().includes('"attachment"')
              : false;
            onMail({
              id: `${account.id}-imap-${msg.seq}`,
              messageId: msg.envelope?.messageId || `${msg.seq}`,
              from: msg.envelope?.from?.[0]
                ? `${msg.envelope.from[0].name || ''} <${msg.envelope.from[0].address}>`.trim()
                : 'Unknown',
              to: msg.envelope?.to?.[0]?.address || '',
              subject: msg.envelope?.subject || '(제목 없음)',
              date: msg.envelope?.date ? new Date(msg.envelope.date) : new Date(),
              preview: '',
              isRead: msg.flags?.has('\\Seen') || false,
              hasAttachments,
            });
          }
          onDone();
        } finally {
          mailbox.release();
        }
      });
    } catch (err) {
      onError(err instanceof Error ? err : new Error(String(err)));
    }
  }

  async moveMail(account: UserMailAccount, seqno: number, targetFolder: string): Promise<{ success: boolean; message: string }> {
    try {
      await imapConnectionPool.execute(account, async (client) => {
        const mailbox = await client.getMailboxLock('INBOX');
        try {
          await client.messageMove(`${seqno}`, targetFolder);
        } finally {
          mailbox.release();
        }
      });
      mailCache.invalidateByPrefix(`mailList:${account.id}:`);
      return { success: true, message: '이동 완료' };
    } catch (err) {
      return { success: false, message: err instanceof Error ? err.message : '오류' };
    }
  }

  async downloadAttachment(
    account: UserMailAccount,
    seqno: number,
    partId: string,
    res: import('express').Response,
    folder: string = 'INBOX',
    filenameHint?: string
  ): Promise<void> {
    await imapConnectionPool.execute(account, async (client) => {
      const mailbox = await client.getMailboxLock(folder);
      try {
        const { meta, content } = await client.download(`${seqno}`, partId);
        const rawFilename = filenameHint || (meta as any).filename || 'attachment';
        const encodedFilename = encodeURIComponent(rawFilename);
        res.setHeader('Content-Disposition', `attachment; filename="${rawFilename}"; filename*=UTF-8''${encodedFilename}`);
        res.setHeader('Content-Type', (meta as any).contentType || 'application/octet-stream');
        if ((meta as any).size) res.setHeader('Content-Length', String((meta as any).size));
        await require('stream/promises').pipeline(content, res);
      } finally {
        mailbox.release();
      }
    });
  }

  async getAttachmentList(account: UserMailAccount, seqno: number, folder: string = 'INBOX'): Promise<Array<{ partId: string; filename: string; contentType: string; size: number }>> {
    return imapConnectionPool.execute(account, async (client) => {
      const mailbox = await client.getMailboxLock(folder);
      try {
        const msg = await client.fetchOne(`${seqno}`, { bodyStructure: true });
        if (!msg || !msg.bodyStructure) return [];
        const result: Array<{ partId: string; filename: string; contentType: string; size: number }> = [];
        function walk(node: any, part: string) {
          const filename = node.parameters?.name || node.dispositionParameters?.filename;
          if (filename && node.type !== 'text' && node.type !== 'multipart') {
            result.push({
              partId: node.part || part,
              filename,
              contentType: `${node.type}/${node.subtype}`,
              size: node.size || 0,
            });
          }
          if (node.childNodes) node.childNodes.forEach((c: any, i: number) => walk(c, `${part}.${i + 1}`));
        }
        walk(msg.bodyStructure, '1');
        return result;
      } finally {
        mailbox.release();
      }
    });
  }

  async testConnection(account: UserMailAccount): Promise<{ success: boolean; message: string }> {
    const decryptedPassword = encryptionService.decrypt(account.password);
    const client = new ImapFlow({
      host: account.host,
      port: account.port,
      secure: account.useTls,
      auth: { user: account.username, pass: decryptedPassword },
      logger: false as any,
      tls: { rejectUnauthorized: false },
    });
    try {
      await client.connect();
      await client.logout();
      return { success: true, message: 'IMAP 연결 성공' };
    } catch (err) {
      let message = '연결 실패';
      if (err instanceof Error) {
        const imapErr = err as any;
        const raw = imapErr.response || imapErr.responseCode || imapErr.cause?.message || err.message;
        const r = String(raw).toLowerCase();
        if (r.includes('authentication') || r.includes('invalid credentials') || r.includes('authenticationfailed') || r.includes('login failed')) {
          message = '인증 실패: 이메일 주소 또는 비밀번호가 올바르지 않습니다.';
        } else if (r.includes('econnrefused') || r.includes('connection refused')) {
          message = '연결 거부: 호스트 또는 포트를 확인하세요.';
        } else if (r.includes('enotfound') || r.includes('getaddrinfo')) {
          message = '호스트를 찾을 수 없습니다. IMAP 주소를 확인하세요.';
        } else if (r.includes('timeout') || r.includes('etimedout')) {
          message = '연결 시간 초과: 서버가 응답하지 않습니다.';
        } else if (r.includes('self signed') || r.includes('certificate')) {
          message = 'SSL 인증서 오류가 발생했습니다.';
        } else if (r.includes('econnreset')) {
          message = '연결이 강제로 끊겼습니다. TLS/SSL 설정을 확인하세요.';
        } else {
          message = raw;
        }
      }
      return { success: false, message };
    }
  }
}

export const userMailImapService = new UserMailImapService();
