import {
  devices,
  expect,
  type Page,
  type Request,
  type Response,
  test,
} from "@playwright/test";

type LoadMetrics = {
  appOrigin: string;
  durationMs: number;
  transferredBytes: number;
  requestedUrls: string[];
  requestedPathCounts: Map<string, number>;
  cacheControlByPath: Map<string, string>;
  contentEncodingByPath: Map<string, string>;
};

async function mockPublicApis(page: Page): Promise<void> {
  await page.route("**/api/v1/**", async (route) => {
    const requestUrl = new URL(route.request().url());
    if (requestUrl.pathname.endsWith("/api/v1/user/me")) {
      await route.fulfill({
        status: 401,
        contentType: "application/json",
        body: JSON.stringify({ error: "unauthorized" }),
      });
      return;
    }

    await route.fulfill({
      status: 200,
      contentType: "application/json",
      body: JSON.stringify({}),
    });
  });
}

async function measureSigninLoad(page: Page): Promise<LoadMetrics> {
  const appOrigin = new URL(
    process.env.BASE_URL ?? `http://127.0.0.1:${process.env.PORT ?? "4173"}`,
  ).origin;
  const requestedUrls: string[] = [];
  const requestedPathCounts = new Map<string, number>();
  const cacheControlByPath = new Map<string, string>();
  const contentEncodingByPath = new Map<string, string>();
  let transferredBytes = 0;

  const onRequest = (request: Request) => {
    const requestUrl = new URL(request.url());
    requestedUrls.push(request.url());
    if (requestUrl.protocol === "http:" || requestUrl.protocol === "https:") {
      const resourceKey = `${requestUrl.origin}${requestUrl.pathname}`;
      requestedPathCounts.set(
        resourceKey,
        (requestedPathCounts.get(resourceKey) ?? 0) + 1,
      );
    }
  };

  const onResponse = async (response: Response) => {
    const url = new URL(response.url());
    const cacheControl = response.headers()["cache-control"];
    if (cacheControl) {
      cacheControlByPath.set(url.pathname, cacheControl);
    }
    const contentEncoding = response.headers()["content-encoding"];
    if (contentEncoding) {
      contentEncodingByPath.set(url.pathname, contentEncoding);
    }

    const timing = response.request().timing();
    if (timing.responseEnd >= 0) {
      const sizes = await response
        .request()
        .sizes()
        .catch(() => null);
      transferredBytes += sizes?.responseBodySize ?? 0;
    }
  };

  page.on("request", onRequest);
  page.on("response", onResponse);

  try {
    const start = performance.now();
    await page.goto("/ko/signin", { waitUntil: "networkidle" });
    await expect(page).toHaveURL(/\/ko\/signin(?:\?.*)?$/);
    const durationMs = Math.round(performance.now() - start);

    return {
      appOrigin,
      durationMs,
      transferredBytes,
      requestedUrls,
      requestedPathCounts,
      cacheControlByPath,
      contentEncodingByPath,
    };
  } finally {
    page.off("request", onRequest);
    page.off("response", onResponse);
  }
}

function expectNoDuplicateStaticRequests(metrics: LoadMetrics): void {
  const duplicates = [...metrics.requestedPathCounts.entries()].filter(
    ([resourceKey, count]) => {
      const resourceUrl = new URL(resourceKey);
      const path = resourceUrl.pathname;
      return (
        count > 1 &&
        resourceUrl.origin === metrics.appOrigin &&
        !path.startsWith("/api/") &&
        !path.endsWith("/ko/signin") &&
        !path.endsWith("/") &&
        !path.endsWith("/main.dart.wasm") &&
        !path.endsWith("/main.dart.mjs") &&
        !path.endsWith("/assets/AssetManifest.bin.json") &&
        !path.endsWith("/skwasm.js") &&
        !path.endsWith("/skwasm.wasm")
      );
    },
  );
  expect(duplicates).toEqual([]);
}

function resolvePerformanceBudget(projectName: string): {
  coldMs: number;
  warmMs: number;
} {
  if (projectName === "webkit-mobile-webapp") {
    return { coldMs: 10_000, warmMs: 4000 };
  }
  if (projectName.includes("webkit")) {
    return { coldMs: 4000, warmMs: 4000 };
  }
  if (projectName.includes("firefox")) {
    return { coldMs: 3000, warmMs: 2800 };
  }
  if (projectName.includes("mobile")) {
    return { coldMs: 3000, warmMs: 2300 };
  }
  return { coldMs: 2300, warmMs: 1500 };
}

function resolveRootRedirectBudget(projectName: string): number {
  if (projectName.includes("webkit")) {
    return 700;
  }
  if (projectName.includes("firefox")) {
    return 600;
  }
  return 300;
}

