import { describe, expect, it } from "vitest";
import { authed, login, setup } from "../helpers.js";

describe("認証ガード (TC-FR-AUTH-001 / NFR-SEC-001)", () => {
  it("未認証で管理APIは401", async () => {
    const { app } = setup();
    const res = await app.request("/api/subscribers");
    expect(res.status).toBe(401);
  });

  it("ログイン後はアクセス可、Cookieは HttpOnly+Secure", async () => {
    const { app } = setup();
    const res = await app.request("/api/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ password: "secret-pass" }),
    });
    expect(res.status).toBe(200);
    const sc = res.headers.get("Set-Cookie") ?? "";
    expect(sc).toContain("HttpOnly");
    expect(sc).toContain("Secure");
    expect(sc).toContain("SameSite=Lax");
    const cookie = sc.split(";")[0];
    const r2 = await app.request("/api/subscribers", { headers: { Cookie: cookie } });
    expect(r2.status).toBe(200);
  });

  it("誤パスワードは401", async () => {
    const { app } = setup();
    const res = await app.request("/api/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ password: "wrong" }),
    });
    expect(res.status).toBe(401);
  });
});

describe("一斉配信API (TC-FR-BC-001)", () => {
  it("リスト→配信下書き作成", async () => {
    const { app } = setup();
    const cookie = await login(app);
    const list = await (await app.request("/api/lists", authed(cookie, { name: "L" }))).json();
    const res = await app.request(
      "/api/broadcasts",
      authed(cookie, { name: "B", subject: "S", body_html: "<p>x</p>", list_id: list.list.id }),
    );
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.broadcast.status).toBe("draft");
  });

  it("必須項目欠如は400", async () => {
    const { app } = setup();
    const cookie = await login(app);
    const res = await app.request("/api/broadcasts", authed(cookie, { name: "B" }));
    expect(res.status).toBe(400);
  });
});

describe("外部連携 (TC-FR-HOOK-001/002/003)", () => {
  async function seedHook(withCampaign: boolean) {
    const { app, store } = setup();
    const cookie = await login(app);
    const list = (await (await app.request("/api/lists", authed(cookie, { name: "L" }))).json()).list;
    let campId: number | null = null;
    if (withCampaign) {
      const camp = (await (await app.request("/api/campaigns", authed(cookie, { name: "C", list_id: list.id }))).json()).campaign;
      await app.request(
        `/api/campaigns/${camp.id}/steps`,
        authed(cookie, { step_order: 0, delay_minutes: 0, subject: "S0", body_html: "x" }),
      );
      campId = camp.id;
    }
    const hook = (await (await app.request("/api/hooks", authed(cookie, { list_id: list.id, campaign_id: campId, name: "H" }))).json()).hook;
    return { app, store, cookie, list, hook, campId };
  }

  it("Webhookで登録+list所属+enroll (TC-FR-HOOK-001)", async () => {
    const { app, store, hook, list, campId } = await seedHook(true);
    const res = await app.request(`/api/hooks/${hook.token}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email: "new@x.com", name: "新規" }),
    });
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.created).toBe(true);
    expect(data.enrolled).toBe(true);
    expect((await store.subscribersInList(list.id)).length).toBe(1);
    expect((await store.listEnrollments(campId!)).length).toBe(1);
  });

  it("同emailの再Webhookは冪等（重複登録/重複enrollなし）", async () => {
    const { app, store, hook, list, campId } = await seedHook(true);
    const body = { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: "dup@x.com" }) } as RequestInit;
    await app.request(`/api/hooks/${hook.token}`, body);
    await app.request(`/api/hooks/${hook.token}`, body);
    expect((await store.subscribersInList(list.id)).length).toBe(1);
    expect((await store.listEnrollments(campId!)).length).toBe(1);
  });

  it("フォームGETで登録ページ、POSTで登録 (TC-FR-HOOK-002)", async () => {
    const { app, store, hook, list } = await seedHook(false);
    const page = await app.request(`/f/${hook.token}`);
    expect(page.status).toBe(200);
    expect(await page.text()).toContain("<form");
    const submit = await app.request(`/f/${hook.token}`, {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: "email=form@x.com&name=フォーム",
    });
    expect(submit.status).toBe(200);
    expect(await submit.text()).toContain("ありがとう");
    expect((await store.subscribersInList(list.id)).length).toBe(1);
  });

  it("無効token404 / 不正email400 (TC-FR-HOOK-003)", async () => {
    const { app, hook } = await seedHook(false);
    const bad = await app.request(`/api/hooks/nope`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email: "x@x.com" }),
    });
    expect(bad.status).toBe(404);
    const badEmail = await app.request(`/api/hooks/${hook.token}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email: "not-an-email" }),
    });
    expect(badEmail.status).toBe(400);
  });
});

describe("配信停止/到達率 (TC-FR-DELIV-002/003)", () => {
  it("/u/:token で配信停止", async () => {
    const { app, store } = setup();
    const { subscriber } = await store.upsertSubscriber("u@x.com", null, "s");
    const res = await app.request(`/u/${subscriber.unsub_token}`);
    expect(res.status).toBe(200);
    expect((await store.getSubscriber(subscriber.id))?.status).toBe("unsubscribed");
  });

  it("Resend Webhookでbounce/complaint反映", async () => {
    const { app, store } = setup();
    await store.upsertSubscriber("b@x.com", null, "s");
    await store.upsertSubscriber("c@x.com", null, "s");
    await app.request("/api/resend/webhook", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ type: "email.bounced", data: { email: "b@x.com" } }),
    });
    await app.request("/api/resend/webhook", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ type: "email.complained", data: { email: "c@x.com" } }),
    });
    const subs = await store.listSubscribers();
    expect(subs.find((s) => s.email === "b@x.com")?.status).toBe("bounced");
    expect(subs.find((s) => s.email === "c@x.com")?.status).toBe("unsubscribed");
  });
});
