import { test, expect, waitForHydration, type CDPSession, type Page } from "./fixtures/test";

// Inline virtual-authenticator + register helpers, mirroring the pattern
// in auth.test.ts / public-content.test.ts so this file runs standalone.
async function setupVirtualAuthenticator(cdp: CDPSession) {
  await cdp.send("WebAuthn.enable");
  const { authenticatorId } = await cdp.send("WebAuthn.addVirtualAuthenticator", {
    options: {
      protocol: "ctap2",
      transport: "internal",
      hasResidentKey: true,
      hasUserVerification: true,
      isUserVerified: true,
    },
  });
  return authenticatorId;
}

async function registerUser(page: Page, email: string, username: string) {
  await page.goto("/auth/register");
  await expect(page.getByRole("heading", { name: "Register" })).toBeVisible();
  await waitForHydration(page);
  await page.getByLabel("Email").fill(email);
  await page.getByLabel("Username").fill(username);
  await page.getByRole("checkbox").check();
  await page.getByRole("button", { name: /Register with Passkey/ }).click();
  await expect(page).toHaveURL("/", { timeout: 10000 });
}

async function setProfileVisibility(page: Page, value: "public" | "private") {
  await page.goto("/settings");
  // Target the radio by name+value; getByLabel collides because the
  // help text of one radio mentions the other's word ("public" appears
  // in the Private radio's helper sentence).
  await page.locator(`input[type=radio][name=profileVisibility][value=${value}]`).check();
  await page.getByRole("button", { name: /^Save$/ }).first().click();
  // SSE keeps the network busy; wait for the save confirmation instead.
  await expect(page.getByText("Profile saved.")).toBeVisible({ timeout: 10000 });
}

// WebAuthn + parallel workers + shared local Postgres race; serialize.
test.describe.configure({ mode: "serial" });