test.describe("UserFront login performance budget", () => {
  test("mobile Chrome service worker install does not fetch unused CanvasKit variants", async ({
    browser,
  }, testInfo) => {
    test.skip(
      testInfo.project.name !== "chromium-mobile-webapp",
      "service worker install race is covered once in the mobile Chromium project",
    );

    const context = await browser.newContext({
      ...devices["Pixel 7"],
      locale: "ko-KR",
      serviceWorkers: "allow",
    });
    const page = await context.newPage();
    await mockPublicApis(page);

    try {
      const serviceWorkerResponse = await context.request.get(
        new URL(
          "/flutter_service_worker.js",
          process.env.BASE_URL ??
            `http://127.0.0.1:${process.env.PORT ?? "4173"}`,
        ).toString(),
      );
      const serviceWorkerBody = await serviceWorkerResponse.text();
      expect(serviceWorkerBody).not.toContain('"/canvaskit/');
      expect(serviceWorkerBody).not.toContain('"/main.dart.');

      await page.goto("/ko/signin", { waitUntil: "domcontentloaded" });
      await page.waitForTimeout(3_000);
    } finally {
      await context.close();
    }
  });

  test("warm login page load stays within the platform budget and reuses cached assets", async ({
    page,
  }, testInfo) => {
    await mockPublicApis(page);
    const budget = resolvePerformanceBudget(testInfo.project.name);

    const cold = await measureSigninLoad(page);
    const warm = await measureSigninLoad(page);
    console.log(
      `[userfront-perf] cold=${cold.durationMs}ms/${cold.transferredBytes}B warm=${warm.durationMs}ms/${warm.transferredBytes}B`,
    );

    expect(cold.durationMs).toBeLessThanOrEqual(budget.coldMs);
    expect(warm.durationMs).toBeLessThanOrEqual(budget.warmMs);
    expect(warm.transferredBytes).toBeLessThanOrEqual(1_000_000);
    expectNoDuplicateStaticRequests(cold);
    expectNoDuplicateStaticRequests(warm);
    const cacheControlByPath = new Map([
      ...cold.cacheControlByPath,
      ...warm.cacheControlByPath,
    ]);
    const appShellCache = cacheControlByPath.get("/ko/signin") ?? "";
    expect(appShellCache).toContain("no-cache");
    const serviceWorkerState = await page.evaluate(async () => {
      if (!("serviceWorker" in navigator)) {
        return {
          available: false,
          secure: window.isSecureContext,
          scriptUrl: "",
        };
      }
      const registrations = await navigator.serviceWorker.getRegistrations();
      const registration = registrations[0];
      return {
        available: true,
        secure: window.isSecureContext,
        count: registrations.length,
        controller: navigator.serviceWorker.controller?.scriptURL ?? "",
        scriptUrl:
          registration?.active?.scriptURL ??
          registration?.waiting?.scriptURL ??
          registration?.installing?.scriptURL ??
          "",
      };
    });
    if (
      testInfo.project.name.includes("mobile") &&
      serviceWorkerState.scriptUrl
    ) {
      expect(new URL(serviceWorkerState.scriptUrl).pathname).toBe(
        "/flutter_service_worker.js",
      );
      const serviceWorkerResponse = await page
        .context()
        .request.get(
          new URL("/flutter_service_worker.js", page.url()).toString(),
        );
      expect(serviceWorkerResponse.headers()["cache-control"] ?? "").toContain(
        "no-cache",
      );
    } else {
      expect(serviceWorkerState.scriptUrl).toBe("");
    }

    expect(cold.durationMs).toBeGreaterThanOrEqual(0);
  });

  test("root redirects to localized signin before Flutter boots", async ({
    page,
  }, testInfo) => {
    await mockPublicApis(page);

    const requestedUrls: string[] = [];
    page.on("request", (request) => {
      requestedUrls.push(request.url());
    });

    const start = performance.now();
    await page.goto("/", { waitUntil: "domcontentloaded" });
    await expect(page).toHaveURL(/\/ko\/signin(?:\?.*)?$/);
    const durationMs = Math.round(performance.now() - start);

    expect(durationMs).toBeLessThanOrEqual(
      resolveRootRedirectBudget(testInfo.project.name),
    );
    const rootIndex = requestedUrls.findIndex(
      (url) => new URL(url).pathname === "/",
    );
    const signinIndex = requestedUrls.findIndex(
      (url) => new URL(url).pathname === "/ko/signin",
    );
    const bootstrapIndex = requestedUrls.findIndex((url) =>
      new URL(url).pathname.endsWith("/flutter_bootstrap.js"),
    );
    expect(rootIndex).toBeGreaterThanOrEqual(0);
    expect(signinIndex).toBeGreaterThan(rootIndex);
    if (bootstrapIndex >= 0) {
      expect(bootstrapIndex).toBeGreaterThan(signinIndex);
    }
  });
});
