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

const shareToken = "playwright";

function withShareToken(path: string) {
  return path.includes("?")
    ? `${path}&token=${shareToken}`
    : `${path}?token=${shareToken}`;
}

type TenantFixture = {
  id: string;
  type: string;
  name: string;
  slug: string;
  description: string;
  status: string;
  parentId?: string;
  memberCount: number;
  createdAt: string;
  updatedAt: string;
};

function tenant(
  id: string,
  type: string,
  name: string,
  slug: string,
  parentId?: string,
): TenantFixture {
  return {
    id,
    type,
    name,
    slug,
    description: "",
    status: "active",
    parentId,
    memberCount: 1,
    createdAt: "2026-04-01T00:00:00.000Z",
    updatedAt: "2026-04-01T00:00:00.000Z",
  };
}

function user(
  id: string,
  name: string,
  tenantSlug: string,
  overrides: Record<string, unknown> = {},
) {
  return {
    id,
    email: `${id}@example.com`,
    name,
    role: "user",
    status: "active",
    tenantSlug,
    companyCode: tenantSlug,
    grade: "사원",
    createdAt: "2026-04-01T00:00:00.000Z",
    updatedAt: "2026-04-01T00:00:00.000Z",
    ...overrides,
  };
}

async function seedOrgfrontAuth(page: Parameters<typeof test>[0]["page"]) {
  const nowInSeconds = Math.floor(Date.now() / 1000);

  await page.addInitScript(
    ({ issuedAt }) => {
      const mockOidcUser = {
        id_token: "playwright-id-token",
        session_state: "playwright-session",
        access_token: "playwright-access-token",
        refresh_token: "playwright-refresh-token",
        token_type: "Bearer",
        scope: "openid profile email",
        profile: {
          sub: "playwright-user",
          email: "playwright@example.com",
          name: "Playwright User",
          role: "tenant_admin",
        },
        expires_at: issuedAt + 3600,
      };
      const storageKeys = [
        "user:http://localhost:5000/oidc:orgfront",
        "user:http://localhost:5000/oidc/:orgfront",
        "user:http://localhost:5000/oidc:devfront",
        "user:http://localhost:5000/oidc/:devfront",
        "user:http://172.16.9.189:5000/oidc:orgfront",
        "user:http://172.16.9.189:5000/oidc/:orgfront",
        "oidc.user:http://localhost:5000/oidc:orgfront",
        "oidc.user:http://localhost:5000/oidc/:orgfront",
        "oidc.user:http://localhost:5000/oidc:devfront",
        "oidc.user:http://localhost:5000/oidc/:devfront",
        "oidc.user:http://172.16.9.189:5000/oidc:orgfront",
        "oidc.user:http://172.16.9.189:5000/oidc/:orgfront",
      ];
      for (const key of storageKeys) {
        window.localStorage.setItem(key, JSON.stringify(mockOidcUser));
      }
      window.localStorage.setItem("playwright_auth_bypass", "1");
      window.localStorage.setItem("dev_tenant_id", "group-hmac");
    },
    { issuedAt: nowInSeconds },
  );

  await page.route("**/oidc/**", async (route) => {
    const url = route.request().url();
    if (url.includes(".well-known/openid-configuration")) {
      await route.fulfill({
        json: {
          issuer: "http://localhost:5000/oidc",
          authorization_endpoint: "http://localhost:5000/oidc/auth",
          token_endpoint: "http://localhost:5000/oidc/token",
          jwks_uri: "http://localhost:5000/oidc/jwks",
          userinfo_endpoint: "http://localhost:5000/oidc/userinfo",
          end_session_endpoint: "http://localhost:5000/oidc/session/end",
        },
        headers: { "Access-Control-Allow-Origin": "*" },
      });
      return;
    }

    if (url.includes("/jwks")) {
      await route.fulfill({
        json: { keys: [] },
        headers: { "Access-Control-Allow-Origin": "*" },
      });
      return;
    }

    await route.fulfill({
      status: 200,
      body: "ok",
      headers: { "Access-Control-Allow-Origin": "*" },
    });
  });
}

