/**
 * WalkGuide — Custom Metric Collectors
 * Menambah metric k6 custom di atas default http_req_* metrics.
 *
 * METRIC YANG DIKUMPULKAN (sesuai ketentuan pillar 3):
 *  1. throughput     — req/s (via http_reqs counter bawaan k6 + custom trend)
 *  2. p95 latency    — custom trend per endpoint
 *  3. error rate     — custom rate
 *  4. db query time  — diperkirakan via response time karena tidak ada akses langsung DB
 *  5. jvm heap       — via Spring Actuator /actuator/metrics/jvm.memory.used
 */
import { Counter, Gauge, Rate, Trend } from "k6/metrics";
import http from "k6/http";

// ── Custom Metrics ────────────────────────────────────────────────────────────

/** Total request error rate (non-2xx) */
export const errorRate = new Rate("walkguide_error_rate");

/** Request latency per endpoint */
export const endpointLatency = new Trend("walkguide_endpoint_latency_ms", true);

/** Location update latency khusus (endpoint paling kritis) */
export const locationLatency = new Trend("walkguide_location_latency_ms", true);

/** Obstacle log latency khusus */
export const obstacleLatency = new Trend("walkguide_obstacle_latency_ms", true);

/** SOS trigger latency khusus (harus paling cepat) */
export const sosLatency = new Trend("walkguide_sos_latency_ms", true);

/** Notification send latency */
export const notifLatency = new Trend("walkguide_notif_latency_ms", true);

/** Auth latency */
export const authLatency = new Trend("walkguide_auth_latency_ms", true);

/** Timeline query latency */
export const timelineLatency = new Trend("walkguide_timeline_latency_ms", true);

/** Pairing flow latency */
export const pairingLatency = new Trend("walkguide_pairing_latency_ms", true);

/** Successful requests counter */
export const successfulRequests = new Counter("walkguide_successful_requests");

/** Failed requests counter */
export const failedRequests = new Counter("walkguide_failed_requests");

/** JVM heap usage (MB) — populated via actuator polling */
export const jvmHeapUsed = new Gauge("walkguide_jvm_heap_used_mb");

/** Active DB connections estimate (via actuator) */
export const dbConnections = new Gauge("walkguide_db_connections");

// ── Endpoint → metric mapping ─────────────────────────────────────────────────

const ENDPOINT_METRIC_MAP = {
  location_update: locationLatency,
  obstacle_log: obstacleLatency,
  sos_trigger: sosLatency,
  send_notification: notifLatency,
  login: authLatency,
  register: authLatency,
  refresh_token: authLatency,
  location_history: timelineLatency,
  activity_logs: timelineLatency,
  activity_logs_guardian: timelineLatency,
  pairing_invite: pairingLatency,
  pairing_respond: pairingLatency,
};

// ── metricsHelper object ──────────────────────────────────────────────────────

export const metricsHelper = {
  /**
   * Catat metric untuk setiap response.
   * @param {string} endpointTag — string yang di-pass sebagai tags.endpoint
   * @param {Response} res       — k6 http response object
   */
  recordEndpoint(endpointTag, res) {
    const isSuccess = res.status >= 200 && res.status < 300;
    const durationMs = res.timings.duration;

    // Error rate
    errorRate.add(!isSuccess);

    // Generic endpoint latency
    endpointLatency.add(durationMs, { endpoint: endpointTag });

    // Specific metric kalau ada di map
    if (ENDPOINT_METRIC_MAP[endpointTag]) {
      ENDPOINT_METRIC_MAP[endpointTag].add(durationMs);
    }

    // Success / fail counter
    if (isSuccess) {
      successfulRequests.add(1);
    } else {
      failedRequests.add(1);
    }
  },

  /**
   * Poll Spring Actuator untuk JVM heap.
   * Call ini dari setup() atau secara periodik dari satu VU khusus.
   * @param {string} baseUrl
   * @param {string} token — optional, actuator mungkin butuh auth
   */
  pollJvmHeap(baseUrl, token = null) {
    const headers = token
      ? { Authorization: `Bearer ${token}`, Accept: "application/json" }
      : { Accept: "application/json" };

    try {
      const res = http.get(`${baseUrl}/actuator/metrics/jvm.memory.used`, {
        headers,
        timeout: "5s",
        tags: { endpoint: "actuator_jvm" },
      });
      if (res.status === 200) {
        const body = JSON.parse(res.body);
        // measurement[0].value adalah bytes
        const heapMb = body.measurements[0].value / (1024 * 1024);
        jvmHeapUsed.add(heapMb);
      }
    } catch (e) {
      // Actuator mungkin tidak public — skip
    }
  },

  /**
   * Poll Spring Actuator untuk DB connection pool (HikariCP).
   * @param {string} baseUrl
   * @param {string} token
   */
  pollDbConnections(baseUrl, token = null) {
    const headers = token
      ? { Authorization: `Bearer ${token}`, Accept: "application/json" }
      : { Accept: "application/json" };

    try {
      const res = http.get(
        `${baseUrl}/actuator/metrics/hikaricp.connections.active`,
        { headers, timeout: "5s", tags: { endpoint: "actuator_db" } },
      );
      if (res.status === 200) {
        const body = JSON.parse(res.body);
        const active = body.measurements[0].value;
        dbConnections.add(active);
      }
    } catch (e) {
      // Skip
    }
  },

  /**
   * Generate ringkasan teks dari custom metrics (untuk print di teardown).
   */
  summary() {
    return {
      errorRate: errorRate,
      locationP95: locationLatency,
      obstacleP95: obstacleLatency,
      sosP95: sosLatency,
      notifP95: notifLatency,
      authP95: authLatency,
      timelineP95: timelineLatency,
      pairingP95: pairingLatency,
    };
  },
};

