<?php
/**
 * POST /api/v1/attempts.php
 *
 * Submit a finished grade. This is the endpoint the offline outbox flushes to,
 * so it MUST be idempotent: the client sends a stable client_uuid (generated
 * once when the grade is saved on-device) and re-sending the same uuid returns
 * the original attempt instead of creating a duplicate.
 *
 * Body: {
 *   client_uuid:    "uuid",            // required, idempotency key
 *   candidate_id:   8,
 *   form_id:        1,
 *   station_name:   "CVS",             // optional, defaults to form name
 *   global_comment: "...",
 *   score:          { "<item_id>": 3, ... },
 *   item_comment:   { "<item_id>": "good", ... }
 * }
 *
 * Scoring + persistence mirror grade.php's save_attempt block exactly.
 */

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

api_require_method('POST');
$user = api_auth($pdo);

$body          = api_body();
$clientUuid    = v_clean_string($body['client_uuid'] ?? '', 36);
$candidateId   = v_int($body['candidate_id'] ?? 0, 0);
$formId        = v_int($body['form_id'] ?? 0, 0);
$stationName   = v_clean_string($body['station_name'] ?? '', 100);
$globalComment = v_clean_string($body['global_comment'] ?? '', 2000);
$scores        = is_array($body['score'] ?? null) ? $body['score'] : [];
$comments      = is_array($body['item_comment'] ?? null) ? $body['item_comment'] : [];

// UUID is mandatory for safe retries.
if ($clientUuid === '' || !preg_match('/^[0-9a-fA-F-]{8,36}$/', $clientUuid)) {
    api_error('invalid_client_uuid', 422);
}
if ($candidateId <= 0 || $formId <= 0) {
    api_error('invalid_input', 422);
}

/** Shape an attempts row into the API response. */
$shapeAttempt = static fn(array $a): array => [
    'id'           => (int) $a['id'],
    'client_uuid'  => $a['client_uuid'] ?? $clientUuid,
    'total_score'  => (int) $a['total_score'],
    'max_score'    => (int) $a['max_score'],
    'percentage'   => (float) $a['percentage'],
    'final_grade'  => $a['final_grade'],
];

