<?php

declare(strict_types=1);

/**
 * Forum write actions (vote / accept), reached via POST /forum/act from
 * public-routes.php. CSRF-guarded and login-gated; returns JSON. Votes key on
 * the visitor's forum identity (qa_users id, resolved by forum_author_id()),
 * satisfying qa_votes' FK to qa_users.
 */

require __DIR__ . '/_shell.php';

use PpPhp\Core\Security;
use PpPhp\Core\DB;

header('Content-Type: application/json');

function fx_json(array $d, int $code = 200): never
{
    http_response_code($code);
    echo json_encode($d);
    exit;
}

/** Reputation a single vote contributes to the author (up rewards, down penalises). */
function fx_rep_contrib(int $value, string $type): int
{
    if ($value === 1)  { return $type === 'answer' ? 10 : 5; }
    if ($value === -1) { return -2; }
    return 0;
}

/** Adjust a member's reputation, floored at zero. */
function fx_award(int $authorId, int $delta): void
{
    if ($authorId <= 0 || $delta === 0) { return; }
    DB::Query('UPDATE qa_users SET reputation = GREATEST(0, reputation + ?) WHERE id = ?', [$delta, $authorId]);
}

if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
    fx_json(['ok' => false, 'error' => 'method'], 405);
}

// CSRF on every write.
$token = (string) ($_POST['_token'] ?? $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '');
if (!Security::CSRF()->Check($token)) {
    fx_json(['ok' => false, 'error' => 'csrf', 'message' => 'Your session expired — please refresh.'], 400);
}

// Login required (as a forum identity).
$uid = forum_author_id();
if (!$uid) {
    fx_json(['ok' => false, 'error' => 'login', 'message' => 'Please sign in to take part.'], 401);
}
// Suspended accounts can read but never write.
if (forum_is_suspended()) {
    fx_json(['ok' => false, 'error' => 'suspended', 'message' => 'Your account is suspended.'], 403);
}

$act = (string) ($_POST['act'] ?? '');

if ($act === 'vote') {
    $type = ($_POST['type'] ?? '') === 'answer' ? 'answer' : 'question';
    $tid  = (int) ($_POST['id'] ?? 0);
    $val  = (int) ($_POST['value'] ?? 0);
    $val  = $val > 0 ? 1 : ($val < 0 ? -1 : 0);
    if ($tid <= 0 || $val === 0) {
        fx_json(['ok' => false, 'error' => 'input'], 400);
    }
    $table = $type === 'answer' ? 'qa_answers' : 'qa_questions';
    $target = dbRow("SELECT id, author_id FROM {$table} WHERE id = ? AND deleted_at IS NULL", [$tid]);
    if (!$target) {
        fx_json(['ok' => false, 'error' => 'notfound'], 404);
    }
    $authorId = (int) ($target['author_id'] ?? 0);
    // qa_votes records WHO voted (unique per user+target, enables toggle); the
    // target's vote_score is adjusted by the DELTA so any seeded base is kept.
    $existing = dbValue('SELECT value FROM qa_votes WHERE user_id = ? AND target_type = ? AND target_id = ?', [$uid, $type, $tid]);
    if ($existing === null) {
        DB::Insert('qa_votes', ['user_id' => $uid, 'target_type' => $type, 'target_id' => $tid, 'value' => $val]);
        $delta = $val;  $myVote = $val;
    } elseif ((int) $existing === $val) {                         // same click → toggle off
        DB::Query('DELETE FROM qa_votes WHERE user_id = ? AND target_type = ? AND target_id = ?', [$uid, $type, $tid]);
        $delta = -$val; $myVote = 0;
    } else {                                                      // switch direction (−1 → +1 = +2)
        DB::Query('UPDATE qa_votes SET value = ? WHERE user_id = ? AND target_type = ? AND target_id = ?', [$val, $uid, $type, $tid]);
        $delta = $val - (int) $existing; $myVote = $val;
    }
    DB::Query("UPDATE {$table} SET vote_score = vote_score + ? WHERE id = ?", [$delta, $tid]);
    // Reputation: reward the content's author for the net change in this vote —
    // never for voting on your own post.
    if ($authorId !== $uid) {
        fx_award($authorId, fx_rep_contrib($myVote, $type) - fx_rep_contrib((int) ($existing ?? 0), $type));
    }
    $score = (int) (dbValue("SELECT vote_score FROM {$table} WHERE id = ?", [$tid]) ?? 0);
    fx_json(['ok' => true, 'score' => $score, 'myVote' => $myVote]);
}

if ($act === 'accept') {
    $aid = (int) ($_POST['id'] ?? 0);
    $a = dbRow('SELECT id, question_id, author_id FROM qa_answers WHERE id = ? AND deleted_at IS NULL', [$aid]);
    if (!$a) {
        fx_json(['ok' => false, 'error' => 'notfound'], 404);
    }
    $q = dbRow('SELECT id, author_id, accepted_answer_id FROM qa_questions WHERE id = ?', [(int) $a['question_id']]);
    if (!$q) {
        fx_json(['ok' => false, 'error' => 'notfound'], 404);
    }
    if ((int) $q['author_id'] !== $uid) {
        fx_json(['ok' => false, 'error' => 'forbidden', 'message' => 'Only the person who asked can accept an answer.'], 403);
    }
    $ACCEPT_REP = 15;
    $prevAid = (int) ($q['accepted_answer_id'] ?? 0);
    $toggleOff = ($prevAid === $aid);
    DB::Query('UPDATE qa_answers SET is_accepted = 0 WHERE question_id = ?', [(int) $q['id']]);

    // Reputation: the accepted answer's author earns; revoke from a previously
    // accepted answer. Never award for accepting your own answer.
    if ($prevAid > 0) {
        $prevAuthor = (int) (dbValue('SELECT author_id FROM qa_answers WHERE id = ?', [$prevAid]) ?? 0);
        if ($prevAuthor !== $uid) { fx_award($prevAuthor, -$ACCEPT_REP); }
    }
    if ($toggleOff) {
        DB::Query('UPDATE qa_questions SET accepted_answer_id = NULL WHERE id = ?', [(int) $q['id']]);
        fx_json(['ok' => true, 'accepted' => 0]);
    }
    DB::Query('UPDATE qa_answers SET is_accepted = 1 WHERE id = ?', [$aid]);
    DB::Query('UPDATE qa_questions SET accepted_answer_id = ? WHERE id = ?', [$aid, (int) $q['id']]);
    $newAuthor = (int) ($a['author_id'] ?? 0);
    if ($newAuthor !== $uid) { fx_award($newAuthor, $ACCEPT_REP); }
    fx_json(['ok' => true, 'accepted' => $aid]);
}

fx_json(['ok' => false, 'error' => 'unknown'], 400);
