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

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

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

function multiTenantUser(
  id: string,
  name: string,
  companyCode: string,
  joinedTenants: Array<ReturnType<typeof tenant>>,
) {
  return {
    ...user(id, name, companyCode),
    joinedTenants,
  };
}

function hanmacUser(id: string, name: string, companyCode: string) {
  return {
    ...user(id, name, companyCode),
    email: `${id}@hanmac.kr`,
  };
}

test("org chart uses svg viewBox zoom for sharp vector rendering", async ({
  page,
}) => {
  await page.route("**/api/v1/public/orgchart**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        sharedWith: "Playwright",
        tenants: [
          tenant("group", "HMAC Group", "hmac"),
          tenant("baron", "Baron", "baron", "group"),
          tenant("engineering", "Engineering", "engineering", "baron"),
          tenant("platform", "Platform", "platform", "engineering"),
        ],
        users: [
          user("u-group", "Group User", "hmac"),
          user("u-baron", "Baron User", "baron"),
          user("u-eng", "Engineering User", "engineering"),
          user("u-platform", "Platform User", "platform"),
        ],
      }),
    });
  });

  await page.goto("/chart?token=vector");
  await expect(page.getByRole("heading", { name: "조직 현황" })).toBeVisible();

  const viewport = page.locator('[data-testid="orgchart-viewport"]');
  const svg = page.locator('[data-testid="orgchart-vector-svg"]');
  await expect(svg).toBeVisible();
  await expect(svg.getByText("Engineering User 사원")).toBeVisible();

  const initialViewBox = await svg.getAttribute("viewBox");
  const transform = await page
    .locator('[data-testid="orgchart-canvas"]')
    .evaluate((element) => window.getComputedStyle(element).transform)
    .catch(() => "none");
  expect(transform).toBe("none");

  const box = await viewport.boundingBox();
  expect(box).not.toBeNull();
  if (!box) return;

  await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
  await page.mouse.wheel(0, -500);

  await expect
    .poll(async () => svg.getAttribute("viewBox"))
    .not.toBe(initialViewBox);
});

test("org chart filters by Hanmac family and company while excluding hanmac.kr accounts", async ({
  page,
}) => {
  await page.route("**/api/v1/public/orgchart**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        sharedWith: "Playwright",
        tenants: [
          tenant("group", "HMAC Group", "hmac"),
          tenant("baron", "Baron", "baron", "group", "COMPANY"),
          tenant("hanmac", "Hanmac", "hanmac", "group", "COMPANY"),
          tenant("engineering", "Engineering", "engineering", "baron"),
          tenant("sales", "Sales", "sales", "hanmac"),
        ],
        users: [
          user("u-group", "Group User", "hmac"),
          user("u-baron", "Baron User", "baron"),
          user("u-eng", "Engineering User", "engineering"),
          user("u-sales", "Sales User", "sales"),
          hanmacUser("hidden", "Hidden Hanmac User", "engineering"),
        ],
      }),
    });
  });

  await page.goto("/chart?token=family");

  await expect(page.getByRole("button", { name: "한맥가족" })).toBeVisible();
  await expect(page.getByRole("button", { name: "Baron" })).toBeVisible();
  await expect(page.getByRole("button", { name: "Hanmac" })).toBeVisible();
  await expect(page.getByRole("button", { name: "전체" })).toHaveCount(0);
  await expect(page.getByText("총 4명")).toBeVisible();

  const svg = page.locator('[data-testid="orgchart-vector-svg"]');
  await expect(svg.getByText(/Hidden Hanmac User/)).toHaveCount(0);
  await expect(svg.getByText("Engineering User 사원")).toBeVisible();
  await expect(svg.getByText("Sales User 사원")).toBeVisible();

  await page.getByRole("button", { name: "Baron" }).click();
  await expect(page.getByText("총 2명")).toBeVisible();
  await expect(page.getByText("총 4명")).toHaveCount(0);
  await expect(svg.getByText("Engineering User 사원")).toBeVisible();
  await expect(svg.getByText(/Sales User/)).toHaveCount(0);
});