// Idempotency fast-path: already stored -> return it.
$st = $pdo->prepare('SELECT id, client_uuid, total_score, max_score, percentage, final_grade
                     FROM attempts WHERE client_uuid = ? LIMIT 1');
$st->execute([$clientUuid]);
if ($existing = $st->fetch()) {
    api_ok(['attempt' => $shapeAttempt($existing), 'duplicate' => true]);
}

// Validate candidate + form, enforce facility scoping (same as grade.php).
$cs = $pdo->prepare('SELECT c.*, f.name AS facility_name
                     FROM candidates c JOIN facilities f ON c.facility_id = f.id
                     WHERE c.id = ? AND c.deleted_at IS NULL');
$cs->execute([$candidateId]);
$candidate = $cs->fetch();

$fs = $pdo->prepare('SELECT * FROM forms WHERE id = ? AND deleted_at IS NULL');
$fs->execute([$formId]);
$form = $fs->fetch();

if (!$candidate || !$form) {
    api_error('invalid_submission', 422);
}
if (!perms_owns_facility($user, $candidate['facility_id'])) {
    api_error('forbidden_facility', 403);
}

if ($stationName === '') {
    $stationName = (string) $form['name'];
}

// Load items and compute totals (clamping each score to its item max).
$is = $pdo->prepare('SELECT * FROM form_items WHERE form_id = ? ORDER BY sort_order');
$is->execute([$formId]);
$items = $is->fetchAll();

$total = 0;
$maxTotal = 0;
foreach ($items as $it) {
    $max = (int) $it['max_score'];
    $maxTotal += $max;
    $total += v_int($scores[$it['id']] ?? 0, 0, $max, 0);
}
$percent = $maxTotal > 0 ? round(($total / $maxTotal) * 100, 2) : 0;
$grade   = api_pick_grade(api_get_bands($pdo, $formId), $percent);

try {
    $pdo->beginTransaction();

    $pdo->prepare('
        INSERT INTO attempts
            (client_uuid, candidate_id, form_id, examiner_id, station_name,
             total_score, max_score, percentage, final_grade, global_comment)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    ')->execute([
        $clientUuid, $candidateId, $formId, $user['id'], $stationName,
        $total, $maxTotal, $percent, $grade, $globalComment,
    ]);

    $attemptId = (int) $pdo->lastInsertId();

    $ins = $pdo->prepare('INSERT INTO attempt_items (attempt_id, form_item_id, score, comment)
                          VALUES (?, ?, ?, ?)');
    foreach ($items as $it) {
        $max = (int) $it['max_score'];
        $val = v_int($scores[$it['id']] ?? 0, 0, $max, 0);
        $cmt = v_clean_string($comments[$it['id']] ?? '', 1000);
        $ins->execute([$attemptId, $it['id'], $val, $cmt]);
    }

    // Reflect this submission on the live dashboard. The web flow creates an
    // osce_sessions row at candidate check-in; the mobile app grades offline
    // with no check-in, so if no session row exists yet we create one (marked
    // submitted) — otherwise the grade would save but never appear in the live
    // table (only in the audit/recent-activity feed).
    $sess = $pdo->prepare('SELECT id FROM osce_sessions WHERE candidate_id = ? AND station = ? LIMIT 1');
    $sess->execute([$candidateId, $stationName]);
    if ($sess->fetch()) {
        $pdo->prepare("
            UPDATE osce_sessions
            SET status = 'submitted', submitted_at = NOW(), examiner_id = ?,
                grading_started_at = COALESCE(grading_started_at, NOW()),
                last_action = 'Form submitted (mobile)'
            WHERE candidate_id = ? AND station = ?
        ")->execute([$user['id'], $candidateId, $stationName]);
    } else {
        $pdo->prepare("
            INSERT INTO osce_sessions
                (candidate_id, station, examiner_id, status, start_time,
                 grading_started_at, submitted_at, duration_minutes, last_action)
            VALUES (?, ?, ?, 'submitted', NOW(), NOW(), NOW(), ?, 'Form submitted (mobile)')
        ")->execute([
            $candidateId,
            $stationName,
            $user['id'],
            (int) ($form['default_duration_minutes'] ?? 8),
        ]);
    }

    $pdo->prepare('DELETE FROM grade_drafts WHERE examiner_id = ? AND candidate_id = ? AND form_id = ?')
        ->execute([$user['id'], $candidateId, $formId]);

    $pdo->commit();
} catch (Throwable $e) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }
    // Concurrent flush of the same uuid: the UNIQUE key rejected the second
    // insert. Treat as success and return the row that won the race.
    $st = $pdo->prepare('SELECT id, client_uuid, total_score, max_score, percentage, final_grade
                         FROM attempts WHERE client_uuid = ? LIMIT 1');
    $st->execute([$clientUuid]);
    if ($row = $st->fetch()) {
        api_ok(['attempt' => $shapeAttempt($row), 'duplicate' => true]);
    }
    error_log('api attempts save: ' . $e->getMessage());
    api_error('save_failed', 500);
}

audit_log($pdo, 'grade_save', [
    'attempt_id'   => $attemptId,
    'candidate_id' => $candidateId,
    'form_id'      => $formId,
    'total'        => $total,
    'percentage'   => $percent,
    'grade'        => $grade,
    'source'       => 'mobile',
]);

api_ok([
    'attempt' => [
        'id'          => $attemptId,
        'client_uuid' => $clientUuid,
        'total_score' => $total,
        'max_score'   => $maxTotal,
        'percentage'  => $percent,
        'final_grade' => $grade,
    ],
    'duplicate' => false,
]);
