<?php
declare(strict_types=1);

namespace App\Questions;

use App\Support\Hooks;
use App\Support\CoreHooks;
use InvalidArgumentException;
use PDO;
use RuntimeException;
use Throwable;

final class AnswerService
{
    public function __construct(
        private readonly PDO $pdo,
    ) {
    }

    public function create(int $userId, int $questionId, string $body, string $status = 'published'): int
    {
        $body = trim($body);
        $status = strtolower(trim($status));
        if (!in_array($status, ['published', 'pending'], true)) {
            throw new InvalidArgumentException('Invalid answer status.');
        }

        if ($body === '') {
            throw new \App\Support\TranslatableException('questions.body_required', [], 'Answer body is required.');
        }

        $this->requireActiveUser($userId);

        $this->pdo->beginTransaction();

        try {
            $question = $this->lockQuestion($questionId);

            if (($question['status'] ?? null) !== 'published') {
                throw new RuntimeException('Answers are allowed only on published questions.');
            }

            $statement = $this->pdo->prepare(
                'INSERT INTO answers (
                    question_id,
                    user_id,
                    body,
                    status,
                    created_at,
                    updated_at
                 ) VALUES (
                    :question_id,
                    :user_id,
                    :body,
                    :status,
                    NOW(),
                    NOW()
                 )'
            );
            $statement->execute([
                'question_id' => $questionId,
                'user_id' => $userId,
                'body' => $body,
                'status' => $status,
            ]);

            $answerId = (int) $this->pdo->lastInsertId();

            if ($status === 'published') {
                $count = $this->pdo->prepare(
                    'UPDATE questions
                     SET answers_count = answers_count + 1,
                         updated_at = NOW()
                     WHERE id = :id'
                );
                $count->execute(['id' => $questionId]);
            }

            $this->pdo->commit();
            Hooks::emit(CoreHooks::ANSWER_CREATED, [
                'answer_id' => $answerId,
                'question_id' => $questionId,
                'user_id' => $userId,
                'status' => $status,
            ]);

            return $answerId;
        } catch (Throwable $throwable) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }

            throw $throwable;
        }
    }

    public function accept(int $actorId, int $answerId): bool
    {
        $this->pdo->beginTransaction();

        try {
            $answer = $this->lockAnswer($answerId);
            if ((string) ($answer['status'] ?? 'published') !== 'published') {
                throw new RuntimeException('Only published answers may be accepted.');
            }
            $question = $this->lockQuestion((int) $answer['question_id']);

            $actor = $this->actor($actorId);
            $isOwner = (int) ($question['user_id'] ?? 0) === $actorId;
            $isModerator = quaeriva_user_has_role($actor, 'moderator', 'admin');
            if (!$isOwner && !$isModerator) {
                throw new RuntimeException('Only the question owner or a moderator may accept an answer.');
            }

            $clear = $this->pdo->prepare(
                'UPDATE answers
                 SET is_best = 0,
                     updated_at = NOW()
                 WHERE question_id = :question_id
                   AND is_best = 1'
            );
            $clear->execute(['question_id' => $answer['question_id']]);

            $mark = $this->pdo->prepare(
                'UPDATE answers
                 SET is_best = 1,
                     updated_at = NOW()
                 WHERE id = :id
                   AND question_id = :question_id'
            );
            $mark->execute([
                'id' => $answerId,
                'question_id' => $answer['question_id'],
            ]);

            $questionUpdate = $this->pdo->prepare(
                'UPDATE questions
                 SET best_answer_id = :best_answer_id,
                     updated_at = NOW()
                 WHERE id = :id'
            );
            $questionUpdate->execute([
                'best_answer_id' => $answerId,
                'id' => $answer['question_id'],
            ]);

            $this->pdo->commit();
            Hooks::emit(CoreHooks::ANSWER_ACCEPTED, [
                'answer_id' => $answerId,
                'question_id' => (int) $answer['question_id'],
                'actor_id' => $actorId,
            ]);

            return true;
        } catch (Throwable $throwable) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }

            throw $throwable;
        }
    }

    private function actor(int $userId): array
    {
        $statement = $this->pdo->prepare("SELECT id, role, status FROM users WHERE id=:id AND status='active' LIMIT 1");
        $statement->execute(['id' => $userId]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);
        if (!is_array($row)) throw new RuntimeException('An active user is required.');
        return $row;
    }

    private function requireActiveUser(int $userId): void
    {
        $statement = $this->pdo->prepare(
            'SELECT id
             FROM users
             WHERE id = :id
               AND status = :status
             LIMIT 1'
        );
        $statement->execute([
            'id' => $userId,
            'status' => 'active',
        ]);

        if ($statement->fetchColumn() === false) {
            throw new RuntimeException('An active user is required.');
        }
    }

    private function lockQuestion(int $questionId): array
    {
        $statement = $this->pdo->prepare(
            'SELECT *
             FROM questions
             WHERE id = :id
             LIMIT 1
             FOR UPDATE'
        );
        $statement->execute(['id' => $questionId]);
        $question = $statement->fetch(PDO::FETCH_ASSOC);

        if (!is_array($question)) {
            throw new RuntimeException('Question not found.');
        }

        return $question;
    }

    private function lockAnswer(int $answerId): array
    {
        $statement = $this->pdo->prepare(
            'SELECT *
             FROM answers
             WHERE id = :id
             LIMIT 1
             FOR UPDATE'
        );
        $statement->execute(['id' => $answerId]);
        $answer = $statement->fetch(PDO::FETCH_ASSOC);

        if (!is_array($answer)) {
            throw new RuntimeException('Answer not found.');
        }

        return $answer;
    }
}
