import { expect, test } from "@playwright/test";
import {
  type ClientRelation,
  type Consent,
  installDevApiMock,
  makeClient,
  seedAuth,
} from "./helpers/devfront-fixtures";
import { installDevFrontStaticRoutes } from "./helpers/static-devfront";

const editRelations = [
  {
    relation: "config_editor",
    subject: "User:playwright-user",
    subjectType: "User",
    subjectId: "playwright-user",
  },
  {
    relation: "admins",
    subject: "User:playwright-user",
    subjectType: "User",
    subjectId: "playwright-user",
  },
  {
    relation: "config_editor",
    subject: "User:admin-user",
    subjectType: "User",
    subjectId: "admin-user",
  },
  {
    relation: "config_editor",
    subject: "User:undefined",
    subjectType: "User",
    subjectId: "undefined",
  },
  {
    relation: "config_editor",
    subject: "User:",
    subjectType: "User",
    subjectId: "",
  },
] satisfies ClientRelation[];

test.describe("DevFront RP claim cache", () => {
  test.beforeEach(async ({ page }) => {
    await installDevFrontStaticRoutes(page);
    await seedAuth(page, "super_admin");
  });

  test("keeps saved RP claim value visible after saving", async ({ page }) => {
    const state = {
      clients: [
        makeClient("client-claims", {
          name: "Claims app",
          metadata: {
            id_token_claims: [
              {
                namespace: "rp_claims",
                key: "old_claim",
                value: "A",
                valueType: "text",
                readPermission: "admin_only",
                writePermission: "admin_only",
              },
            ],
          },
        }),
      ],
      consents: [] as Consent[],
      relations: {
        "client-claims": editRelations,
      },
      auditLogsByCursor: undefined,
      mockRole: "super_admin",
    };
    await installDevApiMock(page, state);

    await page.goto("http://devfront.test/clients/client-claims/settings");

    const claimKeyInput = page
      .getByPlaceholder(/e\.g\. locale|예: locale/i)
      .first();
    await expect(claimKeyInput).toHaveValue("old_claim");
    await expect(claimKeyInput).toBeEnabled();

    await claimKeyInput.fill("new_claim");
    await page.getByRole("button", { name: /^저장$|^Save$/i }).click();

    await expect
      .poll(
        () =>
          (
            state.clients[0]?.metadata?.id_token_claims as
              | Array<{ key?: string }>
              | undefined
          )?.[0]?.key,
      )
      .toBe("new_claim");
    await expect(claimKeyInput).toHaveValue("new_claim");
  });

  test("adds supported scopes and custom claim keys from the scope picker including offline_access", async ({
    page,
  }) => {
    const state = {
      clients: [
        makeClient("client-claims", {
          name: "Claims app",
          metadata: {
            structured_scopes: [
              {
                id: "scope-openid",
                name: "openid",
                description: "OIDC",
                mandatory: true,
              },
            ],
            id_token_claims: [
              {
                namespace: "rp_claims",
                key: "employee_code",
                value: "E001",
                valueType: "text",
                readPermission: "admin_only",
                writePermission: "admin_only",
              },
            ],
          },
        }),
      ],
      consents: [] as Consent[],
      relations: {
        "client-claims": editRelations,
      },
      auditLogsByCursor: undefined,
      mockRole: "super_admin",
    };
    await installDevApiMock(page, state);

    await page.goto("http://devfront.test/clients/client-claims/settings");
    await page
      .getByRole("button", { name: /스코프 추가|Scope 추가|Add Scope/i })
      .click();

    await expect(
      page.getByText("offline_access", { exact: true }),
    ).toBeVisible();
    await expect(
      page.getByRole("button", { name: /employee_code/ }),
    ).toBeVisible();

    await page.getByRole("button", { name: /employee_code/ }).click();
    await page.getByRole("button", { name: /^저장$|^Save$/i }).click();

    await expect
      .poll(() =>
        (
          state.clients[0]?.metadata?.structured_scopes as
            | Array<{ name?: string }>
            | undefined
        )?.some((scope) => scope.name === "employee_code"),
      )
      .toBe(true);
  });

  test("forces read permission on when write permission is enabled", async ({
    page,
  }) => {
    const state = {
      clients: [
        makeClient("client-claims", {
          name: "Claims app",
          metadata: {
            id_token_claims: [
              {
                namespace: "rp_claims",
                key: "locale",
                value: "ko",
                valueType: "text",
                readPermission: "admin_only",
                writePermission: "admin_only",
              },
            ],
          },
        }),
      ],
      consents: [] as Consent[],
      relations: {
        "client-claims": editRelations,
      },
      auditLogsByCursor: undefined,
      mockRole: "super_admin",
    };
    await installDevApiMock(page, state);

    await page.goto("http://devfront.test/clients/client-claims/settings");

    const readSwitch = page
      .getByRole("switch", { name: /사용자 읽기|Allow user read/i })
      .first();
    const writeSwitch = page
      .getByRole("switch", { name: /사용자 쓰기|Allow user write/i })
      .first();

    await expect(readSwitch).toHaveAttribute("aria-checked", "false");
    await expect(writeSwitch).toHaveAttribute("aria-checked", "false");
    await expect(readSwitch).toBeEnabled();
    await expect(writeSwitch).toBeEnabled();

    await writeSwitch.click();

    await expect(readSwitch).toHaveAttribute("aria-checked", "true");
    await expect(writeSwitch).toHaveAttribute("aria-checked", "true");

    await page.getByRole("button", { name: /^저장$|^Save$/i }).click();

    await expect
      .poll(
        () =>
          (
            state.clients[0]?.metadata?.id_token_claims as
              | Array<{
                  readPermission?: string;
                  writePermission?: string;
                }>
              | undefined
          )?.[0],
      )
      .toMatchObject({
        readPermission: "user_and_admin",
        writePermission: "user_and_admin",
      });
  });

  test("blocks saving an RP claim default value that does not match the selected value type", async ({
    page,
  }) => {
    const state = {
      clients: [
        makeClient("client-claims", {
          name: "Claims app",
          metadata: {
            id_token_claims: [
              {
                namespace: "rp_claims",
                key: "profile",
                value: "{}",
                valueType: "text",
                readPermission: "admin_only",
                writePermission: "admin_only",
              },
            ],
          },
        }),
      ],
      consents: [] as Consent[],
      relations: {
        "client-claims": editRelations,
      },
      auditLogsByCursor: undefined,
      mockRole: "super_admin",
    };
    await installDevApiMock(page, state);

    await page.goto("http://devfront.test/clients/client-claims/settings");

    await page
      .getByLabel(/Claim 값 타입|Claim value type/i)
      .first()
      .selectOption("object");
    await page
      .locator('textarea[placeholder="{\\"key\\": \\"value\\"}"]')
      .fill("not-json");

    await expect(
      page.getByRole("button", { name: /^저장$|^Save$/i }),
    ).toBeDisabled();

    expect(
      (
        state.clients[0]?.metadata?.id_token_claims as
          | Array<{ valueType?: string; value?: string }>
          | undefined
      )?.[0],
    ).toMatchObject({
      value: "{}",
      valueType: "text",
    });
  });

  test("saves a float RP claim default value and blocks decimal values for integer number claims", async ({
    page,
  }) => {
    const state = {
      clients: [
        makeClient("client-claims", {
          name: "Claims app",
          metadata: {
            id_token_claims: [
              {
                namespace: "rp_claims",
                key: "ratio",
                value: "0",
                valueType: "text",
                readPermission: "admin_only",
                writePermission: "admin_only",
              },
            ],
          },
        }),
      ],
      consents: [] as Consent[],
      relations: {
        "client-claims": editRelations,
      },
      auditLogsByCursor: undefined,
      mockRole: "super_admin",
    };
    await installDevApiMock(page, state);

    await page.goto("http://devfront.test/clients/client-claims/settings");

    await page
      .getByLabel(/Claim 값 타입|Claim value type/i)
      .first()
      .selectOption("float");
    await page
      .getByPlaceholder(/기본값을 입력하세요|Enter the default value/i)
      .first()
      .fill("3.14");
    await page.getByRole("button", { name: /^저장$|^Save$/i }).click();

    await expect
      .poll(
        () =>
          (
            state.clients[0]?.metadata?.id_token_claims as
              | Array<{ valueType?: string; value?: string }>
              | undefined
          )?.[0],
      )
      .toMatchObject({
        value: "3.14",
        valueType: "float",
      });

    const valueTypeSelect = page
      .getByLabel(/Claim 값 타입|Claim value type/i)
      .first();
    await expect(valueTypeSelect).toHaveValue("float");
    await expect(
      page.getByRole("button", { name: /^저장$|^Save$/i }),
    ).toBeEnabled();

    await valueTypeSelect.selectOption("number");
    await expect(valueTypeSelect).toHaveValue("number");
    const defaultValueInput = page
      .getByPlaceholder(/기본값을 입력하세요|Enter the default value/i)
      .first();
    await expect(defaultValueInput).toHaveAttribute("inputmode", "numeric");
    await defaultValueInput.fill("3.14");

    await expect(
      page.getByText(/Claim 기본값이 타입과 맞지 않습니다|does not match/i),
    ).toBeVisible();
    await expect(
      page.getByRole("button", { name: /^저장$|^Save$/i }),
    ).toBeDisabled();
  });
});