async function installOrgPickerApiMock(
  page: Parameters<typeof test>[0]["page"],
) {
  const tenants = [
    tenant("group-hmac", "COMPANY_GROUP", "HMAC Group", "hmac"),
    tenant("company-baron", "COMPANY", "Baron", "baron", "group-hmac"),
    tenant("company-hanmac", "COMPANY", "Hanmac", "hanmac", "group-hmac"),
    tenant("dept-center", "USER_GROUP", "센터", "center", "company-baron"),
    tenant(
      "team-tech-plan",
      "USER_GROUP",
      "기술기획",
      "tech-plan",
      "dept-center",
    ),
    tenant("team-bcmf", "USER_GROUP", "bCMf", "bcmf", "dept-center"),
    tenant("team-pm", "USER_GROUP", "PM", "pm", "dept-center"),
    tenant(
      "dept-eng",
      "USER_GROUP",
      "Engineering",
      "engineering",
      "company-baron",
    ),
    tenant("team-platform", "USER_GROUP", "Platform", "platform", "dept-eng"),
    tenant("dept-sales", "USER_GROUP", "Sales", "sales", "company-hanmac"),
  ];
  const users = [
    user("user-root", "Group User", "hmac"),
    user("user-baron", "Baron User", "baron"),
    user("user-eng", "Engineering User", "engineering"),
    user("user-platform", "Platform User", "platform", {
      metadata: { employeeNumber: "EMP-9001", skill: "Kubernetes" },
      jobTitle: "Platform Engineer",
      grade: "책임",
      position: "팀장",
    }),
    user("user-sales", "Sales User", "sales"),
  ];

  await page.route("**/api/v1/admin/tenants**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        items: tenants,
        total: tenants.length,
        limit: 10000,
        offset: 0,
      }),
    });
  });

  await page.route("**/api/v1/admin/users**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        items: users,
        total: users.length,
        limit: 5000,
        offset: 0,
      }),
    });
  });
}

test.beforeEach(async ({ page }) => {
  await seedOrgfrontAuth(page);
  await installOrgPickerApiMock(page);
});

test("developer navigation exposes chart, picker, and embed preview", async ({
  page,
}) => {
  await page.goto(withShareToken("/chart"));

  await expect(page.getByRole("link", { name: "조직도" })).toBeVisible();
  await expect(page.getByRole("link", { name: "조직 선택기" })).toBeVisible();
  await expect(page.getByRole("link", { name: "임베딩 검증" })).toBeVisible();

  await page.getByRole("link", { name: "임베딩 검증" }).click();
  await expect(
    page.getByRole("heading", { name: "임베딩 검증" }),
  ).toBeVisible();
  await expect(
    page
      .frameLocator("iframe")
      .getByTestId("org-picker-search-section")
      .getByText("하위 선택"),
  ).toBeVisible();
});

test("picker menu lets developers switch selection mode and selectable type", async ({
  page,
}) => {
  await page.goto(withShareToken("/picker"));

  await expect(page.getByLabel("선택 모드")).toHaveValue("multiple");
  await expect(page.getByLabel("선택 대상")).toHaveValue("both");

  await page.getByLabel("선택 모드").selectOption("single");
  await expect(page.frameLocator("iframe").getByText("하위 선택")).toHaveCount(
    0,
  );

  await page.getByLabel("선택 대상").selectOption("tenant");
  const picker = page.frameLocator("iframe");
  await expect(
    picker.getByRole("button", { name: "Engineering User" }),
  ).toHaveCount(0);
  await expect(
    picker.getByRole("button", { name: "Engineering", exact: true }),
  ).toBeVisible();
});

test("picker defaults to the hanmac-family company-group when no tenant id is supplied", async ({
  page,
}) => {
  await page.unroute("**/api/v1/admin/tenants**");
  await page.unroute("**/api/v1/admin/users**");

  const tenants = [
    tenant("wrong-group", "COMPANY_GROUP", "Wrong Group", "wrong-group"),
    tenant(
      "wrong-company",
      "COMPANY",
      "Wrong Company",
      "wrong-company",
      "wrong-group",
    ),
    tenant("hanmac-family-id", "COMPANY_GROUP", "한맥가족", "hanmac-family"),
    tenant("saman-id", "COMPANY", "삼안", "saman", "hanmac-family-id"),
  ];

  await page.route("**/api/v1/admin/tenants**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        items: tenants,
        total: tenants.length,
        limit: 10000,
        offset: 0,
      }),
    });
  });
  await page.route("**/api/v1/admin/users**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        items: [],
        total: 0,
        limit: 5000,
        offset: 0,
      }),
    });
  });

  await page.goto(withShareToken("/picker"));

  const picker = page.frameLocator("iframe");
  await expect(picker.getByText("한맥가족", { exact: true })).toBeVisible();
  await expect(picker.getByText("삼안", { exact: true })).toBeVisible();
  await expect(picker.getByText("Wrong Group", { exact: true })).toHaveCount(0);
});

