import { expect, type Page, type Route, test } from "@playwright/test";

type ProfileState = {
  department: string;
  getMeCount: number;
  putBodies: Array<Record<string, unknown>>;
};

async function enableFlutterAccessibility(page: Page): Promise<void> {
  const button = page.getByRole("button", { name: "Enable accessibility" });
  if (await button.count()) {
    await button.click({ force: true }).catch(async () => {
      await page
        .locator('flt-semantics-placeholder[aria-label="Enable accessibility"]')
        .evaluate((element) => {
          if (element instanceof HTMLElement) element.click();
        });
    });
    await page.waitForTimeout(200);
  }
}

type ProfileCoords = {
  departmentEditX: number;
  departmentEditY: number;
  departmentInputX: number;
  departmentInputY: number;
  blurX: number;
  blurY: number;
};

const desktopCoords: ProfileCoords = {
  departmentEditX: 1170,
  departmentEditY: 680,
  departmentInputX: 110,
  departmentInputY: 685,
  blurX: 200,
  blurY: 260,
};

const mobileCoords: ProfileCoords = {
  departmentEditX: 350,
  departmentEditY: 680,
  departmentInputX: 110,
  departmentInputY: 685,
  blurX: 200,
  blurY: 260,
};

function coordsFor(page: Page): ProfileCoords {
  const viewport = page.viewportSize();
  return (viewport?.width ?? 1280) <= 500 ? mobileCoords : desktopCoords;
}

function isMobileProject(page: Page): boolean {
  const viewport = page.viewportSize();
  return (viewport?.width ?? 1280) <= 500;
}

async function seedTokenLogin(page: Page): Promise<void> {
  await page.addInitScript(() => {
    window.localStorage.setItem("baron_auth_token", "e30.e30.e30");
    window.localStorage.setItem("baron_auth_provider", "ory");
    window.localStorage.removeItem("baron_auth_cookie_mode");
    window.localStorage.removeItem("baron_auth_pending_provider");
  });
}

async function fillAt(
  page: Page,
  x: number,
  y: number,
  value: string,
): Promise<void> {
  const pane = page.locator("flt-glass-pane");
  await pane.click({ position: { x, y }, force: true });
  await page.waitForTimeout(100);
  await replaceFocusedText(page, value);
}

async function replaceFocusedText(page: Page, value: string): Promise<void> {
  await page.keyboard.press("End");
  for (let index = 0; index < 64; index += 1) {
    await page.keyboard.press("Backspace");
  }
  if (value !== "") {
    await page.keyboard.insertText(value);
  }
  await page.waitForTimeout(100);
}

type BoxCenter = {
  x: number;
  y: number;
};

async function resolveLocatorCenter(
  locator: ReturnType<Page["locator"]>,
): Promise<BoxCenter | null> {
  const handle = await locator
    .elementHandle({ timeout: 1_000 })
    .catch(() => null);
  if (!handle) {
    return null;
  }
  const box = await handle
    .evaluate((element) => {
      const rect = element.getBoundingClientRect();
      return {
        x: rect.left,
        y: rect.top,
        width: rect.width,
        height: rect.height,
      };
    })
    .catch(() => null);
  await handle.dispose();
  if (!box) {
    return null;
  }
  return {
    x: box.x + box.width / 2,
    y: box.y + box.height / 2,
  };
}

async function clickGlassPaneAt(
  page: Page,
  center: BoxCenter | null,
): Promise<boolean> {
  if (!center) {
    return false;
  }
  await page.locator("flt-glass-pane").click({
    position: center,
    force: true,
  });
  await page.waitForTimeout(200);
  return true;
}

async function departmentTextboxIsOpen(page: Page): Promise<boolean> {
  return (await page.getByRole("textbox", { name: "소속" }).count()) > 0;
}

async function openDepartmentEditor(page: Page): Promise<void> {
  const accessibleEditor = page
    .getByRole("group", { name: "소속 QA" })
    .getByRole("button", { name: "편집" });
  const textbox = page.getByRole("textbox", { name: "소속" });
  if ((await accessibleEditor.count()) > 0) {
    const editorCenter = await resolveLocatorCenter(accessibleEditor);
    await accessibleEditor
      .evaluate(
        (element) => {
          if (element instanceof HTMLElement) {
            element.click();
          }
        },
        { timeout: 1_000 },
      )
      .catch(() => undefined);
    await page.waitForTimeout(200);
    if (await departmentTextboxIsOpen(page)) {
      return;
    }
    await clickGlassPaneAt(page, editorCenter);
    if (await departmentTextboxIsOpen(page)) {
      return;
    }
    await accessibleEditor
      .click({ force: true, timeout: 1_000 })
      .catch(() => undefined);
    await page.waitForTimeout(200);
    if (await departmentTextboxIsOpen(page)) {
      return;
    }
  }
  if (isMobileProject(page)) {
    throw new Error("Department editor accessibility button was not found.");
  }
  const coords = coordsFor(page);
  const viewport = page.viewportSize();
  const editCandidates: BoxCenter[] = [
    { x: coords.departmentEditX, y: coords.departmentEditY },
    { x: (viewport?.width ?? 1280) - 110, y: coords.departmentEditY },
    { x: coords.departmentEditX - 24, y: coords.departmentEditY },
    { x: coords.departmentEditX + 24, y: coords.departmentEditY },
  ];
  for (const candidate of editCandidates) {
    await clickGlassPaneAt(page, candidate);
    if (await departmentTextboxIsOpen(page)) {
      return;
    }
  }
  await expect(textbox).toHaveCount(1, { timeout: 1_000 });
}

