import 'dotenv/config';
import { test, expect } from '@playwright/test';
import { ImapFlow } from 'imapflow';
import { simpleParser, type ParsedMail } from 'mailparser';

import { login, apiHeaders } from '../smoke/helpers';

/**
 * Real-API end-to-end test for the portal activation flow over real SMTP+IMAP.
 *
 * Walks the full loop:
 *   1. Create a fresh client with a +addressed test email.
 *   2. Admin invites the client to the portal, triggering the activation email.
 *   3. Connect to IMAP and poll until the activation email lands.
 *   4. Parse the email, extract the activation token from the link.
 *   5. POST /api/portal/auth/activate with the token + new password.
 *   6. POST /api/portal/auth/sign-in with the same email + password.
 *   7. Assert the sign-in succeeds.
 *
 * Requires IMAP_HOST + IMAP_USER + IMAP_PASS in the environment to read the
 * destination mailbox. The test sends to <IMAP_USER>+imap-test-<ts> which
 * routes back to the same inbox via standard +addressing.
 *
 * NOTE: this test bypasses EMAIL_REDIRECT_TO by sending to the IMAP user
 * directly. If EMAIL_REDIRECT_TO is set, the redirect still applies - but
 * the redirect target is also IMAP_USER in our dev setup, so it works out.
 */

const IMAP_HOST = process.env.IMAP_HOST;
const IMAP_PORT = Number(process.env.IMAP_PORT ?? 993);
const IMAP_USER = process.env.IMAP_USER;
const IMAP_PASS = process.env.IMAP_PASS;

const MAILBOX = 'INBOX';
const POLL_TIMEOUT_MS = 60_000;
const POLL_INTERVAL_MS = 5_000;

function plusAddress(base: string, tag: string): string {
  const [local, domain] = base.split('@');
  return `${local}+${tag}@${domain}`;
}

async function fetchActivationToken(args: {
  recipientEmail: string;
  notBefore: Date;
}): Promise<string> {
  if (!IMAP_HOST || !IMAP_USER || !IMAP_PASS) {
    throw new Error('IMAP credentials not configured');
  }

  const client = new ImapFlow({
    host: IMAP_HOST,
    port: IMAP_PORT,
    secure: IMAP_PORT === 993,
    auth: { user: IMAP_USER, pass: IMAP_PASS },
    logger: false,
  });

  await client.connect();
  try {
    const lock = await client.getMailboxLock(MAILBOX);
    try {
      const deadline = Date.now() + POLL_TIMEOUT_MS;
      while (Date.now() < deadline) {
        // Walk recent messages; first one whose body carries an
        // /portal/activate?token=... link wins. We don't filter on TO header
        // because EMAIL_REDIRECT_TO may rewrite it and +addressing may or may
        // not be preserved depending on the server config.
        const searchResult = await client.search({ since: args.notBefore });
        const uids = Array.isArray(searchResult) ? [...searchResult].reverse() : [];
        for (const uid of uids) {
          const msg = await client.fetchOne(String(uid), { source: true });
          if (!msg || !msg.source) continue;
          const parsed: ParsedMail = await simpleParser(msg.source);
          // Skip anything that isn't our send (sender + subject discriminator).
          const fromAddr = parsed.from?.value?.[0]?.address?.toLowerCase() ?? '';
          const subject = (parsed.subject ?? '').toLowerCase();
          if (!fromAddr.includes('noreply@letsbe.solutions') || !subject.includes('activate')) {
            continue;
          }
          const body = parsed.html || parsed.text || '';
          const match = body.match(/\/portal\/activate\?token=([A-Za-z0-9_\-]+)/);
          if (match?.[1]) {
            return match[1];
          }
        }
        await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
      }
      throw new Error(
        `Activation email for ${args.recipientEmail} not found within ${POLL_TIMEOUT_MS}ms`,
      );
    } finally {
      lock.release();
    }
  } finally {
    await client.logout();
  }
}

test.describe('Portal activation: SMTP + IMAP round-trip', () => {
  test.skip(!IMAP_HOST || !IMAP_USER || !IMAP_PASS, 'IMAP_HOST / IMAP_USER / IMAP_PASS not set');

  test.beforeEach(async ({ page }) => {
    await login(page, 'super_admin');
  });

  test('full activation loop via real SMTP delivery + IMAP retrieval', async ({ page }) => {
    const stamp = Date.now();
    const headers = await apiHeaders(page);
    const recipientEmail = plusAddress(IMAP_USER!, `imap-test-${stamp}`);
    const notBefore = new Date(Date.now() - 60_000); // 1-min skew tolerance

    // ─── 1. Create a fresh client ────────────────────────────────────────────
    const clientRes = await page.request.post('/api/v1/clients', {
      headers,
      data: {
        fullName: `IMAP Test Client ${stamp}`,
        contacts: [{ channel: 'email', value: recipientEmail, isPrimary: true }],
      },
    });
    expect(clientRes.ok(), `client create: ${clientRes.status()}`).toBe(true);
    const clientId = (await clientRes.json()).data.id as string;

    // ─── 2. Admin invites the client ─────────────────────────────────────────
    const inviteRes = await page.request.post(`/api/v1/clients/${clientId}/portal-user`, {
      headers,
      data: { email: recipientEmail, name: `IMAP Test Client ${stamp}` },
    });
    expect(inviteRes.ok(), `invite: ${inviteRes.status()} ${await inviteRes.text()}`).toBe(true);

    // ─── 3-4. Poll IMAP and extract the activation token ─────────────────────
    // Match on the +addressed recipient - that's what's preserved in the TO
    // header even after the mailserver delivers it back to IMAP_USER's inbox.
    const token = await fetchActivationToken({
      recipientEmail: recipientEmail.toLowerCase(),
      notBefore,
    });
    expect(token, 'activation token extracted from email').toBeTruthy();

    // ─── 5. Activate the account ─────────────────────────────────────────────
    const password = `Imap-Test-${stamp}!`;
    const activateRes = await page.request.post('/api/portal/auth/activate', {
      data: { token, password },
    });
    expect(activateRes.ok(), `activate: ${activateRes.status()} ${await activateRes.text()}`).toBe(
      true,
    );

    // ─── 6-7. Sign in with the new credentials ───────────────────────────────
    const signInRes = await page.request.post('/api/portal/auth/sign-in', {
      data: { email: recipientEmail, password },
    });
    expect(signInRes.ok(), `sign-in: ${signInRes.status()} ${await signInRes.text()}`).toBe(true);
  });
});