test("embed preview picker orders hanmac-family tenants by the shared policy", async ({
  page,
}) => {
  await page.unroute("**/api/v1/admin/tenants**");
  await page.unroute("**/api/v1/admin/users**");

  const tenants = [
    tenant("hanmac-family-id", "COMPANY_GROUP", "한맥가족", "hanmac-family"),
    tenant(
      "baron-group-id",
      "COMPANY_GROUP",
      "바론그룹",
      "baron-group",
      "hanmac-family-id",
    ),
    tenant("hanmac-id", "COMPANY", "한맥기술", "hanmac", "hanmac-family-id"),
    tenant("saman-id", "COMPANY", "삼안", "saman", "hanmac-family-id"),
    tenant(
      "gpdtdc-id",
      "ORGANIZATION",
      "총괄기획&기술개발센터",
      "gpdtdc",
      "hanmac-family-id",
    ),
  ];

  await page.route("**/api/v1/admin/tenants**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        items: tenants,
        total: tenants.length,
        limit: 10000,
        offset: 0,
      }),
    });
  });
  await page.route("**/api/v1/admin/users**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        items: [],
        total: 0,
        limit: 5000,
        offset: 0,
      }),
    });
  });

  await page.goto(withShareToken("/embed-preview?select=tenant"));

  await expect(
    page.frameLocator("iframe").getByTestId("org-picker-node-name-tenant"),
  ).toHaveText([
    "한맥가족",
    "총괄기획&기술개발센터",
    "삼안",
    "한맥기술",
    "바론그룹",
  ]);
});

test("picker displays user names with grade and optional position", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed/picker?mode=single&select=user"));

  await expect(
    page.getByRole("button", {
      name: "Platform User 책임(팀장)",
    }),
  ).toBeVisible();
});

test("embed preview menu updates the iframe picker source", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview"));

  await expect(page.getByLabel("선택 모드")).toHaveValue("multiple");
  await expect(page.getByLabel("선택 대상")).toHaveValue("both");
  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "mode=multiple",
  );
  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "select=both",
  );
  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "includeDescendants=true",
  );
  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "showDescendantToggle=true",
  );
  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "width=400",
  );
  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "height=600",
  );
  await expect(page.getByTestId("embed-preview-frame-shell")).toHaveCSS(
    "width",
    "400px",
  );

  await page.getByLabel("선택 모드").selectOption("single");
  await page.getByLabel("선택 대상").selectOption("user");

  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "mode=single",
  );
  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "select=user",
  );
  await expect(page.getByTestId("embed-preview-src")).not.toContainText(
    "includeDescendants",
  );
  await expect(page.getByTestId("embed-preview-src")).not.toContainText(
    "showDescendantToggle",
  );
  await expect(page.frameLocator("iframe").getByText("하위 선택")).toHaveCount(
    0,
  );
  await expect(
    page.frameLocator("iframe").getByRole("button", {
      name: "Engineering User 사원 user-eng@example.com",
    }),
  ).toBeVisible();
});

test("embed preview passes tenant slug and custom dimensions through the picker url", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview"));

  await page.getByLabel("tenant slug").fill("baron");
  await page.getByLabel("임베딩 너비").fill("520");
  await page.getByLabel("임베딩 높이").fill("480");

  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "tenantSlug=baron",
  );
  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "width=520",
  );
  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "height=480",
  );
  await expect(page.getByTestId("embed-preview-frame-shell")).toHaveCSS(
    "width",
    "520px",
  );

  const picker = page.frameLocator("iframe");
  await expect(picker.getByText("Engineering User")).toBeVisible();
  await expect(picker.getByText("Sales User")).toHaveCount(0);
});

test("embed picker scopes the tree by tenant slug, hides users for tenant selection, and keeps direct members before child tenants", async ({
  page,
}) => {
  await page.goto(
    withShareToken("/embed-preview?tenantSlug=baron&select=tenant"),
  );

  await expect(page.getByLabel("tenant slug")).toHaveValue("baron");
  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "tenantSlug=baron",
  );

  const picker = page.frameLocator("iframe");
  await expect(picker.getByText("Baron", { exact: true })).toBeVisible();
  await expect(picker.getByText("Hanmac", { exact: true })).toHaveCount(0);
  await expect(picker.getByText("Sales User")).toHaveCount(0);
  await expect(picker.getByText("Baron User")).toHaveCount(0);

  await page.getByLabel("선택 대상").selectOption("both");
  await expect(picker.getByText("Baron User")).toBeVisible();
  const memberBox = await picker.getByText("Baron User").boundingBox();
  const childTenantBox = await picker
    .getByText("센터", { exact: true })
    .boundingBox();
  expect(memberBox).not.toBeNull();
  expect(childTenantBox).not.toBeNull();
  expect(memberBox?.y ?? 0).toBeLessThan(childTenantBox?.y ?? 0);
});

