// HTTP helpers — every request goes through these so checks/envelope handling
// is consistent across smoke and journey scripts.
import http from 'k6/http';
import { check, fail } from 'k6';
import { cfg, SUCCESS_CODE } from './config.js';

const JSON_HEADERS = { 'Content-Type': 'application/json', Accept: 'application/json' };

function url(path) {
  if (path.startsWith('http://') || path.startsWith('https://')) return path;
  return cfg.baseUrl + path;
}

function mergeHeaders(extra) {
  return Object.assign({}, JSON_HEADERS, extra || {});
}

export function withBearer(token, extra) {
  return mergeHeaders(Object.assign({ Authorization: `Bearer ${token}` }, extra || {}));
}

export function get(path, headers, params) {
  return http.get(url(path), Object.assign({ headers: mergeHeaders(headers) }, params || {}));
}

export function post(path, body, headers, params) {
  return http.post(
    url(path),
    body == null ? null : JSON.stringify(body),
    Object.assign({ headers: mergeHeaders(headers) }, params || {}),
  );
}

export function put(path, body, headers, params) {
  return http.put(
    url(path),
    body == null ? null : JSON.stringify(body),
    Object.assign({ headers: mergeHeaders(headers) }, params || {}),
  );
}

export function patch(path, body, headers, params) {
  return http.patch(
    url(path),
    body == null ? null : JSON.stringify(body),
    Object.assign({ headers: mergeHeaders(headers) }, params || {}),
  );
}

export function del(path, body, headers, params) {
  return http.del(
    url(path),
    body == null ? null : JSON.stringify(body),
    Object.assign({ headers: mergeHeaders(headers) }, params || {}),
  );
}

// safeJson parses res.body; returns null when body is empty/not JSON.
export function safeJson(res) {
  if (!res || !res.body || res.body.length === 0) return null;
  try {
    return JSON.parse(res.body);
  } catch (_) {
    return null;
  }
}

// checkEnvelope verifies the standard CloudEP success envelope.
//   { code: 102000, message: "SUCCESS", data: ... }
// Returns the parsed body so callers can keep chaining.
export function checkEnvelope(res, label, expectedStatus = 200, expectedCode = SUCCESS_CODE) {
  const body = safeJson(res);
  const ok = check(res, {
    [`${label}: status ${expectedStatus}`]: (r) => r.status === expectedStatus,
    [`${label}: code ${expectedCode}`]: () => body && body.code === expectedCode,
  });
  if (!ok) {
    // surface real payload so failures are actionable in `make k6-*` output
    fail(`${label} failed: status=${res.status} body=${res.body}`);
  }
  return body;
}

// checkError expects a non-2xx response and a business error code.
// expectedBiz is the 8-digit numeric SSCCCDDD code.
export function checkError(res, label, expectedStatus, expectedBiz) {
  const body = safeJson(res);
  const ok = check(res, {
    [`${label}: status ${expectedStatus}`]: (r) => r.status === expectedStatus,
    [`${label}: code ${expectedBiz}`]: () => body && body.code === expectedBiz,
  });
  if (!ok) {
    fail(`${label} expected error ${expectedBiz} got status=${res.status} body=${res.body}`);
  }
  return body;
}

// checkErrorOneOf accepts any of several (status, code) pairs. Use for
// endpoints whose error path depends on environment wiring (e.g.
// /auth/login may legitimately return either:
//   401 + 28501000 (invalid credentials, OAuth wired)
//   502 + 28802000 (zitadel request failed, OAuth not wired or password
//                   grant not enabled — true of modern ZITADEL v2 by default)
// pairs: array of [status, bizCode] tuples.
export function checkErrorOneOf(res, label, pairs) {
  const body = safeJson(res);
  const matched = pairs.find(
    ([s, c]) => res.status === s && body && body.code === c,
  );
  const labelStr = pairs.map(([s, c]) => `${s}+${c}`).join(' | ');
  const ok = check(res, {
    [`${label}: one of {${labelStr}}`]: () => Boolean(matched),
  });
  if (!ok) {
    fail(
      `${label} expected one of [${labelStr}] got status=${res.status} body=${res.body}`,
    );
  }
  return body;
}
