import { test, expect } from '@playwright/test';
import { ImapFlow } from 'imapflow';
import { createTestAddress, seedTestMail, sendTestMail, deleteAddress, deleteAllMailpitMessages, onMailpitMessage } from '../../fixtures/test-helpers';

const IMAP_HOST = process.env.SMTP_PROXY_HOST || 'smtp-proxy';
const IMAP_PORT = parseInt(process.env.SMTP_PROXY_IMAP_PORT || '11143', 10);

function createClient(user: string, pass: string) {
  return new ImapFlow({
    host: IMAP_HOST,
    port: IMAP_PORT,
    secure: false,
    auth: { user, pass },
    logger: false,
  });
}

test.describe('IMAP Proxy', () => {
  let jwt: string;
  let address: string;

  test.beforeAll(async ({ request }) => {
    ({ jwt, address } = await createTestAddress(request, 'imap-e2e'));
    await seedTestMail(request, address, { subject: 'IMAP Test 1', text: 'First test email' });
    await seedTestMail(request, address, { subject: 'IMAP Test 2', text: 'Second test email' });
  });

  test.afterAll(async ({ request }) => {
    await deleteAddress(request, jwt);
  });

  test('login with JWT token', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    expect(client.usable).toBe(true);
    await client.logout();
  });

  test('login with wrong password fails', async () => {
    const client = createClient(address, 'wrong-password');
    await expect(client.connect()).rejects.toThrow();
  });

  test('LIST returns INBOX', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const mailboxes = await client.list();
    const names = mailboxes.map(m => m.path);
    expect(names).toContain('INBOX');
    await client.logout();
  });

  test('SELECT INBOX returns message count', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('INBOX');
    try {
      expect(client.mailbox).toBeTruthy();
      expect(client.mailbox!.exists).toBeGreaterThanOrEqual(2);
    } finally {
      lock.release();
    }
    await client.logout();
  });

  test('STATUS returns MESSAGES and UIDNEXT', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const status = await client.status('INBOX', { messages: true, uidNext: true });
    expect(status.messages).toBeGreaterThanOrEqual(2);
    expect(status.uidNext).toBeGreaterThan(0);
    await client.logout();
  });

  test('FETCH headers returns Subject', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('INBOX');
    try {
      const msg = await client.fetchOne('1', { headers: true });
      const headers = msg.headers.toString();
      expect(headers).toContain('Subject:');
    } finally {
      lock.release();
    }
    await client.logout();
  });

  test('FETCH body returns content', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('INBOX');
    try {
      const msg = await client.fetchOne('1', { source: true });
      expect(msg.source.length).toBeGreaterThan(0);
    } finally {
      lock.release();
    }
    await client.logout();
  });

  test('SEARCH ALL returns message numbers', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('INBOX');
    try {
      const results = await client.search({ all: true });
      expect(results.length).toBeGreaterThanOrEqual(2);
    } finally {
      lock.release();
    }
    await client.logout();
  });

  test('STORE sets flags', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('INBOX');
    try {
      const result = await client.messageFlagsAdd('1', ['\\Seen']);
      expect(result).toBe(true);
    } finally {
      lock.release();
    }
    await client.logout();
  });

  test('UID FETCH works', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('INBOX');
    try {
      const results = await client.search({ all: true });
      expect(results.length).toBeGreaterThan(0);
      const msg = await client.fetchOne(String(results[0]), { uid: true, flags: true }, { uid: true });
      expect(msg.uid).toBe(results[0]);
    } finally {
      lock.release();
    }
    await client.logout();
  });

  test('FETCH source contains valid MIME with Content-Type and seeded body', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('INBOX');
    try {
      const msg = await client.fetchOne('1', { source: true, envelope: true });
      const source = msg.source.toString('utf-8');
      expect(source).toContain('Content-Type:');
      expect(source).toContain('Subject:');
      // No duplicate From headers (regression: getBodyFile returned full MIME)
      const fromMatches = source.match(/^From:/gm);
      expect(fromMatches).toHaveLength(1);
    } finally {
      lock.release();
    }
    await client.logout();
  });

  test('RFC822.SIZE matches actual source length', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('INBOX');
    try {
      const msg = await client.fetchOne('1', { source: true, size: true });
      const source = msg.source.toString('utf-8');
      expect(msg.size).toBe(Buffer.byteLength(source, 'utf-8'));
    } finally {
      lock.release();
    }
    await client.logout();
  });

  test('FETCH all messages returns correct sequence numbers', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('INBOX');
    try {
      const messages: { seq: number; uid: number }[] = [];
      for await (const msg of client.fetch('1:*', { uid: true })) {
        messages.push({ seq: msg.seq, uid: msg.uid });
      }
      expect(messages.length).toBeGreaterThanOrEqual(2);
      // Sequence numbers must be consecutive starting from 1
      for (let i = 0; i < messages.length; i++) {
        expect(messages[i].seq).toBe(i + 1);
      }
      // UIDs must be strictly ascending
      for (let i = 1; i < messages.length; i++) {
        expect(messages[i].uid).toBeGreaterThan(messages[i - 1].uid);
      }
    } finally {
      lock.release();
    }
    await client.logout();
  });

  test('LIST returns SENT mailbox', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const mailboxes = await client.list();
    const names = mailboxes.map(m => m.path);
    expect(names).toContain('SENT');
    await client.logout();
  });

  test('SELECT INBOX includes UIDVALIDITY and UIDNEXT', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('INBOX');
    try {
      expect(client.mailbox!.uidValidity).toBeGreaterThan(0);
      expect(client.mailbox!.uidNext).toBeGreaterThan(0);
    } finally {
      lock.release();
    }
    await client.logout();
  });
});

test.describe('IMAP Proxy — SENT mailbox', () => {
  let jwt: string;
  let address: string;
  const sentSubject = `IMAP Sent Test ${Date.now()}`;

  test.beforeAll(async ({ request }) => {
    await deleteAllMailpitMessages(request);
    ({ jwt, address } = await createTestAddress(request, 'imap-sent'));

    const listener = onMailpitMessage((m) => m.Subject === sentSubject);
    await listener.ready;

    await sendTestMail(request, address, {
      to_mail: `recipient@test.example.com`,
      subject: sentSubject,
      content: 'E2E sent mail body',
    });
    await listener.message;
  });

  test.afterAll(async ({ request }) => {
    await deleteAddress(request, jwt);
  });

  test('SELECT SENT returns message count', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('SENT');
    try {
      expect(client.mailbox).toBeTruthy();
      expect(client.mailbox!.exists).toBeGreaterThanOrEqual(1);
    } finally {
      lock.release();
    }
    await client.logout();
  });

  test('FETCH SENT source contains valid MIME', async () => {
    const client = createClient(address, jwt);
    await client.connect();
    const lock = await client.getMailboxLock('SENT');
    try {
      const msg = await client.fetchOne('1', { source: true, envelope: true });
      const source = msg.source.toString('utf-8');
      expect(source.length).toBeGreaterThan(50);
      expect(source).toContain('Content-Type:');
      expect(source).toContain('Subject:');
    } finally {
      lock.release();
    }
    await client.logout();
  });
});
