<?php
declare(strict_types=1);

require_once __DIR__ . '/AchievementService.php';

final class XPService
{
    private PDO $db;
    private AchievementService $achievementService;

    public function __construct(PDO $db)
    {
        $this->db = $db;
        $this->achievementService = new AchievementService($db);
    }

    /**
     * Cộng XP cho học sinh.
     *
     * $bookId, $unitId, $lessonId, $activityId có thể để null nếu XP không gắn
     * với nội dung học cụ thể.
     */
    public function award(
        int $studentId,
        int $academicYearId,
        ?int $bookId,
        ?int $unitId,
        ?int $lessonId,
        ?int $activityId,
        string $activityType,
        int $xp,
        string $xpSource,
        ?string $note = null,
        ?int $referenceId = null
    ): array {
        if ($studentId <= 0 || $academicYearId <= 0) {
            throw new InvalidArgumentException('studentId và academicYearId phải lớn hơn 0.');
        }

        if ($xp <= 0) {
            return [
                'success' => false,
                'message' => 'XP phải lớn hơn 0.',
                'xp_awarded' => 0,
                'year_xp' => $this->getStudentXP($studentId, $academicYearId),
                'lifetime_xp' => $this->getLifetimeXP($studentId),
                'achievements' => [],
            ];
        }

        $activityType = $this->cleanCode($activityType);
        $xpSource = $this->cleanCode($xpSource);

        $this->db->beginTransaction();

        try {
            $this->lockStudent($studentId);

            $this->addTransaction(
                studentId: $studentId,
                academicYearId: $academicYearId,
                bookId: $bookId,
                unitId: $unitId,
                lessonId: $lessonId,
                activityId: $activityId,
                activityType: $activityType,
                xpChange: $xp,
                xpSource: $xpSource,
                note: $note,
                referenceId: $referenceId
            );

            $this->updateStudentXP($studentId, $academicYearId, $xp);
            $this->updateLifetimeXP($studentId, $xp);

            $unlocked = $this->achievementService->checkAndUnlock(
                $studentId,
                $academicYearId
            );

            foreach ($unlocked as $achievement) {
                $reward = (int)($achievement['xp_reward'] ?? 0);

                if ($reward <= 0) {
                    continue;
                }

                $this->addTransaction(
                    studentId: $studentId,
                    academicYearId: $academicYearId,
                    bookId: null,
                    unitId: null,
                    lessonId: null,
                    activityId: null,
                    activityType: 'achievement',
                    xpChange: $reward,
                    xpSource: 'achievement',
                    note: 'Mở khóa thành tựu: ' . (string)$achievement['name'],
                    referenceId: (int)$achievement['id']
                );

                $this->updateStudentXP($studentId, $academicYearId, $reward);
                $this->updateLifetimeXP($studentId, $reward);
            }

            $yearXP = $this->getStudentXP($studentId, $academicYearId);
            $lifetimeXP = $this->getLifetimeXP($studentId);

            $this->db->commit();

            return [
                'success' => true,
                'message' => 'Đã cộng XP.',
                'xp_awarded' => $xp,
                'year_xp' => $yearXP,
                'lifetime_xp' => $lifetimeXP,
                'achievements' => $unlocked,
            ];
        } catch (Throwable $e) {
            if ($this->db->inTransaction()) {
                $this->db->rollBack();
            }

            throw $e;
        }
    }

    /**
     * Trừ/hoàn tác XP. Chỉ nên gọi từ trang quản trị.
     */
    public function removeXP(
        int $studentId,
        int $academicYearId,
        int $xp,
        string $reason = 'admin_adjustment'
    ): array {
        if ($xp <= 0) {
            throw new InvalidArgumentException('Số XP cần trừ phải lớn hơn 0.');
        }

        $currentYearXP = $this->getStudentXP($studentId, $academicYearId);
        $currentLifetimeXP = $this->getLifetimeXP($studentId);
        $actualDeduction = min($xp, $currentYearXP, $currentLifetimeXP);

        if ($actualDeduction <= 0) {
            return [
                'success' => false,
                'message' => 'Học sinh không có XP để trừ.',
                'xp_removed' => 0,
                'year_xp' => $currentYearXP,
                'lifetime_xp' => $currentLifetimeXP,
            ];
        }

        $this->db->beginTransaction();

        try {
            $this->lockStudent($studentId);

            $this->addTransaction(
                studentId: $studentId,
                academicYearId: $academicYearId,
                bookId: null,
                unitId: null,
                lessonId: null,
                activityId: null,
                activityType: 'admin',
                xpChange: -$actualDeduction,
                xpSource: 'admin_adjustment',
                note: $reason,
                referenceId: null
            );

            $this->updateStudentXP($studentId, $academicYearId, -$actualDeduction);
            $this->updateLifetimeXP($studentId, -$actualDeduction);

            $yearXP = $this->getStudentXP($studentId, $academicYearId);
            $lifetimeXP = $this->getLifetimeXP($studentId);

            $this->db->commit();

            return [
                'success' => true,
                'message' => 'Đã trừ XP.',
                'xp_removed' => $actualDeduction,
                'year_xp' => $yearXP,
                'lifetime_xp' => $lifetimeXP,
            ];
        } catch (Throwable $e) {
            if ($this->db->inTransaction()) {
                $this->db->rollBack();
            }

            throw $e;
        }
    }