test("org chart hides internal and private organizations in the status chart", async ({
  page,
}) => {
  await page.route("**/api/v1/public/orgchart**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        sharedWith: "Playwright",
        tenants: [
          tenant("group", "HMAC Group", "hmac"),
          tenant("visible", "Visible Org", "visible", "group", "ORGANIZATION"),
          tenant(
            "internal",
            "Internal Org",
            "internal",
            "group",
            "ORGANIZATION",
            {
              visibility: "internal",
            },
          ),
          tenant(
            "internal-child",
            "Internal Child",
            "internal-child",
            "internal",
            "ORGANIZATION",
          ),
          tenant("private", "Private Org", "private", "group", "ORGANIZATION", {
            visibility: "private",
          }),
        ],
        users: [
          user("u-visible", "Visible User", "visible"),
          user("u-internal", "Internal User", "internal"),
          user("u-private", "Private User", "private"),
        ],
      }),
    });
  });

  await page.goto("/chart?token=visibility&includeInternal=true");

  const svg = page.locator('[data-testid="orgchart-vector-svg"]');
  await expect(svg.getByText("Visible Org")).toBeVisible();
  await expect(svg.getByText("Visible User 사원")).toBeVisible();
  await expect(
    svg.getByText(/Internal Org|Internal Child|Private Org/),
  ).toHaveCount(0);
  await expect(svg.getByText(/Internal User|Private User/)).toHaveCount(0);
});

test("org chart balances large member groups with automatic member columns", async ({
  page,
}) => {
  const members = Array.from({ length: 10 }, (_, index) =>
    user(`u-member-${index + 1}`, `Member ${index + 1}`, "engineering"),
  );

  await page.route("**/api/v1/public/orgchart**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        sharedWith: "Playwright",
        tenants: [
          tenant("group", "HMAC Group", "hmac"),
          tenant("engineering", "Engineering", "engineering", "group"),
        ],
        users: members,
      }),
    });
  });

  await page.goto("/chart?token=member-columns");

  const engineeringNode = page.locator(
    '[data-testid="orgchart-node-engineering"]',
  );
  await expect(engineeringNode).toBeVisible();
  await expect(
    engineeringNode.locator('[data-member-columns="2"]'),
  ).toBeVisible();
});

test("org chart displays user names with short grade aliases and no job details", async ({
  page,
}) => {
  await page.route("**/api/v1/public/orgchart**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        sharedWith: "Playwright",
        tenants: [
          tenant("group", "HMAC Group", "hmac"),
          tenant("baron", "Baron", "baron", "group", "COMPANY"),
          tenant("engineering", "Engineering", "engineering", "baron"),
        ],
        users: [
          {
            ...user("u-eng", "Engineering User", "engineering"),
            jobTitle: "Platform Engineer",
            grade: "책임연구원",
            position: "팀장",
          },
        ],
      }),
    });
  });

  await page.goto("/chart?token=display-name");

  const svg = page.locator('[data-testid="orgchart-vector-svg"]');
  await expect(svg.getByText("Engineering User 책임")).toBeVisible();
  await expect(svg.getByText(/팀장|Platform Engineer/)).toHaveCount(0);
});

test("org chart orders top executive members by rank priority", async ({
  page,
}) => {
  const executiveUser = (id: string, name: string, grade: string) => ({
    ...user(id, name, "engineering"),
    grade,
  });

  await page.route("**/api/v1/public/orgchart**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        sharedWith: "Playwright",
        tenants: [
          tenant("group", "HMAC Group", "hmac"),
          tenant("engineering", "Engineering", "engineering", "group"),
        ],
        users: [
          executiveUser("u-vice-president", "Vice President", "부사장"),
          executiveUser("u-adviser", "Adviser", "고문"),
          executiveUser("u-vice-chair", "Vice Chair", "부회장"),
          executiveUser("u-president", "President", "사장"),
          executiveUser("u-chair", "Chair", "회장"),
          executiveUser("u-director", "Director", "전무"),
        ],
      }),
    });
  });

  await page.goto("/chart?token=rank-priority");

  const engineeringNode = page.locator(
    '[data-testid="orgchart-node-engineering"]',
  );
  await expect(engineeringNode).toBeVisible();

  const orderedMemberIds = await engineeringNode
    .locator('[data-testid^="orgchart-member-"]')
    .evaluateAll((elements) =>
      elements.map((element) => element.getAttribute("data-testid")),
    );

  expect(orderedMemberIds).toEqual([
    "orgchart-member-u-chair",
    "orgchart-member-u-president",
    "orgchart-member-u-vice-chair",
    "orgchart-member-u-adviser",
    "orgchart-member-u-vice-president",
    "orgchart-member-u-director",
  ]);
});

