import { getVaultServerUrl } from './sidecar-server';

// Vaultwarden token broker. The platform (not the device) obtains and refreshes the Vaultwarden token via
// the sidecar's `/identity/connect/token`. We deal in the OAuth-style form grants only; the master
// password never appears here — the client sends a pre-derived `authHash` (Bitwarden's master password
// hash), exactly what Vaultwarden itself checks.

export type ConnectResult = { ok: true; data: Record<string, unknown> } | { ok: false; status: number; error: string };

async function postConnectToken(form: URLSearchParams): Promise<ConnectResult> {
  const base = getVaultServerUrl();
  if (!base) return { ok: false, status: 503, error: 'vault sidecar unavailable' };
  let res: Response;
  try {
    res = await fetch(`${base}/identity/connect/token`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: form.toString(),
      signal: AbortSignal.timeout(30_000),
    });
  } catch {
    return { ok: false, status: 502, error: 'vault upstream unreachable' };
  }
  const data = (await res.json().catch(() => ({}))) as Record<string, unknown>;
  if (!res.ok) {
    // Vaultwarden puts the human message in different places (error_description/error are often empty on a
    // bad login; the real text is in `message` or `errorModel.message`). Check them all.
    const errorModel = data.errorModel as Record<string, unknown> | undefined;
    const candidates = [data.error_description, errorModel?.message, data.message, data.error];
    const desc = candidates.find((c): c is string => typeof c === 'string' && c.length > 0) ?? 'vault login failed';
    return { ok: false, status: res.status, error: desc };
  }
  return { ok: true, data };
}

export type PasswordGrantInput = {
  email: string;
  authHash: string;
  clientId: string;
  deviceIdentifier: string;
  deviceType: string;
  deviceName: string;
};

/** Owner login: exchange the client-derived auth hash for a Vaultwarden token set. */
export function passwordGrant(p: PasswordGrantInput): Promise<ConnectResult> {
  const form = new URLSearchParams();
  form.set('grant_type', 'password');
  form.set('username', p.email);
  form.set('password', p.authHash);
  form.set('scope', 'api offline_access'); // offline_access → a refresh token comes back
  form.set('client_id', p.clientId);
  form.set('deviceIdentifier', p.deviceIdentifier);
  form.set('deviceType', p.deviceType);
  form.set('deviceName', p.deviceName);
  return postConnectToken(form);
}

/** Renew the access token from the stored refresh token (no password needed). */
export function refreshGrant(refreshToken: string, clientId: string): Promise<ConnectResult> {
  const form = new URLSearchParams();
  form.set('grant_type', 'refresh_token');
  form.set('refresh_token', refreshToken);
  form.set('client_id', clientId);
  return postConnectToken(form);
}

// Vaultwarden mixes PascalCase and camelCase field names across versions — read a field case-insensitively.
export function pick(data: Record<string, unknown>, name: string): unknown {
  const lower = name.charAt(0).toLowerCase() + name.slice(1);
  const upper = name.charAt(0).toUpperCase() + name.slice(1);
  return data[name] ?? data[lower] ?? data[upper];
}

/** Compute an absolute expiry from a token response's `expires_in` (seconds), if present. */
export function expiryFrom(data: Record<string, unknown>, now: number): Date | null {
  const secs = data.expires_in;
  return typeof secs === 'number' && Number.isFinite(secs) ? new Date(now + secs * 1000) : null;
}