    public function getStudentXP(int $studentId, int $academicYearId): int
    {
        $stmt = $this->db->prepare(
            'SELECT xp
             FROM student_xp
             WHERE student_id = :student_id
               AND academic_year_id = :academic_year_id
             LIMIT 1'
        );

        $stmt->execute([
            ':student_id' => $studentId,
            ':academic_year_id' => $academicYearId,
        ]);

        return (int)($stmt->fetchColumn() ?: 0);
    }

    public function getLifetimeXP(int $studentId): int
    {
        $stmt = $this->db->prepare(
            'SELECT lifetime_xp FROM students WHERE id = :student_id LIMIT 1'
        );
        $stmt->execute([':student_id' => $studentId]);

        return (int)($stmt->fetchColumn() ?: 0);
    }

    private function addTransaction(
        int $studentId,
        int $academicYearId,
        ?int $bookId,
        ?int $unitId,
        ?int $lessonId,
        ?int $activityId,
        string $activityType,
        int $xpChange,
        string $xpSource,
        ?string $note,
        ?int $referenceId
    ): void {
        $sql = '
            INSERT INTO xp_transactions
            (
                student_id,
                academic_year_id,
                book_id,
                unit_id,
                lesson_id,
                level_id,
                activity_id,
                activity_type,
                xp_source,
                reference_id,
                xp_change,
                reason,
                created_at,
                updated_at
            )
            VALUES
            (
                :student_id,
                :academic_year_id,
                :book_id,
                :unit_id,
                :lesson_id,
                NULL,
                :activity_id,
                :activity_type,
                :xp_source,
                :reference_id,
                :xp_change,
                :reason,
                NOW(),
                NOW()
            )
        ';

        $stmt = $this->db->prepare($sql);
        $stmt->execute([
            ':student_id' => $studentId,
            ':academic_year_id' => $academicYearId,
            ':book_id' => $bookId,
            ':unit_id' => $unitId,
            ':lesson_id' => $lessonId,
            ':activity_id' => $activityId,
            ':activity_type' => $activityType,
            ':xp_source' => $xpSource,
            ':reference_id' => $referenceId,
            ':xp_change' => $xpChange,
            ':reason' => $note !== null && trim($note) !== '' ? trim($note) : $xpSource,
        ]);
    }

    private function updateStudentXP(
        int $studentId,
        int $academicYearId,
        int $xpChange
    ): void {
        $sql = '
            INSERT INTO student_xp
                (student_id, academic_year_id, xp, created_at, updated_at)
            VALUES
                (:student_id, :academic_year_id, GREATEST(:xp_insert, 0), NOW(), NOW())
            ON DUPLICATE KEY UPDATE
                xp = GREATEST(xp + :xp_update, 0),
                updated_at = NOW()
        ';

        $stmt = $this->db->prepare($sql);
        $stmt->execute([
            ':student_id' => $studentId,
            ':academic_year_id' => $academicYearId,
            ':xp_insert' => $xpChange,
            ':xp_update' => $xpChange,
        ]);
    }

    private function updateLifetimeXP(int $studentId, int $xpChange): void
    {
        $stmt = $this->db->prepare(
            'UPDATE students
             SET lifetime_xp = GREATEST(lifetime_xp + :xp_change, 0),
                 updated_at = NOW()
             WHERE id = :student_id'
        );

        $stmt->execute([
            ':xp_change' => $xpChange,
            ':student_id' => $studentId,
        ]);

        if ($stmt->rowCount() === 0) {
            $check = $this->db->prepare('SELECT id FROM students WHERE id = :id');
            $check->execute([':id' => $studentId]);

            if (!$check->fetchColumn()) {
                throw new RuntimeException('Không tìm thấy học sinh.');
            }
        }
    }

    private function lockStudent(int $studentId): void
    {
        $stmt = $this->db->prepare(
            'SELECT id FROM students WHERE id = :student_id FOR UPDATE'
        );
        $stmt->execute([':student_id' => $studentId]);

        if (!$stmt->fetchColumn()) {
            throw new RuntimeException('Không tìm thấy học sinh.');
        }
    }

    private function cleanCode(string $value): string
    {
        $value = strtolower(trim($value));
        $value = preg_replace('/[^a-z0-9_-]+/', '_', $value) ?? '';

        if ($value === '') {
            throw new InvalidArgumentException('Mã nguồn XP hoặc loại hoạt động không hợp lệ.');
        }

        return substr($value, 0, 50);
    }
}
