import { test, expect } from "@playwright/test";
import { BASE_PATH } from "../../playwright.config";
import {
  startApp,
  startFakeSmtp,
  smtpEnvFor,
  validSubmission,
  type FakeSmtp,
  type RunningApp,
} from "../helpers/harness";

/**
 * The contact API exercised against the real standalone bundle and a local
 * SMTP server, so "the form works" means a message was genuinely accepted by a
 * mail server — not merely that the browser saw a success banner.
 */

test.describe.configure({ mode: "serial" });

let smtp: FakeSmtp;
let app: RunningApp;

test.beforeAll(async () => {
  smtp = await startFakeSmtp();
  app = await startApp({ env: smtpEnvFor(smtp.port), basePath: BASE_PATH });
});

test.afterAll(async () => {
  await app?.close();
  await smtp?.close();
});

test.beforeEach(() => {
  smtp.messages.length = 0;
  smtp.rejectAuth = false;
});

test("delivers a submitted enquiry to the configured mailbox", async () => {
  const res = await app.post("/api/contact", validSubmission({ name: "Ada Lovelace" }));

  expect(res.status).toBe(200);
  expect(res.json.success).toBe(true);
  expect(smtp.messages).toHaveLength(1);

  const mail = smtp.messages[0];
  expect(mail.to).toEqual(["inbox@clearfin.test"]);
  expect(mail.from).toBe("website@clearfin.test");
  expect(mail.header("subject")).toBe("[ClearFin] General Inquiry: Ada Lovelace (Test Corp)");
  expect(mail.header("reply-to")).toBe("runner@clearfin.test");
  expect(mail.decoded()).toContain("Automated test - please ignore.");
});

test("carries the selected subject through to the mail", async () => {
  await app.post("/api/contact", validSubmission({ subject: "Trading Applications" }));

  expect(smtp.messages).toHaveLength(1);
  expect(smtp.messages[0].header("subject")).toContain("Trading Applications");
});

test("sends an enquiry that omits the optional phone number", async () => {
  const { phone: _omitted, ...noPhone } = validSubmission();
  const res = await app.post("/api/contact", noPhone);

  expect(res.status).toBe(200);
  expect(smtp.messages).toHaveLength(1);
  expect(smtp.messages[0].decoded()).toContain("n/a");
});

test.describe("rejected submissions never reach the mail server", () => {
  test("missing required fields are a 400", async () => {
    const res = await app.post("/api/contact", { email: "runner@clearfin.test" });

    expect(res.status).toBe(400);
    expect(res.json.errors).toEqual(
      expect.arrayContaining(["Name is required.", "Message is required."]),
    );
    expect(smtp.messages).toHaveLength(0);
  });

  test("a malformed email address is a 400", async () => {
    const res = await app.post("/api/contact", validSubmission({ email: "nope" }));

    expect(res.status).toBe(400);
    expect(smtp.messages).toHaveLength(0);
  });

  test("a subject outside the offered list is a 400", async () => {
    const res = await app.post("/api/contact", validSubmission({ subject: "Free Money" }));

    expect(res.status).toBe(400);
    expect(smtp.messages).toHaveLength(0);
  });

  test("a body that is not JSON is a 400, not a 500", async () => {
    const res = await app.post("/api/contact", "this is not json");

    expect(res.status).toBe(400);
    expect(smtp.messages).toHaveLength(0);
  });
});

test("neutralises HTML and header injection in delivered mail", async () => {
  await app.post(
    "/api/contact",
    validSubmission({
      name: "Ada\r\nBcc: attacker@example.com",
      message: '<script>alert("xss")</script>',
    }),
  );

  expect(smtp.messages).toHaveLength(1);
  const mail = smtp.messages[0];

  // The injected text is fine as body content; what must not happen is it
  // becoming a header of its own or an extra envelope recipient.
  expect(mail.to).toEqual(["inbox@clearfin.test"]);
  expect(mail.header("bcc")).toBe("");
  expect(mail.header("subject")).toBe(
    "[ClearFin] General Inquiry: Ada Bcc: attacker@example.com (Test Corp)",
  );
  // The HTML part is escaped; the plain-text part is intentionally verbatim.
  expect(mail.html()).not.toContain("<script>");
  expect(mail.html()).toContain("&lt;script&gt;");
  expect(mail.text()).toContain('<script>alert("xss")</script>');
});

test("reports a 502 when the mail server rejects the credentials", async () => {
  smtp.rejectAuth = true;

  const res = await app.post("/api/contact", validSubmission());

  expect(res.status).toBe(502);
  expect(res.json.error).toContain("Failed to send message");
  expect(smtp.messages).toHaveLength(0);
});

test("the mail health check passes when SMTP is configured", async () => {
  const res = await app.get("/api/health/mail");

  expect(res.status).toBe(200);
  expect(res.json).toMatchObject({ ok: true, configured: true });
});
