<?php
/**
 * POST /api/v1/login.php
 *
 * Body: { username, password, device_name?, device_id?, platform?, fcm_token? }
 * On success issues a Bearer token (returned once) and records the device.
 * Rate-limited and audited exactly like the web login.
 */

require_once __DIR__ . '/../../config.php';
require_once __DIR__ . '/../../lib/api.php';

api_require_method('POST');

$body     = api_body();
$username = v_clean_string($body['username'] ?? '', 50);
$password = (string) ($body['password'] ?? '');
$ip       = api_client_ip();

if ($username === '' || $password === '') {
    api_error('missing_credentials', 422);
}

if (login_is_locked($pdo, $username, $ip)) {
    api_error('locked', 429, ['minutes' => login_lockout_minutes()]);
}

$stmt = $pdo->prepare('SELECT * FROM users WHERE username = ? LIMIT 1');
$stmt->execute([$username]);
$u = $stmt->fetch();

if (!$u || (int) $u['active'] !== 1 || !password_verify($password, (string) $u['password_hash'])) {
    login_attempt_failed($pdo, $username, $ip);
    // Constant-ish delay to flatten user-enumeration timing differences.
    usleep(random_int(150000, 350000));
    api_error('invalid_credentials', 401);
}

// Opportunistic rehash if the cost factor has changed since the hash was made.
if (password_needs_rehash((string) $u['password_hash'], PASSWORD_DEFAULT)) {
    try {
        $newHash = password_hash($password, PASSWORD_DEFAULT);
        $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?')
            ->execute([$newHash, (int) $u['id']]);
    } catch (Throwable $e) {
        error_log('api login rehash: ' . $e->getMessage());
    }
}

login_attempt_success($pdo, $username, $ip);

$token = api_generate_token();
$hash  = api_token_hash($token);

$deviceName = v_clean_string($body['device_name'] ?? '', 120);
$deviceId   = v_clean_string($body['device_id'] ?? '', 120);
$platform   = v_clean_string($body['platform'] ?? '', 20);
$fcmToken   = v_clean_string($body['fcm_token'] ?? '', 255);

$ttlDays   = max(1, (int) osce_env('API_TOKEN_TTL_DAYS', 30));
$expiresAt = (new DateTime("+{$ttlDays} days"))->format('Y-m-d H:i:s');

try {
    // One active token per device: re-login on a known device retires the old one.
    if ($deviceId !== '') {
        $pdo->prepare('UPDATE api_tokens SET revoked_at = NOW()
                       WHERE user_id = ? AND device_id = ? AND revoked_at IS NULL')
            ->execute([(int) $u['id'], $deviceId]);
    }

    $pdo->prepare('
        INSERT INTO api_tokens
            (user_id, token_hash, device_name, device_id, platform, fcm_token, expires_at)
        VALUES (?, ?, ?, ?, ?, ?, ?)
    ')->execute([
        (int) $u['id'],
        $hash,
        $deviceName !== '' ? $deviceName : null,
        $deviceId   !== '' ? $deviceId   : null,
        $platform   !== '' ? $platform   : null,
        $fcmToken   !== '' ? $fcmToken   : null,
        $expiresAt,
    ]);
} catch (Throwable $e) {
    error_log('api login token insert: ' . $e->getMessage());
    api_error('login_failed', 500);
}

$_SESSION['user_id'] = (int) $u['id'];
$_SESSION['role']    = $u['role'];
audit_log($pdo, 'api_login', [
    'user_id'  => (int) $u['id'],
    'device'   => $deviceName,
    'platform' => $platform,
]);

api_ok([
    'token'      => $token,
    'expires_at' => (new DateTime($expiresAt))->format('c'),
    'user'       => [
        'id'          => (int) $u['id'],
        'username'    => $u['username'],
        'full_name'   => $u['full_name'],
        'role'        => perms_role(['role' => $u['role']]),
        'facility_id' => $u['facility_id'] !== null ? (int) $u['facility_id'] : null,
    ],
]);
