import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';

// ── Configuration ────────────────────────────────────────────────────────────
const BASE_URL = __ENV.DARKWATCH_BASE_URL || 'http://localhost:3000';
const TEST_EMAIL = __ENV.TEST_EMAIL || 'loadtest@darkwatch.shieldai';
const TEST_PASSWORD = __ENV.TEST_PASSWORD || 'LoadTest2026!';
const DURATION = __ENV.DURATION || '300s'; // 5 minutes
const TARGET_RPS = parseInt(__ENV.TARGET_RPS || '500', 10);
const CREDENTIAL_POOL_SIZE = parseInt(__ENV.CREDENTIAL_POOL_SIZE || '100', 10);

// P99 latency thresholds (ms)
const THRESHOLDS = {
  login: parseInt(__ENV.LOGIN_P99_MS || '200', 10),
  logout: parseInt(__ENV.LOGOUT_P99_MS || '100', 10),
  refresh: parseInt(__ENV.REFRESH_P99_MS || '150', 10),
};

// ── Custom Metrics ───────────────────────────────────────────────────────────
const loginLatency = new Trend('login_p99');
const logoutLatency = new Trend('logout_p99');
const refreshLatency = new Trend('refresh_p99');

const loginSuccess = new Rate('login_success');
const logoutSuccess = new Rate('logout_success');
const refreshSuccess = new Rate('refresh_success');

// ── Helpers ──────────────────────────────────────────────────────────────────
function uuidv4() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    const r = (Math.random() * 16) | 0;
    const v = c === 'x' ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

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

// ── P1#3: Fixed credential pool (reuses pre-seeded users, not unique per call) ──
const credentialPool = Array.from({ length: CREDENTIAL_POOL_SIZE }, (_, i) => ({
  email: `${TEST_EMAIL.replace('@', `_${i}@`)}`,
  password: TEST_PASSWORD,
}));

// Fake token pool fallback — used when setup() warmup is skipped or fails
const tokenPool = Array.from({ length: CREDENTIAL_POOL_SIZE }, () => ({
  accessToken: uuidv4(),
  refreshToken: uuidv4(),
}));

// ── Setup: Seed real tokens via login warmup ──────────────────────────────────
export function setup() {
  const creds = credentialPool[0];
  const payload = JSON.stringify({ email: creds.email, password: creds.password });
  const res = http.post(`${BASE_URL}/auth/login`, payload, { headers: authHeaders });

  try {
    const json = JSON.parse(res.body);
    const accessToken = json.access_token || json.token || json.data?.access_token;
    const refreshToken = json.refresh_token || json.data?.refresh_token;

    if (accessToken && refreshToken) {
      return {
        accessToken,
        refreshToken,
        warmupSuccess: true,
      };
    }
  } catch {
    // fall through to fake tokens
  }

  console.warn(`[warmup] Login returned ${res.status} — standalone scenarios will use fake tokens (expect 401/403)`);
  return {
    accessToken: tokenPool[0].accessToken,
    refreshToken: tokenPool[0].refreshToken,
    warmupSuccess: false,
  };
}

// ── Scenario: Login (POST /auth/login) ──────────────────────────────────────
function testLogin(email, password) {
  const creds = email
    ? { email, password }
    : credentialPool[Math.floor(Math.random() * credentialPool.length)];

  const payload = JSON.stringify({
    email: creds.email,
    password: creds.password,
  });

  const res = http.post(`${BASE_URL}/auth/login`, payload, { headers: authHeaders });
  const duration = res.timings.duration;
  loginLatency.add(duration);

  const success = res.status === 200 || res.status === 201;
  loginSuccess.add(success);

  check(res, {
    'login: status 200 or 201': (r) => r.status === 200 || r.status === 201,
    'login: has access_token': (r) => {
      try {
        const json = JSON.parse(r.body);
        return !!json.access_token || !!json.token || !!json.data?.access_token;
      } catch {
        return false;
      }
    },
    `login: P99 < ${THRESHOLDS.login}ms`: (r) => duration < THRESHOLDS.login,
  });

  try {
    const json = JSON.parse(res.body);
    return {
      accessToken: json.access_token || json.token || json.data?.access_token || uuidv4(),
      refreshToken: json.refresh_token || json.data?.refresh_token || uuidv4(),
      userId: json.user?.id || json.data?.user?.id || uuidv4(),
    };
  } catch {
    return {
      accessToken: uuidv4(),
      refreshToken: uuidv4(),
      userId: uuidv4(),
    };
  }
}

// ── Scenario: Refresh (POST /auth/refresh) ──────────────────────────────────
function testRefresh(refreshToken) {
  const token = refreshToken || tokenPool[Math.floor(Math.random() * tokenPool.length)].refreshToken;

  const payload = JSON.stringify({
    refresh_token: token,
  });

  const res = http.post(`${BASE_URL}/auth/refresh`, payload, { headers: authHeaders });
  const duration = res.timings.duration;
  refreshLatency.add(duration);

  const success = res.status === 200;
  refreshSuccess.add(success);

  check(res, {
    'refresh: status 200': (r) => r.status === 200,
    'refresh: has new access_token': (r) => {
      try {
        const json = JSON.parse(r.body);
        return !!json.access_token || !!json.token || !!json.data?.access_token;
      } catch {
        return false;
      }
    },
    `refresh: P99 < ${THRESHOLDS.refresh}ms`: (r) => duration < THRESHOLDS.refresh,
  });

  try {
    const json = JSON.parse(res.body);
    return {
      accessToken: json.access_token || json.token || json.data?.access_token || uuidv4(),
      refreshToken: json.refresh_token || json.data?.refresh_token || token,
    };
  } catch {
    return {
      accessToken: uuidv4(),
      refreshToken: token,
    };
  }
}

