"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.waitForClusterHealthyViaApi = exports.ensureTasklistV2Enabled = exports.ensureClusterViaApi = exports.createClusterViaApi = exports.deleteClusterViaApi = void 0;
const test_1 = require("@playwright/test");
const sleep_1 = require("./sleep");
const PLAN_TYPE_ID = '574cde5f-54ae-43c1-851a-e6fb4f03423a';
const CHANNEL_ID = 'cf355219-cc2d-4266-83e1-2430998ddf30';
const GCP_REGION_ID = 'a6fadf1d-b2a0-44ab-b301-2f9aaf6681dc';
const AWS_REGION_ID = '30fcdd4f-05d7-1c7a-54df-8d9defbd250f';
const GENERATION_MAP = {
    '8.10': '7df47bbb-8665-4598-b12b-f53338d4aff0',
    '8.9': '32478fdf-dbea-47b9-abe2-e9cbfc6c4324',
    '8.8': '7ff71498-dc2d-486f-9baa-e46b7b503c89',
    '8.7': 'd7ab8eef-9dd2-4b61-8142-fb1e7eea6710',
};
function getConsoleBaseUrl() {
    if (process.env.IS_PROD === 'true') {
        return 'https://console.cloud.camunda.io';
    }
    // PLAYWRIGHT_BASE_URL is the UI hostname (console.ultrawombat.com); the actual
    // API backend lives at console.cloud.ultrawombat.com — map accordingly.
    const uiUrl = process.env.PLAYWRIGHT_BASE_URL ?? 'https://console.ultrawombat.com';
    return uiUrl
        .replace('console.ultrawombat.com', 'console.cloud.ultrawombat.com')
        .replace('console.camunda.io', 'console.cloud.camunda.io');
}
function getOrgId() {
    const orgId = process.env.ORG_ID;
    if (!orgId)
        throw new Error('ORG_ID env var is not set');
    return orgId;
}
// Auth0 SPA JS stores tokens in localStorage under keys starting with @@auth0spajs@@
// cacheLocation: 'localstorage' is configured in the Console frontend auth store
async function getUserToken(page) {
    const token = await page.evaluate(() => {
        for (const key of Object.keys(localStorage)) {
            if (key.startsWith('@@auth0spajs@@')) {
                try {
                    const data = JSON.parse(localStorage.getItem(key) ?? '');
                    if (data?.body?.access_token)
                        return data.body.access_token;
                }
                catch {
                    // skip malformed entries
                }
            }
        }
        return null;
    });
    if (!token) {
        throw new Error('Could not extract access token from browser localStorage — ' +
            'ensure loginWithRetry has been called before any cluster API operations');
    }
    return token;
}
async function findClusterByName(page, clusterName) {
    const token = await getUserToken(page);
    const orgId = getOrgId();
    const response = await page.request.get(`${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters`, { headers: { Authorization: `Bearer ${token}` } });
    if (response.status() !== 200)
        return undefined;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    let data;
    try {
        data = await response.json();
    }
    catch {
        return undefined;
    }
    const clusters = Array.isArray(data)
        ? data
        : data.clusters ?? [];
    return clusters.find((c) => c.name === clusterName);
}
async function deleteClusterViaApi(page, clusterName) {
    const cluster = await findClusterByName(page, clusterName);
    if (!cluster)
        return;
    const token = await getUserToken(page);
    const orgId = getOrgId();
    await page.request.delete(`${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters/${cluster.uuid}`, { headers: { Authorization: `Bearer ${token}` } });
    await (0, sleep_1.sleep)(5000);
}
exports.deleteClusterViaApi = deleteClusterViaApi;
async function resolveGenerationId() {
    // MINOR_VERSION is always a plain "8.x" string in every trigger flow
    // (run-tests-saas extracts it from the human-readable version input;
    // run-tests-smoke-int sets it straight from c8Version). CLUSTER_VERSION is
    // NOT reliable for this lookup: the camunda/camunda-triggered PR dispatch
    // flow (playwright_saas_pr_trigger_monorepo.yml -> run-tests-smoke-int)
    // deliberately puts a random per-PR cluster *generation name* there
    // (openssl rand -hex 6), a pre-existing convention unrelated to this
    // GENERATION_MAP lookup. Fall back to it only for callers that don't set
    // MINOR_VERSION.
    const rawVersion = process.env.MINOR_VERSION || process.env.CLUSTER_VERSION || '';
    const minorVersion = rawVersion.match(/^(\d+\.\d+)/)?.[1] ?? rawVersion;
    const generationId = GENERATION_MAP[minorVersion];
    if (generationId)
        return generationId;
    throw new Error(`No generation UUID found for MINOR_VERSION/CLUSTER_VERSION="${rawVersion}" ` +
        `(resolved minor: "${minorVersion}"). ` +
        `Known versions: ${Object.keys(GENERATION_MAP).join(', ')}`);
}
async function createClusterViaApi(page, clusterName, region = 'GCP') {
    const token = await getUserToken(page);
    const orgId = getOrgId();
    // Some trigger flows (e.g. playwright_saas_pr_trigger_monorepo.yml) create a
    // throwaway cluster generation per run, built from that specific commit's
    // component images, and pass its real UUID through as CLUSTER_GENERATION_ID.
    // That must win over the static GENERATION_MAP lookup -- falling back to a
    // fixed generation would silently test the wrong (stale) images instead of
    // failing loudly.
    const generationId = process.env.CLUSTER_GENERATION_ID || (await resolveGenerationId());
    const k8sContextId = region.toUpperCase() === 'AWS' ? AWS_REGION_ID : GCP_REGION_ID;
    const createResponse = await page.request.post(`${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters`, {
        headers: { Authorization: `Bearer ${token}` },
        data: {
            name: clusterName,
            planTypeId: PLAN_TYPE_ID,
            channelId: CHANNEL_ID,
            generationId,
            k8sContextId,
            autoUpdate: true,
            stageLabel: 'dev',
            // Upgrading a cluster to Tasklist v2 is a one-way, irreversible switch,
            // and v2 cannot run job-worker-based user tasks, draft variables, or
            // public start forms -- all of which the @tasklistV1-tagged suites
            // exercise. Explicitly request v1 rather than relying on whatever the
            // Console API defaults new clusters to, since a cluster that ends up
            // v2-enabled can never be brought back to v1 for the rest of its life.
            tasklistV2Enabled: false,
        },
    });
    if (createResponse.status() !== 200 && createResponse.status() !== 201) {
        throw new Error(`Failed to create cluster "${clusterName}": ` +
            `HTTP ${createResponse.status()} - ${await createResponse.text()}`);
    }
    const result = await createResponse.json();
    const clusterId = result.clusterId ??
        result.uuid ??
        result.data?.cluster?.uuid ??
        result.data?.clusterId;
    if (!clusterId) {
        throw new Error(`Cluster creation response missing UUID: ${JSON.stringify(result)}`);
    }
    return clusterId;
}
exports.createClusterViaApi = createClusterViaApi;
async function ensureClusterViaApi(page, clusterName, region = 'GCP') {
    // Versions with more than one Tasklist-mode Playwright project (e.g. 8.8,
    // 8.9's chromium-v1/chromium-v2) run test-setup.spec.ts once per project,
    // concurrently, against the same org -- and both target a cluster with
    // this same name. Deleting-then-recreating unconditionally here would race
    // with the sibling project's setup: whichever one runs its delete second
    // rips out the cluster the other project already started using, silently
    // orphaning any process instances/jobs created on it in the meantime.
    //
    // Each PR/nightly run gets a brand-new, empty org (see the
    // create-organization/delete-organization jobs), so within a single run a
    // cluster with this name can only mean one thing: the sibling project's
    // setup already created it. Adopt it instead of destroying it -- whichever
    // project's setup gets here first "wins" and creates it, the other one
    // just reuses it.
    const existing = await findClusterByName(page, clusterName);
    if (existing) {
        return existing.uuid;
    }
    return createClusterViaApi(page, clusterName, region);
}
exports.ensureClusterViaApi = ensureClusterViaApi;
async function ensureTasklistV2Enabled(page, clusterUuid) {
    // Console's cluster settings UI frames this as a one-time "Upgrade to
    // Tasklist v2" action performed after creation (new clusters start on v1),
    // not something reliably controllable via the create payload alone. Call
    // the same update endpoint that action uses, explicitly, so a dedicated
    // cluster genuinely ends up v2-enabled regardless of the create call's own
    // default. This is irreversible -- only use it on a cluster dedicated to
    // tests that specifically require a real v2-upgraded backend, never on a
    // cluster shared with anything that needs v1 (e.g. job-worker-based user
    // tasks, draft variables, public start forms).
    const token = await getUserToken(page);
    const orgId = getOrgId();
    const response = await page.request.patch(`${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters/${clusterUuid}`, {
        headers: { Authorization: `Bearer ${token}` },
        data: { tasklistV2Enabled: true },
    });
    if (response.status() !== 200 && response.status() !== 204) {
        throw new Error(`Failed to upgrade cluster "${clusterUuid}" to Tasklist v2: ` +
            `HTTP ${response.status()} - ${await response.text()}`);
    }
}
exports.ensureTasklistV2Enabled = ensureTasklistV2Enabled;
async function waitForClusterHealthyViaApi(page, clusterUuid, timeoutMs = 1500000) {
    const orgId = getOrgId();
    const url = `${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters/${clusterUuid}`;
    await (0, test_1.expect)(async () => {
        const token = await getUserToken(page);
        const response = await page.request.get(url, {
            headers: { Authorization: `Bearer ${token}` },
        });
        (0, test_1.expect)(response.status()).toBe(200);
        const data = await response.json();
        const cluster = data.data ?? data;
        (0, test_1.expect)(cluster.status?.ready).toBe('Healthy');
    }).toPass({ timeout: timeoutMs, intervals: [30000, 60000] });
    // The cluster reports "Healthy" as soon as its infrastructure is up, but
    // orchestration-cluster authorization and job/task distribution can take
    // a bit longer to finish propagating on a just-created cluster (the same
    // class of delay 8.8's runProcessInstance() already retries around for
    // migrations). Give it a short buffer before handing control back, so the
    // first real test action isn't the thing racing that propagation window.
    await (0, sleep_1.sleep)(60000);
}
exports.waitForClusterHealthyViaApi = waitForClusterHealthyViaApi;