async function blurDepartmentEditor(page: Page): Promise<void> {
  const textbox = page.getByRole("textbox", { name: "소속" });
  if ((await textbox.count()) > 0) {
    await textbox.blur();
    await page.waitForTimeout(250);
    return;
  }
  if (isMobileProject(page)) {
    throw new Error("Department textbox was not found.");
  }
  const coords = coordsFor(page);
  await page.locator("flt-glass-pane").click({
    position: { x: coords.blurX, y: coords.blurY },
    force: true,
  });
  await page.waitForTimeout(250);
}

async function submitDepartmentEditor(page: Page): Promise<void> {
  const textbox = page.getByRole("textbox", { name: "소속" });
  if ((await textbox.count()) > 0) {
    await textbox.press("Enter");
    await page.waitForTimeout(250);
    return;
  }
  if (isMobileProject(page)) {
    throw new Error("Department textbox was not found.");
  }
  await page.keyboard.press("Enter");
  await page.waitForTimeout(250);
}

async function fillDepartmentField(page: Page, value: string): Promise<void> {
  const textbox = page.getByRole("textbox", { name: "소속" });
  if (!isMobileProject(page)) {
    if ((await textbox.count()) > 0) {
      await textbox.click({ force: true });
      await page.waitForTimeout(100);
    }
    const coords = coordsFor(page);
    await fillAt(page, coords.departmentInputX, coords.departmentInputY, value);
    return;
  }

  if ((await textbox.count()) > 0) {
    await textbox.click({ force: true });
    await page.waitForTimeout(100);
    await replaceFocusedText(page, value);
    return;
  }
  if (isMobileProject(page)) {
    throw new Error("Department textbox was not found.");
  }
  const coords = coordsFor(page);
  await fillAt(page, coords.departmentInputX, coords.departmentInputY, value);
}

async function mockProfileApis(page: Page, state: ProfileState): Promise<void> {
  await page.route("**/api/v1/**", async (route: Route) => {
    const request = route.request();
    const requestUrl = new URL(request.url());
    const path = requestUrl.pathname;
    const method = request.method().toUpperCase();

    if (path.endsWith("/api/v1/user/me") && method === "GET") {
      const authHeader = request.headers().authorization ?? "";
      if (!authHeader.startsWith("Bearer ")) {
        await route.fulfill({
          status: 401,
          contentType: "application/json",
          body: JSON.stringify({ error: "unauthorized" }),
        });
        return;
      }
      state.getMeCount += 1;
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify({
          id: "e2e-user",
          email: "e2e@example.com",
          name: "E2E User",
          phone: "+821012341234",
          department: state.department,
          affiliationType: "employee",
          companyCode: "BARON",
          tenant: {
            id: "tenant-1",
            name: "Baron",
            slug: "baron",
            description: "E2E tenant",
          },
        }),
      });
      return;
    }

    if (path.endsWith("/api/v1/user/me") && method === "PUT") {
      const body = (request.postDataJSON() ?? {}) as Record<string, unknown>;
      state.putBodies.push(body);
      const nextDepartment = String(body.department ?? "").trim();
      if (nextDepartment !== "") {
        state.department = nextDepartment;
      }
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify({
          status: "success",
          updatedAt: "2026-02-24T00:00:00Z",
        }),
      });
      return;
    }

    if (path.endsWith("/api/v1/user/rp/linked")) {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify({ items: [] }),
      });
      return;
    }

    if (path.endsWith("/api/v1/audit/auth/timeline")) {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify({ items: [], next_cursor: "" }),
      });
      return;
    }

    if (path.endsWith("/api/v1/client-log")) {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify({ ok: true }),
      });
      return;
    }

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

async function openProfilePage(page: Page): Promise<void> {
  await page.goto("/ko/profile");
  await expect(page).toHaveURL(/\/ko\/profile$/);
  await enableFlutterAccessibility(page);
  await page.waitForTimeout(1200);
}

async function waitForInitialProfileLoad(state: ProfileState): Promise<void> {
  await expect.poll(() => state.getMeCount).toBeGreaterThan(0);
}