test("org chart only highlights flagged member cards", async ({ page }) => {
  await page.route("**/api/v1/public/orgchart**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        sharedWith: "Playwright",
        tenants: [
          tenant("group", "HMAC Group", "hmac"),
          tenant("engineering", "Engineering", "engineering", "group"),
        ],
        users: [
          user("u-normal", "Normal User", "engineering"),
          {
            ...user("u-owner", "Owner User", "engineering"),
            metadata: {
              additionalAppointments: [
                {
                  tenantSlug: "engineering",
                  isOwner: true,
                },
              ],
            },
          },
          {
            ...user("u-admin", "Admin User", "engineering"),
            metadata: {
              additionalAppointments: [
                {
                  tenantSlug: "engineering",
                  isAdmin: true,
                },
              ],
            },
          },
          {
            ...user("u-manager", "Manager User", "engineering"),
            metadata: {
              additionalAppointments: [
                {
                  tenantSlug: "engineering",
                  isManager: true,
                },
              ],
            },
          },
        ],
      }),
    });
  });

  await page.goto("/chart?token=highlighted-members");

  const engineeringNode = page.locator(
    '[data-testid="orgchart-node-engineering"]',
  );
  await expect(
    engineeringNode.locator('[data-testid="orgchart-member-u-normal"]'),
  ).toHaveAttribute("data-highlighted", "false");
  await expect(
    engineeringNode.locator('[data-testid="orgchart-member-u-owner"]'),
  ).toHaveAttribute("data-highlighted", "true");
  await expect(
    engineeringNode.locator('[data-testid="orgchart-member-u-admin"]'),
  ).toHaveAttribute("data-highlighted", "true");
  await expect(
    engineeringNode.locator('[data-testid="orgchart-member-u-manager"]'),
  ).toHaveAttribute("data-highlighted", "true");
});

test("org chart places multi-tenant users only on leaf memberships without duplicate rendering", async ({
  page,
}) => {
  const issuedAt = Math.floor(Date.now() / 1000);
  await page.addInitScript(
    ({ issuedAt: seededIssuedAt }) => {
      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: seededIssuedAt + 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");
    },
    { issuedAt },
  );

  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": "*" },
    });
  });

  const tenants = [
    tenant("group", "HMAC Group", "hmac"),
    tenant("baron", "Baron", "baron", "group", "COMPANY"),
    tenant("engineering", "Engineering", "engineering", "baron"),
    tenant("platform", "Platform", "platform", "engineering"),
  ];
  const [groupTenant, baronTenant, engineeringTenant, platformTenant] = tenants;

  await page.route("**/api/v1/admin/orgchart/snapshot**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        tenants,
        users: [
          multiTenantUser("u-shared", "Shared User", "baron", [
            groupTenant,
            baronTenant,
            engineeringTenant,
            platformTenant,
          ]),
        ],
        cache: { source: "redis", hit: true },
      }),
    });
  });

  await page.goto("/chart");

  await expect(page.getByText("총 1명")).toBeVisible();
  const svg = page.locator('[data-testid="orgchart-vector-svg"]');
  await expect(svg).toBeVisible();
  await expect(svg.getByText(/Shared User/)).toHaveCount(1);
  await expect(svg.getByText(/^1$/)).toHaveCount(4);
});