test("embed picker keeps the lightweight search controls inside the picker section at the default embed width", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview"));

  const picker = page.frameLocator("iframe");
  const searchSection = picker.getByTestId("org-picker-search-section");
  await expect(searchSection).toBeVisible();
  await expect(searchSection.getByLabel("company 필터")).toHaveCount(0);
  await expect(searchSection.getByText("선택 결과")).toHaveCount(0);

  const searchBox = await searchSection
    .getByLabel("조직/구성원 검색")
    .boundingBox();
  const descendantToggle = await searchSection
    .getByTestId("org-picker-descendant-toggle")
    .boundingBox();
  const sectionBox = await searchSection.boundingBox();
  expect(searchBox).not.toBeNull();
  expect(descendantToggle).not.toBeNull();
  expect(sectionBox).not.toBeNull();
  expect(
    Math.abs((searchBox?.y ?? 0) - (descendantToggle?.y ?? 0)),
  ).toBeLessThanOrEqual(8);
  expect(sectionBox?.height ?? 0).toBeLessThanOrEqual(72);
});

test("embed picker keeps only the lightweight picker surface scrollable", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview"));

  const picker = page.frameLocator("iframe");
  await expect(
    picker.getByRole("heading", { name: "조직 선택기" }),
  ).toHaveCount(0);
  await expect(picker.getByTestId("org-picker-search-section")).toBeVisible();
  await expect(picker.getByTestId("org-picker-tree-scroll")).toBeVisible();

  await expect
    .poll(async () =>
      picker.locator("body").evaluate((element) => {
        const style = window.getComputedStyle(element);
        return `${style.overflowX}/${style.overflowY}`;
      }),
    )
    .toBe("hidden/hidden");

  await expect
    .poll(async () =>
      picker
        .getByTestId("org-picker-tree-scroll")
        .evaluate((element) => window.getComputedStyle(element).overflowY),
    )
    .toBe("auto");

  const rootRowHeight = await picker
    .getByRole("button", { name: "HMAC Group 접기" })
    .locator("xpath=..")
    .evaluate((element) => element.getBoundingClientRect().height);
  expect(rootRowHeight).toBeLessThanOrEqual(30);
});

test("embed preview can hide the descendant selection switch", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview?mode=multiple&select=both"));

  await expect(page.getByLabel("하위 선택 스위치 표시")).toBeChecked();
  await page.getByLabel("하위 선택 스위치 표시").uncheck();

  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "includeDescendants=true",
  );
  await expect(page.getByTestId("embed-preview-src")).toContainText(
    "showDescendantToggle=false",
  );
  await expect(page.frameLocator("iframe").getByText("하위 선택")).toHaveCount(
    0,
  );
});

test("embed picker renders compact tree rows with member emails", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview?mode=single&select=user"));

  const picker = page.frameLocator("iframe");
  await expect(picker.getByText("user-eng@example.com")).toBeVisible();
  await expect(
    picker.getByRole("button", { name: "Engineering User 구성원" }),
  ).toHaveCount(0);
  await expect(
    picker.getByRole("button", {
      name: "Engineering User 사원 user-eng@example.com",
    }),
  ).toBeVisible();
});

test("embed picker filters organizations and users by id, name, and metadata", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview?mode=multiple&select=both"));

  const picker = page.frameLocator("iframe");
  const search = picker.getByLabel("조직/구성원 검색");
  await expect(picker.getByTestId("org-picker-search-section")).toBeVisible();
  await expect(search).toBeVisible();

  await search.fill("user-platform");
  await expect(picker.getByText("Platform User")).toBeVisible();
  await expect(picker.getByText("Sales User")).toHaveCount(0);

  await search.fill("EMP-9001");
  await expect(picker.getByText("Platform User")).toBeVisible();
  await expect(picker.getByText("Engineering User")).toHaveCount(0);

  await search.fill("Sales");
  await expect(picker.getByText("Sales", { exact: true })).toBeVisible();
  await expect(picker.getByText("Sales User")).toBeVisible();
  await expect(picker.getByText("Platform User")).toHaveCount(0);
});