test.describe("UserFront WASM profile department editing", () => {
  test.skip(({ isMobile }) => isMobile, "Desktop only (hardcoded coordinates)");
  test.skip(
    ({ browserName }) => browserName === "webkit",
    "WebKit headless does not consistently open Flutter profile edit controls; Chromium and Firefox cover this flow.",
  );

  test.afterEach(async ({ page }) => {
    await page.unroute("**/api/v1/**");
  });

  test("소속 수정 후 명시 저장하면 저장 요청이 전송되고 새로고침 후 최신 값으로 재조회된다", async ({
    page,
  }) => {
    const state: ProfileState = {
      department: "QA",
      getMeCount: 0,
      putBodies: [],
    };
    await seedTokenLogin(page);
    await mockProfileApis(page, state);
    await openProfilePage(page);
    await waitForInitialProfileLoad(state);

    await openDepartmentEditor(page);
    await fillDepartmentField(page, "QA-Updated");
    await submitDepartmentEditor(page);

    await expect.poll(() => state.putBodies.length).toBe(1);
    expect(state.putBodies[0]?.department).toBe("QA-Updated");
    expect(state.department).toBe("QA-Updated");

    const getCountBeforeReload = state.getMeCount;
    await page.reload();
    await expect(page).toHaveURL(/\/ko\/profile$/);
    await expect
      .poll(() => state.getMeCount)
      .toBeGreaterThan(getCountBeforeReload);
  });

  test("소속 입력 후 즉시 새로고침해도 저장 요청이 중복 전송되지 않는다", async ({
    page,
  }) => {
    const state: ProfileState = {
      department: "QA",
      getMeCount: 0,
      putBodies: [],
    };
    await seedTokenLogin(page);
    await mockProfileApis(page, state);
    await openProfilePage(page);
    await waitForInitialProfileLoad(state);

    await openDepartmentEditor(page);
    await fillDepartmentField(page, "QA-Repro");

    await page.reload();
    await expect(page).toHaveURL(/\/ko\/profile$/);
    expect(state.putBodies.length).toBeLessThanOrEqual(1);
    if (state.putBodies.length > 0) {
      expect(state.putBodies[0]?.department).toBe("QA-Repro");
      expect(state.department).toBe("QA-Repro");
      return;
    }
    expect(state.department).toBe("QA");
  });

  test("소속에 기존값을 그대로 입력하고 포커스 아웃하면 저장 요청이 전송되지 않는다", async ({
    page,
  }) => {
    const state: ProfileState = {
      department: "QA",
      getMeCount: 0,
      putBodies: [],
    };
    await seedTokenLogin(page);
    await mockProfileApis(page, state);
    await openProfilePage(page);
    await waitForInitialProfileLoad(state);

    await openDepartmentEditor(page);
    await fillDepartmentField(page, "QA");
    await blurDepartmentEditor(page);

    expect(state.putBodies).toHaveLength(0);
  });

  test("소속을 빈 값으로 입력하면 저장 요청이 전송되지 않는다", async ({
    page,
  }) => {
    const state: ProfileState = {
      department: "QA",
      getMeCount: 0,
      putBodies: [],
    };
    await seedTokenLogin(page);
    await mockProfileApis(page, state);
    await openProfilePage(page);
    await waitForInitialProfileLoad(state);

    await openDepartmentEditor(page);
    await fillDepartmentField(page, "");
    await blurDepartmentEditor(page);

    expect(state.putBodies).toHaveLength(0);
    expect(state.department).toBe("QA");
  });

  test("소속을 저장한 뒤 새로고침 후 다시 저장해도 저장 요청이 누락되지 않는다", async ({
    page,
  }) => {
    const state: ProfileState = {
      department: "QA",
      getMeCount: 0,
      putBodies: [],
    };
    await seedTokenLogin(page);
    await mockProfileApis(page, state);
    await openProfilePage(page);
    await waitForInitialProfileLoad(state);

    await openDepartmentEditor(page);
    await fillDepartmentField(page, "QA-1");
    await submitDepartmentEditor(page);
    await expect.poll(() => state.putBodies.length).toBe(1);

    const getCountBeforeReload = state.getMeCount;
    await page.reload();
    await expect(page).toHaveURL(/\/ko\/profile$/);
    await enableFlutterAccessibility(page);
    await expect
      .poll(() => state.getMeCount)
      .toBeGreaterThan(getCountBeforeReload);
    await page.waitForTimeout(1200);

    await openDepartmentEditor(page);
    await fillDepartmentField(page, "QA-2");
    await submitDepartmentEditor(page);
    await expect.poll(() => state.putBodies.length).toBe(2);

    expect(state.putBodies[0]?.department).toBe("QA-1");
    expect(state.putBodies[1]?.department).toBe("QA-2");
    expect(state.department).toBe("QA-2");
  });
});