test("org chart allows a user in a hanmac-family descendant tenant", async ({
  page,
}) => {
  await page.addInitScript(() => {
    window.localStorage.setItem("playwright_auth_bypass", "1");
    window.localStorage.setItem("dev_tenant_id", "saman-id");
  });

  await page.route("**/api/v1/admin/orgchart/snapshot**", async (route) => {
    expect(route.request().headers()["x-tenant-id"]).toBe("saman-id");
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        tenants: [
          tenant(
            "hanmac-family-id",
            "한맥가족",
            "hanmac-family",
            "hanmac-family-id",
            "COMPANY_GROUP",
          ),
          tenant("saman-id", "삼안", "saman", "hanmac-family-id", "COMPANY"),
          tenant("saman-platform-id", "플랫폼팀", "saman-platform", "saman-id"),
        ],
        users: [
          {
            ...user("u-saman", "Saman Descendant User", "saman-platform"),
            tenantSlug: "saman",
            tenant: tenant(
              "saman-id",
              "삼안",
              "saman",
              "hanmac-family-id",
              "COMPANY",
            ),
            joinedTenants: [
              tenant(
                "saman-platform-id",
                "플랫폼팀",
                "saman-platform",
                "saman-id",
              ),
            ],
          },
        ],
        cache: { source: "redis", hit: true },
      }),
    });
  });

  await page.goto("/chart");

  await expect(
    page.getByText("조직도를 불러올 수 없거나 만료된 링크입니다."),
  ).toHaveCount(0);
  await expect(
    page.getByRole("button", { name: "조직: 한맥가족" }),
  ).toBeVisible();
  const svg = page.locator('[data-testid="orgchart-vector-svg"]');
  await expect(svg.getByText("한맥가족", { exact: true })).toBeVisible();
  await expect(svg.getByText("삼안", { exact: true })).toBeVisible();
  await expect(svg.getByText(/Saman Descendant User/)).toBeVisible();
});

test("org chart logs authenticated snapshot failures with actionable diagnostics", async ({
  page,
}) => {
  const consoleMessages: string[] = [];
  page.on("console", async (message) => {
    if (message.type() !== "error") return;
    const values = await Promise.all(
      message.args().map((arg) => arg.jsonValue().catch(() => "")),
    );
    consoleMessages.push(JSON.stringify(values));
  });

  await page.addInitScript(() => {
    window.localStorage.setItem("playwright_auth_bypass", "1");
    window.localStorage.setItem("dev_tenant_id", "saman-id");
  });

  await page.route("**/api/v1/admin/orgchart/snapshot**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      status: 503,
      body: JSON.stringify({
        code: "service_unavailable",
        error: "tenant root traversal failed",
      }),
    });
  });

  await page.goto("/chart");

  await expect(
    page.getByText(
      "조직도를 불러올 수 없습니다. 로그인 상태와 조직 권한을 확인해 주세요.",
    ),
  ).toBeVisible();
  await expect(
    page.getByText("조직도를 불러올 수 없거나 만료된 링크입니다."),
  ).toHaveCount(0);
  await expect
    .poll(() =>
      consoleMessages.some(
        (message) =>
          message.includes("[orgfront] Org chart load failed") &&
          message.includes("service_unavailable") &&
          message.includes("saman-id") &&
          message.includes("/v1/admin/orgchart/snapshot"),
      ),
    )
    .toBe(true);
});