test("embed picker search does not keep unmatched descendants under a matching organization", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview?mode=multiple&select=both"));

  const picker = page.frameLocator("iframe");
  await picker.getByLabel("조직/구성원 검색").fill("센");

  await expect(picker.getByText("센터", { exact: true })).toBeVisible();
  await expect(picker.getByText("기술기획", { exact: true })).toHaveCount(0);
  await expect(picker.getByText("bCMf", { exact: true })).toHaveCount(0);
  await expect(picker.getByText("PM", { exact: true })).toHaveCount(0);
});

test("embed picker posts a single user selection with type, id, and name", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview?mode=single&select=user"));

  const picker = page.frameLocator("iframe");
  await picker
    .getByRole("button", { name: "Engineering User 사원 user-eng@example.com" })
    .click();
  await picker.getByRole("button", { name: "선택 완료" }).click();

  const output = page.getByTestId("embed-preview-output");
  await expect(output).toContainText('"type": "user"');
  await expect(output).toContainText('"id": "user-eng"');
  await expect(output).toContainText('"name": "Engineering User 사원"');
  await expect(output).not.toContainText("tenantId");
});

test("embed picker single selection counts only the selected node without descendants", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview?mode=single&select=both"));

  const picker = page.frameLocator("iframe");
  await picker
    .getByRole("button", { name: "Engineering", exact: true })
    .click();
  await expect(picker.getByText("1개 항목 선택됨")).toBeVisible();
  await expect(picker.getByText("3개 항목 선택됨")).toHaveCount(0);
  await expect(picker.getByText("4개 항목 선택됨")).toHaveCount(0);

  await picker.getByRole("button", { name: "선택 완료" }).click();
  const output = page.getByTestId("embed-preview-output");
  await expect(output).toContainText('"id": "dept-eng"');
  await expect(output).not.toContainText('"id": "user-eng"');
  await expect(output).not.toContainText('"id": "team-platform"');
  await expect(output).not.toContainText('"id": "user-platform"');
});

test("embed picker highlights a single selected item without tree connectors", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview?mode=single&select=both"));

  const picker = page.frameLocator("iframe");
  await expect(
    picker.getByRole("button", { name: "Engineering", exact: true }),
  ).toBeVisible();
  await expect(picker.getByTestId("org-picker-tree-connector")).toHaveCount(0);

  const selected = picker.getByRole("button", {
    name: "Engineering",
    exact: true,
  });
  await selected.click();
  await expect(selected).toHaveAttribute("aria-pressed", "true");
  await expect(selected).toHaveAttribute("data-selected", "true");
});

test("embed picker renders tenant names with the dedicated tenant text color", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview?mode=single&select=both"));

  const picker = page.frameLocator("iframe");
  const tenantName = picker.getByTestId("org-picker-node-name-tenant").first();
  await expect(tenantName).toBeVisible();
  await expect
    .poll(() =>
      tenantName.evaluate((element) => window.getComputedStyle(element).color),
    )
    .toBe("rgb(10, 33, 20)");
});

test("embed picker includes descendants by default and can disable descendant inclusion", async ({
  page,
}) => {
  await page.goto(withShareToken("/embed-preview?mode=multiple&select=both"));

  let picker = page.frameLocator("iframe");
  await expect(
    picker.getByTestId("org-picker-search-section").getByText("하위 선택"),
  ).toBeVisible();
  await picker.getByLabel("Engineering 선택").check();
  await expect(picker.getByLabel("Platform 선택")).toBeChecked();
  await expect(
    picker.getByLabel("Platform User 책임(팀장) 선택"),
  ).toBeChecked();
  await picker.getByRole("button", { name: "선택 완료" }).click();

  let output = page.getByTestId("embed-preview-output");
  await expect(output).toContainText('"id": "dept-eng"');
  await expect(output).toContainText('"id": "team-platform"');
  await expect(output).toContainText('"id": "user-platform"');

  await page.goto(
    withShareToken(
      "/embed-preview?mode=multiple&select=both&includeDescendants=false",
    ),
  );
  picker = page.frameLocator("iframe");
  await picker.getByLabel("Engineering 선택").check();
  await expect(picker.getByLabel("Platform 선택")).not.toBeChecked();
  await expect(
    picker.getByLabel("Platform User 책임(팀장) 선택"),
  ).not.toBeChecked();
  await picker.getByRole("button", { name: "선택 완료" }).click();

  output = page.getByTestId("embed-preview-output");
  await expect(output).toContainText('"id": "dept-eng"');
  await expect(output).not.toContainText('"id": "team-platform"');
  await expect(output).not.toContainText('"id": "user-platform"');
});
