/**
 * Shared browser-test helpers.
 *
 * Import this in every `*.integration.test.ts` file.
 * Provides login, API calling, and page-navigation utilities
 * that work inside the Vitest browser environment.
 */
import { expect } from 'vitest';
import { CREDENTIALS } from './fixtures';
import { BASE } from './setup';

// ── Types ───────────────────────────────────────────────────────────

interface ApiResponse<T = unknown> {
  code: number;
  data: T;
  message?: string;
}

type Role = keyof typeof CREDENTIALS;

// ── Auth helpers ────────────────────────────────────────────────────

/**
 * Login as a specific role and store the token in localStorage.
 * Returns the parsed response data.
 */
export async function loginAs(
  role: Role,
): Promise<{ token: string; user: Record<string, unknown> }> {
  const creds = CREDENTIALS[role];
  const res = await fetch(`${BASE}/api/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(creds),
  });
  expect(res.status).toBe(201);
  const json = (await res.json()) as ApiResponse<{ token: string; user: Record<string, unknown> }>;
  expect(json.code).toBe(0);
  localStorage.setItem('token', json.data.token);
  localStorage.setItem('user', JSON.stringify(json.data.user));
  return json.data;
}

/**
 * Logout: clear localStorage.
 */
export function logout(): void {
  localStorage.removeItem('token');
  localStorage.removeItem('user');
  localStorage.removeItem('permissions');
}

// ── API helpers (authenticated) ─────────────────────────────────────

function authHeaders(): Record<string, string> {
  const token = localStorage.getItem('token');
  return {
    'Content-Type': 'application/json',
    ...(token ? { Authorization: `Bearer ${token}` } : {}),
  };
}

export async function apiGet<T = unknown>(url: string): Promise<ApiResponse<T>> {
  const res = await fetch(`${BASE}${url}`, { headers: authHeaders() });
  return (await res.json()) as ApiResponse<T>;
}

export async function apiPost<T = unknown>(url: string, body?: unknown): Promise<ApiResponse<T>> {
  const res = await fetch(`${BASE}${url}`, {
    method: 'POST',
    headers: authHeaders(),
    body: body ? JSON.stringify(body) : undefined,
  });
  return (await res.json()) as ApiResponse<T>;
}

export async function apiPut<T = unknown>(url: string, body?: unknown): Promise<ApiResponse<T>> {
  const res = await fetch(`${BASE}${url}`, {
    method: 'PUT',
    headers: authHeaders(),
    body: body ? JSON.stringify(body) : undefined,
  });
  return (await res.json()) as ApiResponse<T>;
}

export async function apiDelete<T = unknown>(url: string): Promise<ApiResponse<T>> {
  const res = await fetch(`${BASE}${url}`, {
    method: 'DELETE',
    headers: authHeaders(),
  });
  return (await res.json()) as ApiResponse<T>;
}

// ── Page helpers ────────────────────────────────────────────────────

/**
 * Navigate to a page and wait for it to load.
 */
export async function goTo(path: string): Promise<void> {
  document.location.href = `${BASE}${path}`;
  // Wait for React to render
  await new Promise((r) => setTimeout(r, 500));
}

/**
 * Assert the current page URL contains the given path.
 */
export async function assertOnPage(path: string): Promise<void> {
  // Wait a tick for SPA routing
  await new Promise((r) => setTimeout(r, 300));
  expect(window.location.pathname).toContain(path);
}

// ── Wait helpers ────────────────────────────────────────────────────

/** Poll until a condition is true or timeout. */
export async function waitFor(
  condition: () => boolean | Promise<boolean>,
  timeout = 5000,
  interval = 200,
): Promise<void> {
  const start = Date.now();
  while (Date.now() - start < timeout) {
    if (await condition()) return;
    await new Promise((r) => setTimeout(r, interval));
  }
  throw new Error(`waitFor timed out after ${timeout}ms`);
}

// ── Assertion helpers ───────────────────────────────────────────────

/** Assert an API response is successful (code === 0). */
export function assertOk<T>(res: ApiResponse<T>, msg?: string): T {
  expect(res.code, msg ?? 'API should return code 0').toBe(0);
  return res.data;
}

/** Assert an API response is an error (code !== 0). */
export function assertError(res: ApiResponse, expectedCode?: number): void {
  expect(res.code).not.toBe(0);
  if (expectedCode !== undefined) {
    expect(res.code).toBe(expectedCode);
  }
}

/** Assert a 403 is returned (permission denied). */
export async function assertForbidden(promise: Promise<Response>): Promise<void> {
  const res = await promise;
  expect(res.status).toBe(403);
}

/** Assert a 401 is returned (unauthenticated). */
export async function assertUnauthenticated(promise: Promise<Response>): Promise<void> {
  const res = await promise;
  expect(res.status).toBe(401);
}

// ── Sensitive data helpers (PRD §3.3) ───────────────────────────────

/** Assert phone number is masked: 138****0001 */
export function assertPhoneMasked(displayed: string): void {
  expect(displayed).toMatch(/^\d{3}\*{4}\d{4}$/);
}

/** Assert ID card is masked: 110101********1234 */
export function assertIdCardMasked(displayed: string): void {
  expect(displayed).toMatch(/^\d{6}\*{8}\d{4}$/);
}
