import { expect } from "@playwright/test";
import { randomBytes } from "crypto";

import { WEBAPP_URL } from "@calcom/lib/constants";
import { prisma } from "@calcom/prisma";
import { generateSecret } from "@calcom/trpc/server/routers/viewer/oAuth/addClient.handler";

import { test } from "./lib/fixtures";

test.afterEach(async ({ users }) => {
  await users.deleteAll();
});

let client: {
  clientId: string;
  redirectUri: string;
  orginalSecret: string;
  name: string;
  clientSecret: string;
  logo: string | null;
};

test.describe("OAuth Provider", () => {
  test.beforeAll(async () => {
    client = await createTestCLient();
  });
  test("should create valid access toke & refresh token for user", async ({ page, users }) => {
    const user = await users.create({ username: "test user", name: "test user" });
    await user.apiLogin();

    await page.goto(
      `auth/oauth2/authorize?client_id=${client.clientId}&redirect_uri=${client.redirectUri}&response_type=code&scope=READ_PROFILE&state=1234`
    );
    await page.getByTestId("allow-button").click();

    await page.waitForFunction(() => {
      return window.location.href.startsWith("https://example.com");
    });

    const url = new URL(page.url());

    // authorization code that is returned to client with redirect uri
    const code = url.searchParams.get("code");

    // request token with authorization code
    const tokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/token`, {
      body: JSON.stringify({
        code,
        client_id: client.clientId,
        client_secret: client.orginalSecret,
        grant_type: "authorization_code",
        redirect_uri: client.redirectUri,
      }),
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
    });

    const tokenData = await tokenResponse.json();

    // test if token is valid
    const meResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/me`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${tokenData.access_token}`,
      },
    });

    const meData = await meResponse.json();

    // check if user access token is valid
    expect(meData.username.startsWith("test user")).toBe(true);

    // request new token with refresh token
    const refreshTokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/refreshToken`, {
      body: JSON.stringify({
        refresh_token: tokenData.refresh_token,
        client_id: client.clientId,
        client_secret: client.orginalSecret,
        grant_type: "refresh_token",
      }),
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
    });

    const refreshTokenData = await refreshTokenResponse.json();

    expect(refreshTokenData.access_token).not.toBe(tokenData.access_token);

    const validTokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/me`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${tokenData.access_token}`,
      },
    });

    expect(meData.username.startsWith("test user")).toBe(true);
  });

  test("should create valid access token & refresh token for team", async ({ page, users }) => {
    const user = await users.create({ username: "test user", name: "test user" }, { hasTeam: true });
    await user.apiLogin();

    await page.goto(
      `auth/oauth2/authorize?client_id=${client.clientId}&redirect_uri=${client.redirectUri}&response_type=code&scope=READ_PROFILE&state=1234`
    );

    await page.locator("#account-select").click();

    await page.locator("#react-select-2-option-1").click();

    await page.getByTestId("allow-button").click();

    await page.waitForFunction(() => {
      return window.location.href.startsWith("https://example.com");
    });

    const url = new URL(page.url());

    // authorization code that is returned to client with redirect uri
    const code = url.searchParams.get("code");

    // request token with authorization code
    const tokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/token`, {
      body: JSON.stringify({
        code,
        client_id: client.clientId,
        client_secret: client.orginalSecret,
        grant_type: "authorization_code",
        redirect_uri: client.redirectUri,
      }),
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
    });

    const tokenData = await tokenResponse.json();

    // test if token is valid
    const meResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/me`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${tokenData.access_token}`,
      },
    });

    const meData = await meResponse.json();

    // Check if team access token is valid
    expect(meData.username).toEqual(`user-id-${user.id}'s Team`);

    // request new token with refresh token
    const refreshTokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/refreshToken`, {
      body: JSON.stringify({
        refresh_token: tokenData.refresh_token,
        client_id: client.clientId,
        client_secret: client.orginalSecret,
        grant_type: "refresh_token",
      }),
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
    });

    const refreshTokenData = await refreshTokenResponse.json();

    expect(refreshTokenData.access_token).not.toBe(tokenData.access_token);

    const validTokenResponse = await fetch(`${WEBAPP_URL}/api/auth/oauth/me`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${tokenData.access_token}`,
      },
    });

    expect(meData.username).toEqual(`user-id-${user.id}'s Team`);
  });

  test("redirect not logged-in users to login page and after forward to authorization page", async ({
    page,
    users,
  }) => {
    const user = await users.create({ username: "test-user", name: "test user" });

    await page.goto(
      `auth/oauth2/authorize?client_id=${client.clientId}&redirect_uri=${client.redirectUri}&response_type=code&scope=READ_PROFILE&state=1234`
    );

    // check if user is redirected to login page
    await expect(page.getByRole("heading", { name: "Welcome back" })).toBeVisible();
    await page.locator("#email").fill(user.email);
    await page.locator("#password").fill(user.username || "");
    await page.locator('[type="submit"]').click();

    await page.waitForSelector("#account-select");

    await expect(page.getByText("test user")).toBeVisible();
  });
});

const createTestCLient = async () => {
  const [hashedSecret, secret] = generateSecret();
  const clientId = randomBytes(32).toString("hex");

  const client = await prisma.oAuthClient.create({
    data: {
      name: "Test Client",
      clientId,
      clientSecret: hashedSecret,
      redirectUri: "https://example.com",
    },
  });

  return { ...client, orginalSecret: secret };
};