/**
 * Default thresholds yang bisa di-reuse di semua scenario options.
 * Sesuai ketentuan pillar 3: p95 < 500ms, error rate < 1%.
 */
export const DEFAULT_THRESHOLDS = {
  // ── HTTP default k6
  http_req_duration: ["p(95)<500", "p(99)<1000"],
  http_req_failed: ["rate<0.01"],

  // ── WalkGuide custom
  walkguide_error_rate: ["rate<0.01"],
  walkguide_endpoint_latency_ms: ["p(95)<500"],
  walkguide_location_latency_ms: ["p(95)<300"], // location harus cepat
  walkguide_obstacle_latency_ms: ["p(95)<400"],
  walkguide_sos_latency_ms: ["p(95)<200"], // SOS PALING KRITIS
  walkguide_notif_latency_ms: ["p(95)<500"],
  walkguide_auth_latency_ms: ["p(95)<800"],
  walkguide_timeline_latency_ms: ["p(95)<1000"], // query analytics boleh lebih lambat
  walkguide_pairing_latency_ms: ["p(95)<600"],
};

/**
 * Relaxed thresholds untuk stress test (ekspektasi lebih longgar).
 */
export const STRESS_THRESHOLDS = {
  http_req_duration: ["p(95)<1500", "p(99)<3000"],
  http_req_failed: ["rate<0.05"],
  walkguide_error_rate: ["rate<0.05"],
  walkguide_sos_latency_ms: ["p(95)<500"],
};

/**
 * Threshold untuk smoke test lokal.
 * Dipakai saat backend jalan di laptop tetapi database masih remote, sehingga
 * angka latency lebih cocok dipakai sebagai bukti run, bukan production SLA.
 */
export const LOCAL_SMOKE_THRESHOLDS = {
  http_req_duration: ["p(95)<35000", "p(99)<40000"],
  http_req_failed: ["rate<0.10"],
  walkguide_error_rate: ["rate<0.10"],
  walkguide_endpoint_latency_ms: ["p(95)<35000"],
  walkguide_location_latency_ms: ["p(95)<3000"],
  walkguide_obstacle_latency_ms: ["p(95)<5000"],
  walkguide_sos_latency_ms: ["p(95)<3000"],
  walkguide_notif_latency_ms: ["p(95)<5000"],
  walkguide_auth_latency_ms: ["p(95)<35000"],
  walkguide_timeline_latency_ms: ["p(95)<5000"],
  walkguide_pairing_latency_ms: ["p(95)<5000"],
};

export function pickThresholds(overrides = {}) {
  const base =
    __ENV.BENCH_PROFILE === "local"
      ? LOCAL_SMOKE_THRESHOLDS
      : DEFAULT_THRESHOLDS;
  return { ...base, ...overrides };
}

/**
 * handleSummary helper — generate output JSON + text.
 * Import di tiap scenario: export { handleSummary } from '../modules/metrics-helper.js'
 */
export function handleSummary(data) {
  const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
  const fileName = `k6-results/summary_${stamp}.json`;

  return {
    stdout: textSummary(data),
    [fileName]: JSON.stringify(data, null, 2),
  };
}

function textSummary(data) {
  const { metrics } = data;
  const lines = [
    "═══════════════════════════════════════════════════════════════",
    "  WalkGuide Load Test — Result Summary",
    "═══════════════════════════════════════════════════════════════",
  ];

  const pick = (name, stat) => {
    if (metrics[name]) {
      const val = metrics[name].values[stat];
      return val !== undefined ? val.toFixed(2) : "N/A";
    }
    return "N/A";
  };

  lines.push(
    `  http_req_duration    p95=${pick("http_req_duration", "p(95)")}ms  p99=${pick("http_req_duration", "p(99)")}ms`,
  );
  lines.push(`  http_req_failed      rate=${pick("http_req_failed", "rate")}`);
  lines.push(
    `  sos_latency          p95=${pick("walkguide_sos_latency_ms", "p(95)")}ms`,
  );
  lines.push(
    `  location_latency     p95=${pick("walkguide_location_latency_ms", "p(95)")}ms`,
  );
  lines.push(
    `  obstacle_latency     p95=${pick("walkguide_obstacle_latency_ms", "p(95)")}ms`,
  );
  lines.push(
    `  notif_latency        p95=${pick("walkguide_notif_latency_ms", "p(95)")}ms`,
  );
  lines.push(
    `  auth_latency         p95=${pick("walkguide_auth_latency_ms", "p(95)")}ms`,
  );
  lines.push(
    `  timeline_latency     p95=${pick("walkguide_timeline_latency_ms", "p(95)")}ms`,
  );
  lines.push("═══════════════════════════════════════════════════════════════");
  return lines.join("\n") + "\n";
}