test.describe("Social follows + /feed", () => {
  test("/feed redirects anonymous visitors to login", async ({ page }) => {
    await page.goto("/feed");
    await expect(page).toHaveURL(/\/auth\/login/, { timeout: 10000 });
  });

  test("/follows/outgoing redirects anonymous visitors to login", async ({ page }) => {
    await page.goto("/follows/outgoing");
    await expect(page).toHaveURL(/\/auth\/login/, { timeout: 10000 });
  });

  test("/follows/requests redirects to /notifications?tab=requests", async ({ page, browser }) => {
    // The route was folded into the Notifications page as a tab. The
    // 301 still resolves; for anonymous visitors the notifications
    // loader then redirects to /auth/login.
    await page.goto("/follows/requests");
    await expect(page).toHaveURL(/\/auth\/login/, { timeout: 10000 });

    // Signed-in visitors land on /notifications?tab=requests after the
    // 301.
    const cdp = await page.context().newCDPSession(page);
    await setupVirtualAuthenticator(cdp);
    const stamp = Date.now();
    const ctx = await browser.newContext();
    const p2 = await ctx.newPage();
    const p2cdp = await p2.context().newCDPSession(p2);
    await setupVirtualAuthenticator(p2cdp);
    await registerUser(p2, `redir-${stamp}@example.com`, `redir${stamp}`);
    await p2.goto("/follows/requests");
    await expect(p2).toHaveURL(/\/notifications\?tab=requests/, { timeout: 10000 });
    await ctx.close();
  });

  test("Follow button toggles state on a public profile", async ({ page, browser }) => {
    const cdp = await page.context().newCDPSession(page);
    await setupVirtualAuthenticator(cdp);

    const stamp = Date.now();
    const aEmail = `social-a-${stamp}@example.com`;
    const aUsername = `sa${stamp}`;
    const bEmail = `social-b-${stamp}@example.com`;
    const bUsername = `sb${stamp}`;

    await registerUser(page, aEmail, aUsername);

    // Register B in a separate browser context so we have two independent
    // sessions. New users default to `private` — B flips to public so this
    // test exercises the auto-accept path.
    const bCtx = await browser.newContext();
    const bPage = await bCtx.newPage();
    const bCdp = await bPage.context().newCDPSession(bPage);
    await setupVirtualAuthenticator(bCdp);
    await registerUser(bPage, bEmail, bUsername);
    await setProfileVisibility(bPage, "public");

    // A visits B's profile and follows.
    await page.goto(`/users/${bUsername}`);
    await expect(page.getByRole("button", { name: "Follow" })).toBeVisible();
    await page.getByRole("button", { name: "Follow" }).click();
    await expect(page.getByRole("button", { name: "Unfollow" })).toBeVisible({ timeout: 5000 });

    // Follower count on B's profile should now read 1.
    await page.reload();
    await expect(page.getByRole("link", { name: /1\s+Followers/i })).toBeVisible();

    // Unfollow.
    await page.getByRole("button", { name: "Unfollow" }).click();
    await expect(page.getByRole("button", { name: "Follow" })).toBeVisible({ timeout: 5000 });

    await bCtx.close();
  });

  test("Private profile: stub for visitors, Request → Pending → Approve → full view", async ({ page, browser }) => {
    const cdp = await page.context().newCDPSession(page);
    await setupVirtualAuthenticator(cdp);

    const stamp = Date.now();
    const aEmail = `req-a-${stamp}@example.com`;
    const aUsername = `ra${stamp}`;
    const bEmail = `req-b-${stamp}@example.com`;
    const bUsername = `rb${stamp}`;

    await registerUser(page, aEmail, aUsername);

    const bCtx = await browser.newContext();
    const bPage = await bCtx.newPage();
    const bCdp = await bPage.context().newCDPSession(bPage);
    await setupVirtualAuthenticator(bCdp);
    await registerUser(bPage, bEmail, bUsername);
    // B stays at the default `private`. Verify by loading the profile
    // anonymously and seeing the stub.

    const anonCtx = await browser.newContext();
    const anon = await anonCtx.newPage();
    const anonResp = await anon.goto(`/users/${bUsername}`);
    expect(anonResp?.status()).toBe(200);
    await expect(anon.getByText(/This profile is private/i)).toBeVisible();

    // A (signed in) visits B's private profile — sees stub + Request button.
    await page.goto(`/users/${bUsername}`);
    await expect(page.getByText(/This profile is private/i)).toBeVisible();
    await expect(page.getByRole("button", { name: /Request to follow/i })).toBeVisible();
    await page.getByRole("button", { name: /Request to follow/i }).click();
    await expect(page.getByRole("button", { name: /Requested/i })).toBeVisible({ timeout: 5000 });

    // B sees the request in the Requests tab on /notifications.
    await bPage.goto("/notifications?tab=requests");
    await expect(bPage.getByText(`@${aUsername}`)).toBeVisible();
    await bPage.getByRole("button", { name: "Approve" }).click();
    // Empty state after approval. The text-visibility assertion below
    // already polls; explicit networkidle would hang on SSE.
    await expect(bPage.getByText(/No pending follow requests/i)).toBeVisible();

    // A reloads B's profile — now sees full content (no stub) + Unfollow.
    await page.goto(`/users/${bUsername}`);
    await expect(page.getByText(/This profile is private/i)).not.toBeVisible();
    await expect(page.getByRole("button", { name: "Unfollow" })).toBeVisible();

    await bCtx.close();
    await anonCtx.close();
  });

  test("Private profile: visitor sees stub layout (200, not 404)", async ({ page, browser }) => {
    const cdp = await page.context().newCDPSession(page);
    await setupVirtualAuthenticator(cdp);
    const stamp = Date.now();
    const username = `priv${stamp}`;
    await registerUser(page, `priv-${stamp}@example.com`, username);
    // User stays at default `private`.

    const anonCtx = await browser.newContext();
    const anon = await anonCtx.newPage();
    const resp = await anon.goto(`/users/${username}`);
    expect(resp?.status()).toBe(200);
    await expect(anon.getByText(/This profile is private/i)).toBeVisible();

    await anonCtx.close();
  });
});