test("org chart places GPDTDC representative users on visible leaf appointments", async ({
  page,
}) => {
  await page.route("**/api/v1/public/orgchart**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        sharedWith: "Playwright",
        tenants: [
          tenant("group", "HMAC Group", "hmac"),
          tenant("gpdtdc", "GPDTDC", "gpdtdc", "group", "COMPANY"),
          tenant("tdc", "기술개발센터", "tdc", "gpdtdc", "ORGANIZATION"),
          tenant("tdc-leaf", "기술개발센터 1팀", "tdc-leaf", "tdc"),
          tenant(
            "internal-planning",
            "내부 구성 조직",
            "internal-planning",
            "group",
            "ORGANIZATION",
            { visibility: "internal" },
          ),
          tenant(
            "internal-leaf",
            "내부 구성 하위 조직",
            "internal-leaf",
            "gpdtdc",
            "USER_GROUP",
            { visibility: "internal" },
          ),
        ],
        users: [
          {
            ...user("u-gpdtdc-leaf", "GPDTDC Leaf User", "gpdtdc"),
            tenantSlug: "gpdtdc",
            companyCode: undefined,
            metadata: {
              additionalAppointments: [
                {
                  tenantSlug: "internal-planning",
                  isPrimary: true,
                },
                {
                  tenantSlug: "tdc-leaf",
                  isPrimary: false,
                  grade: "책임",
                  position: "팀장",
                },
              ],
            },
          },
          {
            ...user("u-hidden-only", "Hidden Only User", "gpdtdc"),
            tenantSlug: "gpdtdc",
            companyCode: undefined,
            metadata: {
              additionalAppointments: [
                {
                  tenantSlug: "internal-leaf",
                  isPrimary: true,
                },
              ],
            },
          },
        ],
      }),
    });
  });

  await page.goto("/chart?token=gpdtdc-leaf");

  await expect(page.getByText("총 1명")).toBeVisible();
  const svg = page.locator('[data-testid="orgchart-vector-svg"]');
  await expect(svg).toBeVisible();
  await expect(svg.getByText("내부 구성 조직")).toHaveCount(0);
  await expect(svg.getByText("내부 구성 하위 조직")).toHaveCount(0);
  await expect(svg.getByText(/Hidden Only User/)).toHaveCount(0);
  await expect(
    page
      .locator('[data-testid="orgchart-node-gpdtdc"]')
      .getByText(/GPDTDC Leaf User/),
  ).toHaveCount(0);
  await expect(
    page
      .locator('[data-testid="orgchart-node-tdc-leaf"]')
      .getByText("GPDTDC Leaf User 책임"),
  ).toBeVisible();
});

test("org chart counts multi-leaf tenant users once in ancestor totals", async ({
  page,
}) => {
  await page.route("**/api/v1/public/orgchart**", async (route) => {
    const tenants = [
      tenant("group", "HMAC Group", "hmac"),
      tenant("baron", "Baron", "baron", "group", "COMPANY"),
      tenant("engineering", "Engineering", "engineering", "baron"),
      tenant("platform", "Platform", "platform", "engineering"),
      tenant("security", "Security", "security", "engineering"),
    ];
    const [groupTenant, baronTenant, engineeringTenant, platformTenant] =
      tenants;
    const securityTenant = tenants[4];

    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        sharedWith: "Playwright",
        tenants,
        users: [
          multiTenantUser("u-shared", "Shared User", "baron", [
            groupTenant,
            baronTenant,
            engineeringTenant,
            platformTenant,
            securityTenant,
          ]),
        ],
      }),
    });
  });

  await page.goto("/chart?token=multi-leaf-count");

  await expect(page.getByText("총 1명")).toBeVisible();
  const svg = page.locator('[data-testid="orgchart-vector-svg"]');
  await expect(svg).toBeVisible();
  await expect(svg.getByText(/Shared User/)).toHaveCount(2);
  await expect(svg.getByText(/^1$/)).toHaveCount(5);
});

test("org chart hides system global tenant members", async ({ page }) => {
  await page.route("**/api/v1/public/orgchart**", async (route) => {
    await route.fulfill({
      contentType: "application/json",
      body: JSON.stringify({
        sharedWith: "Playwright",
        tenants: [
          tenant("system", "시스템 전역", "system", undefined, "SYSTEM"),
          tenant("group", "HMAC Group", "hmac"),
          tenant("baron", "Baron", "baron", "group", "COMPANY"),
        ],
        users: [
          user("u-global", "Global Admin", "system"),
          {
            ...multiTenantUser("u-system-admin", "System Admin", "system", [
              tenant("baron", "Baron", "baron", "group", "COMPANY"),
            ]),
            role: "super_admin",
          },
          user("u-baron", "Baron User", "baron"),
        ],
      }),
    });
  });

  await page.goto("/chart?token=hide-system-global");

  await expect(page.getByText("총 1명")).toBeVisible();
  const svg = page.locator('[data-testid="orgchart-vector-svg"]');
  await expect(svg).toBeVisible();
  await expect(svg.getByText(/시스템 전역/)).toHaveCount(0);
  await expect(svg.getByText(/Global Admin/)).toHaveCount(0);
  await expect(svg.getByText(/System Admin/)).toHaveCount(0);
  await expect(svg.getByText("Baron User 사원")).toBeVisible();
});
