<?php

namespace App\Services\Admissions;

use Illuminate\Support\Facades\DB;

/**
 * Issues the public-facing reference numbers used across the platform.
 *
 * Numbers are drawn from a locked counter row rather than the table's
 * auto-increment id, so the sequence stays dense, predictable and free of
 * information about how many records exist (PRD 64, 65).
 */
class ReferenceNumberService
{
    public const TYPE_APPLICATION = 'application';
    public const TYPE_ADMISSION = 'admission';

    /** e.g. KCU-2026-000001 */
    public function applicationNumber(?int $year = null): string
    {
        $config = config('kcu.application_number');
        $year ??= (int) now()->year;

        return $this->format(
            $config['format'],
            setting('application_number_prefix', $config['prefix']),
            $year,
            $this->nextValue(self::TYPE_APPLICATION, $year),
            $config['sequence_length'],
        );
    }

    /** e.g. KCU/2026/000001 */
    public function admissionNumber(?int $year = null): string
    {
        $config = config('kcu.admission_number');
        $year ??= (int) now()->year;

        return $this->format(
            $config['format'],
            setting('admission_number_prefix', $config['prefix']),
            $year,
            $this->nextValue(self::TYPE_ADMISSION, $year),
            $config['sequence_length'],
        );
    }

    /** A short, non-guessable code printed on generated letters for verification. */
    public function verificationCode(): string
    {
        return strtoupper(bin2hex(random_bytes(6)));
    }

    public function paymentReference(): string
    {
        return sprintf(
            '%s-%s-%s',
            config('kcu.payments.reference_prefix', 'PAY'),
            now()->format('ymd'),
            strtoupper(bin2hex(random_bytes(4)))
        );
    }

    /**
     * Claim the next value for a counter, locking the row so concurrent
     * callers queue rather than collide.
     */
    protected function nextValue(string $type, int $year): int
    {
        return DB::transaction(function () use ($type, $year) {
            $row = DB::table('number_sequences')
                ->where('type', $type)
                ->where('year', $year)
                ->lockForUpdate()
                ->first();

            if (! $row) {
                DB::table('number_sequences')->insert([
                    'type' => $type,
                    'year' => $year,
                    'next_value' => 2,
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);

                return 1;
            }

            DB::table('number_sequences')
                ->where('id', $row->id)
                ->update([
                    'next_value' => $row->next_value + 1,
                    'updated_at' => now(),
                ]);

            return (int) $row->next_value;
        });
    }

    protected function format(string $pattern, string $prefix, int $year, int $sequence, int $length): string
    {
        return strtr($pattern, [
            '{prefix}' => $prefix,
            '{year}' => (string) $year,
            '{sequence}' => str_pad((string) $sequence, $length, '0', STR_PAD_LEFT),
        ]);
    }
}
