/**
 * Thin axios client used by tests to seed/teardown users and engagements
 * without going through the UI. The bootstrap admin is created out-of-band
 * (via `make create-admin`) and logs in once to provision per-suite users.
 */
import axios, { AxiosInstance, isAxiosError } from 'axios';

export interface User {
  id: number;
  username: string;
  role: 'admin' | 'redteam' | 'soc';
  created_at: string | null;
}

export interface Engagement {
  id: number;
  name: string;
  description: string | null;
  start_date: string;
  end_date: string | null;
  status: 'planned' | 'active' | 'closed';
  created_at: string | null;
  created_by: { id: number; username: string } | null;
}

export type Role = User['role'];

const BASE_URL = process.env.MIMIC_BASE_URL ?? 'http://localhost:5000';
const BOOTSTRAP_ADMIN_USER = process.env.MIMIC_BOOTSTRAP_USER ?? 'root';
const BOOTSTRAP_ADMIN_PASS = process.env.MIMIC_BOOTSTRAP_PASS ?? 'rootpass8';

export function makeClient(token?: string): AxiosInstance {
  return axios.create({
    baseURL: `${BASE_URL}/api`,
    headers: token ? { Authorization: `Bearer ${token}` } : undefined,
    validateStatus: () => true, // tests assert on status themselves
  });
}

export async function login(
  username: string,
  password: string,
): Promise<{ token: string; user: User }> {
  const client = makeClient();
  const r = await client.post('/auth/login', { username, password });
  if (r.status !== 200) {
    throw new Error(`login(${username}) failed: ${r.status} ${JSON.stringify(r.data)}`);
  }
  return { token: r.data.access_token as string, user: r.data.user as User };
}

export async function adminToken(): Promise<string> {
  const { token } = await login(BOOTSTRAP_ADMIN_USER, BOOTSTRAP_ADMIN_PASS);
  return token;
}

/**
 * Idempotent helper: ensures a user with the given username/role exists and
 * has the requested password. Returns the user record.
 *
 * Strategy:
 *   - try login: if it succeeds, we're done.
 *   - else: as admin, list users; if username found, PATCH password+role; else POST.
 */
export async function ensureUser(
  username: string,
  password: string,
  role: Role,
): Promise<User> {
  try {
    const { user } = await login(username, password);
    if (user.role !== role) {
      const admin = await adminToken();
      const client = makeClient(admin);
      const r = await client.patch(`/users/${user.id}`, { role });
      if (r.status !== 200) throw new Error(`patch role: ${r.status}`);
      return r.data as User;
    }
    return user;
  } catch {
    // fall through to admin path
  }
  const admin = await adminToken();
  const client = makeClient(admin);
  const list = await client.get('/users');
  if (list.status !== 200) {
    throw new Error(`list users failed: ${list.status} ${JSON.stringify(list.data)}`);
  }
  const existing = (list.data as User[]).find((u) => u.username === username);
  if (existing) {
    const r = await client.patch(`/users/${existing.id}`, { password, role });
    if (r.status !== 200) {
      throw new Error(`patch user failed: ${r.status} ${JSON.stringify(r.data)}`);
    }
    return r.data as User;
  }
  const r = await client.post('/users', { username, password, role });
  if (r.status !== 201) {
    throw new Error(`create user failed: ${r.status} ${JSON.stringify(r.data)}`);
  }
  return r.data as User;
}

export async function deleteUserByUsername(token: string, username: string): Promise<void> {
  const client = makeClient(token);
  const list = await client.get('/users');
  if (list.status !== 200) return;
  const u = (list.data as User[]).find((x) => x.username === username);
  if (!u) return;
  await client.delete(`/users/${u.id}`);
}

export async function createEngagement(
  token: string,
  payload: Partial<Pick<Engagement, 'name' | 'description' | 'start_date' | 'end_date' | 'status'>>,
): Promise<Engagement> {
  const client = makeClient(token);
  const body = {
    name: payload.name ?? 'Test Engagement',
    description: payload.description,
    start_date: payload.start_date ?? '2026-01-01',
    end_date: payload.end_date,
    status: payload.status ?? 'planned',
  };
  const r = await client.post('/engagements', body);
  if (r.status !== 201) {
    throw new Error(`create engagement failed: ${r.status} ${JSON.stringify(r.data)}`);
  }
  return r.data as Engagement;
}

export async function deleteEngagement(token: string, id: number): Promise<void> {
  const client = makeClient(token);
  await client.delete(`/engagements/${id}`);
}

export async function listEngagements(token: string): Promise<Engagement[]> {
  const client = makeClient(token);
  const r = await client.get('/engagements');
  if (r.status !== 200) {
    throw new Error(`list engagements failed: ${r.status}`);
  }
  return r.data as Engagement[];
}

export async function deleteAllEngagements(token: string): Promise<void> {
  const items = await listEngagements(token);
  await Promise.all(items.map((e) => deleteEngagement(token, e.id)));
}

export async function waitForHealth(timeoutMs = 30_000): Promise<void> {
  const deadline = Date.now() + timeoutMs;
  const client = makeClient();
  let lastErr: unknown = null;
  while (Date.now() < deadline) {
    try {
      const r = await client.get('/health');
      if (r.status === 200) return;
    } catch (e) {
      lastErr = e;
    }
    await new Promise((r) => setTimeout(r, 500));
  }
  throw new Error(
    `backend not healthy after ${timeoutMs}ms: ${
      isAxiosError(lastErr) ? lastErr.message : String(lastErr)
    }`,
  );
}

export const BASE = BASE_URL;
