import { execSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { expect } from "@playwright/test";
import {
  show,
  listMachineUsers,
  getMachineUserToken,
  listOAuth2Clients,
  getAppHealth,
  createWorkspace,
  deploy,
} from "@tailor-platform/sdk/cli";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const APP_NAME = "template-app";
const STATIC_WEBSITE_NAME = "default-web";
const STATE_FILE = path.resolve(__dirname, ".e2e-state.json");
const frontendDir = path.resolve(__dirname, "..");
const backendDir = path.resolve(frontendDir, "../backend");

export interface E2eState {
  workspaceId: string;
  appUrl: string;
  clientId: string;
  graphqlEndpoint: string;
  token: string;
  frontendUrl: string;
  // false when CI provided the workspace, so teardown must not delete it
  // (other jobs share it).
  ownsWorkspace: boolean;
}

async function withCwd<T>(dir: string, fn: () => Promise<T>): Promise<T> {
  const original = process.cwd();
  process.chdir(dir);
  try {
    return await fn();
  } finally {
    process.chdir(original);
  }
}

async function globalSetup() {
  // In CI, the workspace is already deployed and seeded; resolve its connection
  // info only. Do NOT deploy or re-seed: the integration job shares it, and
  // re-seeding would wipe its data. Tests create their own rows.
  // Locally (no env var), create, deploy, and seed a workspace.
  const providedWorkspaceId = process.env.TAILOR_PLATFORM_WORKSPACE_ID;
  if (providedWorkspaceId) {
    const workspaceId = providedWorkspaceId;
    console.log(`[global-setup] reusing workspace ${workspaceId} (no deploy/seed)`);

    const app = await withCwd(backendDir, () => show({ workspaceId }));
    const appUrl = app.url;

    const machineUsers = await withCwd(backendDir, () => listMachineUsers({ workspaceId }));
    const tokens = await withCwd(backendDir, () =>
      getMachineUserToken({ name: machineUsers[0].name, workspaceId }),
    );

    const oauth2Clients = await withCwd(backendDir, () => listOAuth2Clients({ workspaceId }));
    const clientId = oauth2Clients[0].clientId;

    // CI sets E2E_BASE_URL before launching Playwright; the config reads it for
    // use.baseURL before this setup runs, so it cannot be set here.
    const frontendUrl = process.env.E2E_BASE_URL ?? "";
    const state: E2eState = {
      workspaceId,
      appUrl,
      clientId,
      graphqlEndpoint: `${appUrl}/query`,
      token: tokens.accessToken,
      frontendUrl,
      ownsWorkspace: false,
    };
    fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
    return;
  }

  const workspaceName = `${APP_NAME}-e2e-${Date.now()}`;
  const workspace = await withCwd(backendDir, () =>
    createWorkspace({
      name: workspaceName,
      region: "asia-northeast",
      organizationId: process.env.TAILOR_PLATFORM_ORGANIZATION_ID,
      folderId: process.env.TAILOR_PLATFORM_FOLDER_ID,
    }),
  );

  const workspaceId = workspace.id;

  await withCwd(backendDir, () => deploy({ workspaceId, yes: true }));

  await withCwd(backendDir, async () => {
    await expect
      .poll(
        async () => (await getAppHealth({ name: APP_NAME, workspaceId })).status.toLowerCase(),
        {
          intervals: [500, 1_000, 2_000, 3_000],
          timeout: 60_000,
          message: "App did not become healthy after deploy",
        },
      )
      .toBe("ok");
  });

  const app = await withCwd(backendDir, () => show({ workspaceId }));
  const appUrl = app.url;

  const machineUsers = await withCwd(backendDir, () => listMachineUsers({ workspaceId }));
  const tokens = await withCwd(backendDir, () =>
    getMachineUserToken({
      name: machineUsers[0].name,
      workspaceId,
    }),
  );

  const oauth2Clients = await withCwd(backendDir, () => listOAuth2Clients({ workspaceId }));
  const clientId = oauth2Clients[0].clientId;

  // Seed master data
  console.log("[global-setup] Seeding data...");
  execSync(`node seed/exec.mjs --skip-idp -m ${machineUsers[0].name}`, {
    cwd: backendDir,
    stdio: "inherit",
    env: { ...process.env, TAILOR_PLATFORM_WORKSPACE_ID: workspaceId },
  });

  // Build and deploy the frontend
  console.log("[global-setup] Building frontend...");
  execSync("pnpm run build", {
    cwd: frontendDir,
    stdio: "inherit",
    env: {
      ...process.env,
      VITE_TAILOR_APP_URL: appUrl,
      VITE_TAILOR_CLIENT_ID: clientId,
    },
  });

  console.log("[global-setup] Deploying frontend...");
  execSync(
    `pnpm tailor-sdk staticwebsite deploy --name ${STATIC_WEBSITE_NAME} --dir ./dist --workspace-id ${workspaceId}`,
    {
      cwd: frontendDir,
      stdio: "inherit",
    },
  );

  // Get the static website URL
  const swOutput = execSync(
    `pnpm tailor-sdk staticwebsite get ${STATIC_WEBSITE_NAME} --workspace-id ${workspaceId} --json`,
    {
      cwd: frontendDir,
      encoding: "utf-8",
    },
  );
  const swInfo = JSON.parse(swOutput) as { url: string };
  const frontendUrl = swInfo.url;

  console.log(`[global-setup] Frontend deployed at: ${frontendUrl}`);

  const state: E2eState = {
    workspaceId,
    appUrl,
    clientId,
    graphqlEndpoint: `${appUrl}/query`,
    token: tokens.accessToken,
    frontendUrl,
    ownsWorkspace: true,
  };

  fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));

  process.env.E2E_BASE_URL = frontendUrl;
}

export default globalSetup;