// ── P2#4: Scenario: Logout (POST /auth/logout) — refresh_token in body, Bearer in header ──
function testLogout(accessToken, refreshToken) {
  const poolEntry = tokenPool[Math.floor(Math.random() * tokenPool.length)];
  const token = accessToken || poolEntry.accessToken;
  const refreshTkn = refreshToken || poolEntry.refreshToken;

  const payload = JSON.stringify({
    refresh_token: refreshTkn,
  });

  const res = http.post(`${BASE_URL}/auth/logout`, payload, {
    headers: {
      ...authHeaders,
      Authorization: `Bearer ${token}`,
    },
  });
  const duration = res.timings.duration;
  logoutLatency.add(duration);

  const success = res.status === 200 || res.status === 204;
  logoutSuccess.add(success);

  check(res, {
    'logout: status 200 or 204': (r) => r.status === 200 || r.status === 204,
    `logout: P99 < ${THRESHOLDS.logout}ms`: (r) => duration < THRESHOLDS.logout,
  });
}

// ── P1#1 + P1#2: Options with all scenarios merged (each iteration = 1 HTTP call) ──
export const options = {
  scenarios: {
    sustained_load: {
      executor: 'constant-arrival-rate',
      duration: DURATION,
      rate: TARGET_RPS,
      preAllocatedVUs: 20,
      maxVUs: 100,
      startTime: '0s',
      exec: 'mixedWorkload',
      tags: { scenario: 'sustained_load' },
    },
    login_only: {
      executor: 'constant-arrival-rate',
      duration: DURATION,
      rate: TARGET_RPS,
      preAllocatedVUs: 20,
      maxVUs: 100,
      exec: 'loginOnly',
      startTime: '0s',
      tags: { scenario: 'login_only' },
    },
    logout_only: {
      executor: 'constant-arrival-rate',
      duration: DURATION,
      rate: TARGET_RPS,
      preAllocatedVUs: 20,
      maxVUs: 100,
      exec: 'logoutOnly',
      startTime: '0s',
      tags: { scenario: 'logout_only' },
    },
    refresh_only: {
      executor: 'constant-arrival-rate',
      duration: DURATION,
      rate: TARGET_RPS,
      preAllocatedVUs: 20,
      maxVUs: 100,
      exec: 'refreshOnly',
      startTime: '0s',
      tags: { scenario: 'refresh_only' },
    },
  },
  thresholds: {
    `login_p99`: [`p(99)<${THRESHOLDS.login}`],
    `logout_p99`: [`p(99)<${THRESHOLDS.logout}`],
    `refresh_p99`: [`p(99)<${THRESHOLDS.refresh}`],
    `login_success`: ['rate>0.95'],
    `logout_success`: ['rate>0.95'],
    `refresh_success`: ['rate>0.95'],
    http_req_duration: [`p(95)<300`, `p(99)<400`],
    http_req_failed: ['rate<0.05'],
  },
};

// P1#1: Mixed workload — exactly 1 HTTP call per iteration, weighted 40/35/25
export function mixedWorkload() {
  const rand = Math.random();

  if (rand < 0.4) {
    testLogin();
  } else if (rand < 0.75) {
    testRefresh();
  } else {
    testLogout();
  }
}

// Individual endpoint scenarios — each makes exactly 1 HTTP call per iteration
// NOTE: constant-arrival-rate executor does not pass setup() data to scenario functions.
// Standalone runs always use fake tokens (expected 401/403). For real-token testing,
// run as part of the mixedWorkload scenario or switch to vus executor.
export function loginOnly() {
  testLogin();
  sleep(0.1);
}

export function logoutOnly() {
  const poolEntry = tokenPool[Math.floor(Math.random() * tokenPool.length)];
  console.warn('[logoutOnly] Using fake token (constant-arrival-rate does not pass setup() data)');
  testLogout(poolEntry.accessToken, poolEntry.refreshToken);
  sleep(0.1);
}

export function refreshOnly() {
  const poolEntry = tokenPool[Math.floor(Math.random() * tokenPool.length)];
  console.warn('[refreshOnly] Using fake token (constant-arrival-rate does not pass setup() data)');
  testRefresh(poolEntry.refreshToken);
  sleep(0.1);
}

// ── Summary Hook ─────────────────────────────────────────────────────────────
export function handleSummary(data) {
  // P2#5: Only evaluate metrics that have thresholds defined
  const thresholdedMetrics = Object.entries(data.metrics).filter(
    ([_, metric]) => metric && metric.thresholds && metric.thresholds.length > 0
  );

  const passed = thresholdedMetrics.every(([_, metric]) =>
    metric.thresholds.every((t) => t.pass)
  );

  const loginP99 = data.metrics.login_p99?.values['p(99)']?.toFixed(2) || 'N/A';
  const logoutP99 = data.metrics.logout_p99?.values['p(99)']?.toFixed(2) || 'N/A';
  const refreshP99 = data.metrics.refresh_p99?.values['p(99)']?.toFixed(2) || 'N/A';

  return {
    'stdout': `\n=== Darkwatch Auth Load Test Results ===\n` +
      `Login P99:     ${loginP99}ms (threshold: ${THRESHOLDS.login}ms)\n` +
      `Logout P99:    ${logoutP99}ms (threshold: ${THRESHOLDS.logout}ms)\n` +
      `Refresh P99:   ${refreshP99}ms (threshold: ${THRESHOLDS.refresh}ms)\n` +
      `Overall:       ${passed ? 'PASS' : 'FAIL'}\n`,
  };
}
