<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Admin\Tdaftarexcul;
use App\Models\Admin\Totps;
use App\Models\Admin\Tkelas;
use App\Models\Admin\Tkelsis1;
use App\Models\Admin\Tstopexcul;
use App\Models\Datmas\Indentitas;
use App\Models\Datmas\Siswa;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class MobileExculController extends Controller
{
    protected int $maxActiveExtracurriculars = 2;
    protected int $otpMaxAttempts = 3;
    protected int $otpDecayMinutes = 5;

    public function index(Request $request): JsonResponse
    {
        $siswa = $this->authenticatedStudent($request);
        $accountId = $this->authenticatedAccountId($request);
        $payload = $this->extracurricularPayload($siswa);

        return response()->json([
            'success' => true,
            'message' => 'Data ekstrakurikuler berhasil diambil.',
            'data' => [
                'account_id' => $accountId,
                'nouid' => $siswa->indentitas?->nouid,
                'idsis' => (int) $siswa->id,
                'summary' => $payload['summary'],
                'limits' => $this->limitsPayload(),
                'important_info' => $this->importantInfo(),
                'joined' => $payload['joined'],
                'active' => $payload['active'],
                'waiting' => $payload['waiting'],
                'rejected' => $payload['rejected'],
                'exited' => $payload['exited'],
                'available' => $payload['available'],
                'kegiatan' => $payload['legacy'],
            ],
        ]);
    }

    public function show(Request $request, int $extracurricular): JsonResponse
    {
        $siswa = $this->authenticatedStudent($request);
        $accountId = $this->authenticatedAccountId($request);
        $excul = $this->findActiveExtracurricular($extracurricular);

        if (! $excul) {
            return $this->notFoundResponse();
        }

        $statusIds = $this->statusIds($siswa);
        $registeredCount = $this->registeredCounts(collect([$excul]))->get($excul->id, 0);
        $activeOrWaitingCount = $this->activeOrWaitingCount($siswa, (int) $excul->id);

        return response()->json([
            'success' => true,
            'message' => 'Detail ekstrakurikuler berhasil diambil.',
            'data' => [
                'account_id' => $accountId,
                'nouid' => $siswa->indentitas?->nouid,
                'idsis' => (int) $siswa->id,
                'extracurricular' => $this->transformExtracurricular(
                    $excul,
                    $statusIds,
                    (int) $registeredCount,
                    $activeOrWaitingCount
                ),
                'registrations' => $this->registrationHistory($siswa, (int) $excul->id),
                'stops' => $this->stopHistory($siswa, (int) $excul->id),
                'limits' => $this->limitsPayload(),
                'important_info' => $this->importantInfo(),
            ],
        ]);
    }

    public function register(Request $request, int $extracurricular): JsonResponse
    {
        $siswa = $this->authenticatedStudent($request);
        $excul = $this->findActiveExtracurricular($extracurricular);

        if (! $excul) {
            return $this->notFoundResponse();
        }

        [$month, $note] = $this->validatedMonthAndNote(
            $request,
            'bulan_mulai',
            'Bulan mulai wajib diisi dengan format YYYY-MM.',
            'Motivasi mendaftar wajib diisi.'
        );

        $monthDate = Carbon::createFromFormat('Y-m', $month)->startOfMonth();
        $this->ensureCanRegister($siswa, $excul, $monthDate);

        // Ambil tokenOwner sebagai sumber idmen fallback.
        // Sibling yang belum daftar mobile mandiri tidak punya tindentitas sendiri,
        // sehingga idmen diambil dari pemilik token (akun keluarga yang sama).
        $tokenOwner = $request->attributes->get('auth_token_owner');

        DB::transaction(function () use ($siswa, $excul, $monthDate, $note, $tokenOwner) {
            $siswa->loadMissing(['indentitas', 'kelsis']);

            $tkelsis = Tkelsis1::firstOrNew([
                'ids' => $siswa->id,
                'idkel' => $excul->id,
            ]);

            $tkelsis->tin = $siswa->kelsis?->tin;
            $tkelsis->idta = $siswa->kelsis?->idta;

            // Prioritas idmen: (1) dari indentitas siswa sendiri jika ada,
            // (2) dari pemilik token (fallback untuk sibling tanpa tindentitas).
            // (3) default ke 263 jika keduanya null.
            $tkelsis->idmen = $siswa->indentitas?->idmen
                ?? $tokenOwner?->indentitas?->idmen
                ?? 263;

            $tkelsis->idtra = random_int(1, 999);
            $tkelsis->sta = 0;
            $tkelsis->save();

            Tdaftarexcul::create([
                'tin' => $siswa->kelsis?->tin,
                'idta' => $siswa->kelsis?->idta,
                'idsis' => $siswa->id,
                'tgl' => $monthDate->format('Y-m-d'),
                'idkel' => $excul->id,
                'ket' => $note,
                'sta' => 0,
            ]);
        });

        return response()->json([
            'success' => true,
            'message' => 'Pendaftaran ekstrakurikuler berhasil dikirim untuk bulan '.$monthDate->locale('id')->translatedFormat('F Y').'.',
            'data' => [
                'extracurricular_id' => (int) $excul->id,
                'bulan_mulai' => $monthDate->format('Y-m'),
                'bulan_mulai_label' => $monthDate->locale('id')->translatedFormat('F Y'),
                'tgl_mulai' => $monthDate->format('Y-m-d'),
                'month' => $monthDate->format('Y-m'),
                'status_code' => 0,
                'status' => 'waiting',
                'status_label' => 'Menunggu',
            ],
        ], 201);
    }

    public function sendLeaveOtp(Request $request, int $extracurricular): JsonResponse
    {
        $siswa = $this->authenticatedStudent($request);
        $excul = $this->findActiveExtracurricular($extracurricular);

        if (! $excul) {
            return $this->notFoundResponse();
        }

        $month = $this->validatedMonth(
            $request,
            'bulan_berhenti',
            'Bulan berhenti wajib diisi dengan format YYYY-MM.'
        );
        $monthDate = Carbon::createFromFormat('Y-m', $month)->startOfMonth();

        $phone = $this->otpPhone($siswa);
        $rateKey = 'mobile-excul-leave-otp:'.$phone;

        if (RateLimiter::tooManyAttempts($rateKey, $this->otpMaxAttempts)) {
            throw ValidationException::withMessages([
                'phone' => ['Terlalu banyak permintaan OTP. Silakan coba lagi nanti.'],
            ]);
        }

        $otp = (string) random_int(100000, 999999);
        $expiresAt = now()->addMinutes(5);

        Totps::query()->updateOrCreate(
            ['phone' => $phone],
            [
                'otp' => $otp,
                'expires_at' => $expiresAt,
                'attempts' => 0,
                'verified_at' => null,
            ]
        );

        $this->sendOtpMessage(
            $phone,
            "Kode OTP keluar ekstrakurikuler {$excul->nam}: {$otp}, berlaku 5 menit."
        );

        RateLimiter::hit($rateKey, $this->otpDecayMinutes * 60);

        return response()->json([
            'success' => true,
            'message' => 'OTP keluar ekstrakurikuler berhasil dikirim ke WhatsApp.',
            'data' => [
                'extracurricular_id' => (int) $excul->id,
                'bulan_berhenti' => $monthDate->format('Y-m'),
                'bulan_berhenti_label' => $monthDate->locale('id')->translatedFormat('F Y'),
                'phone' => $phone,
                'expires_at' => $expiresAt->toIso8601String(),
            ],
        ]);
    }

    public function leave(Request $request, int $extracurricular): JsonResponse
    {
        $siswa = $this->authenticatedStudent($request);
        $excul = $this->findActiveExtracurricular($extracurricular);

        if (! $excul) {
            return $this->notFoundResponse();
        }

        [$month, $reason] = $this->validatedMonthAndNote(
            $request,
            'bulan_berhenti',
            'Bulan berhenti wajib diisi dengan format YYYY-MM.',
            'Alasan berhenti wajib diisi.'
        );

        $otp = $request->validate([
            'otp' => ['required', 'string', 'digits:6'],
        ])['otp'];

        $monthDate = Carbon::createFromFormat('Y-m', $month)->startOfMonth();

        $monthlyRecord = $this->activeMonthlyRecord($siswa, $excul, $monthDate);

        if (! $monthlyRecord) {
            return $this->monthlyRecordNotFoundResponse($monthDate);
        }

        $phone = $this->otpPhone($siswa);
        $otpRecord = $this->verifyLeaveOtp($phone, $otp);

        DB::transaction(function () use ($siswa, $excul, $monthDate, $reason, $monthlyRecord, $otpRecord) {
            $siswa->loadMissing(['indentitas', 'kelsis']);

            $monthlyRecord->update(['sta' => -2]);

            Tstopexcul::create([
                'tin' => $siswa->kelsis?->tin,
                'idta' => $siswa->kelsis?->idta,
                'idsis' => $siswa->id,
                'tgl' => now(),
                'idkel' => $excul->id,
                'ala' => $reason,
                'idks1' => $monthlyRecord->id,
                'tglex' => $monthDate->format('Y-m-d'),
            ]);

            Tkelsis1::query()
                ->where('ids', $siswa->id)
                ->where('idkel', $excul->id)
                ->update(['sta' => -2]);

            $otpRecord->delete();
        });

        RateLimiter::clear('mobile-excul-leave-verify:'.$phone);

        return response()->json([
            'success' => true,
            'message' => 'Pengajuan keluar ekstrakurikuler berhasil dikirim untuk bulan '.$monthDate->locale('id')->translatedFormat('F Y').'.',
            'data' => [
                'extracurricular_id' => (int) $excul->id,
                'bulan_berhenti' => $monthDate->format('Y-m'),
                'bulan_berhenti_label' => $monthDate->locale('id')->translatedFormat('F Y'),
                'tgl_berhenti' => $monthDate->format('Y-m-d'),
                'month' => $monthDate->format('Y-m'),
                'status_code' => -2,
                'status' => 'exited',
                'status_label' => 'Keluar',
            ],
        ]);
    }

    protected function extracurricularPayload(Siswa $siswa): array
    {
        $excul = $this->activeExtracurriculars();
        $statusIds = $this->statusIds($siswa);
        $registeredCounts = $this->registeredCounts($excul);
        $activeOrWaitingCount = $this->activeOrWaitingCount($siswa);

        $available = $excul
            ->map(fn (Tkelas $item) => $this->transformExtracurricular(
                $item,
                $statusIds,
                (int) $registeredCounts->get($item->id, 0),
                $activeOrWaitingCount
            ))
            ->values()
            ->all();

        $grouped = collect($available)->groupBy('status');

        return [
            'summary' => [
                'active_count' => count($statusIds['sub']),
                'waiting_count' => count($statusIds['waiting']),
                'registered_count' => $activeOrWaitingCount,
                'max_registered' => $this->maxActiveExtracurriculars,
                'can_register_more' => $activeOrWaitingCount < $this->maxActiveExtracurriculars,
            ],
            'joined' => collect($grouped->get('active', collect()))
                ->merge($grouped->get('waiting', collect()))
                ->values()
                ->all(),
            'active' => $grouped->get('active', collect())->values()->all(),
            'waiting' => $grouped->get('waiting', collect())->values()->all(),
            'rejected' => $grouped->get('rejected', collect())->values()->all(),
            'exited' => $grouped->get('exited', collect())->values()->all(),
            'available' => $available,
            'legacy' => [
                'sub' => $statusIds['sub'],
                'waiting' => $statusIds['waiting'],
                'rejected' => $statusIds['rejected'],
                'exited' => $statusIds['exited'],
                'excul' => $available,
            ],
        ];
    }

    protected function activeExtracurriculars(): Collection
    {
        return Tkelas::query()
            ->with('jenis')
            ->whereHas('jenis', function ($query) {
                $query->where('tip', 1)->where('sta', 0);
            })
            ->where('idta', idta()->id)
            ->orderBy('nam')
            ->get();
    }

    protected function findActiveExtracurricular(int $id): ?Tkelas
    {
        return Tkelas::query()
            ->with('jenis')
            ->whereHas('jenis', function ($query) {
                $query->where('tip', 1)->where('sta', 0);
            })
            ->where('idta', idta()->id)
            ->whereKey($id)
            ->first();
    }

    protected function statusIds(Siswa $siswa): array
    {
        $subscriptions = Tkelsis1::query()
            ->where('ids', $siswa->id)
            ->get(['idkel', 'sta']);

        return [
            'sub' => $subscriptions->whereIn('sta', [1])->pluck('idkel')->map(fn ($id) => (int) $id)->unique()->values()->all(),
            'waiting' => $subscriptions->where('sta', 0)->pluck('idkel')->map(fn ($id) => (int) $id)->unique()->values()->all(),
            'rejected' => $subscriptions->where('sta', -1)->pluck('idkel')->map(fn ($id) => (int) $id)->unique()->values()->all(),
            'exited' => $subscriptions->where('sta', -2)->pluck('idkel')->map(fn ($id) => (int) $id)->unique()->values()->all(),
        ];
    }

    protected function registeredCounts(Collection $excul): Collection
    {
        $ids = $excul->pluck('id')->all();

        if (empty($ids)) {
            return collect();
        }

        return Tkelsis1::query()
            ->whereIn('idkel', $ids)
            ->whereIn('sta', [0, 1])
            ->select('idkel', DB::raw('count(*) as count'))
            ->groupBy('idkel')
            ->pluck('count', 'idkel');
    }

    protected function transformExtracurricular(
        Tkelas $excul,
        array $statusIds,
        int $registeredCount,
        int $activeOrWaitingCount
    ): array {
        $status = $this->statusFor((int) $excul->id, $statusIds);
        $quota = $excul->qty !== null ? (int) $excul->qty : null;
        $isFull = $quota !== null && $quota > 0 && $registeredCount >= $quota;
        $hasJoinedThisExcul = in_array((int) $excul->id, [...$statusIds['sub'], ...$statusIds['waiting']], true);
        $canRegister = in_array($status['status'], ['none', 'rejected', 'exited'], true)
            && (! $isFull || $hasJoinedThisExcul)
            && ($hasJoinedThisExcul || $activeOrWaitingCount < $this->maxActiveExtracurriculars);
        $paymentType = Str::contains(Str::lower((string) $excul->nam), 'bimbel') ? 'paid' : 'free';
        $schedule = $this->scheduleFromDescription($excul->ket ? (string) $excul->ket : null);

        return [
            'id' => (int) $excul->id,
            'name' => (string) $excul->nam,
            'description' => $excul->ket ? (string) $excul->ket : null,
            'quota' => $quota,
            'registered' => $registeredCount,
            'registered_count' => $registeredCount,
            'is_full' => $isFull,
            'status_code' => $status['code'],
            'status' => $status['status'],
            'status_label' => $status['label'],
            'status_text' => $status['label'],
            'can_register' => $canRegister,
            'can_leave' => $status['status'] === 'active',
            'payment_type' => $paymentType,
            'payment_label' => $paymentType === 'paid' ? 'Berbayar' : 'Gratis',
            'day' => $schedule['day'],
            'time' => $schedule['time'],
            'coach' => $schedule['coach'],
            'schedule' => [
                'day' => $schedule['day'],
                'time' => $schedule['time'],
                'label' => $schedule['label'],
            ],
        ];
    }

    protected function scheduleFromDescription(?string $description): array
    {
        $payload = [
            'day' => null,
            'time' => null,
            'coach' => null,
            'label' => null,
        ];

        if (! $description) {
            return $payload;
        }

        if (preg_match('/jadwal=([^;]+)/i', $description, $scheduleMatch)) {
            $schedule = trim($scheduleMatch[1]);

            if (preg_match('/^([A-Za-z]+)\s+(.+)$/', $schedule, $parts)) {
                $payload['day'] = $parts[1];
                $payload['time'] = trim($parts[2]);
            } else {
                $payload['label'] = $schedule;
            }
        }

        if (preg_match('/pelatih=([^;]+)/i', $description, $coachMatch)) {
            $payload['coach'] = trim($coachMatch[1]);
        }

        if ($payload['day'] && $payload['time']) {
            $payload['label'] = $payload['day'].', '.$payload['time'];
        }

        return $payload;
    }

    protected function statusFor(int $exculId, array $statusIds): array
    {
        if (in_array($exculId, $statusIds['sub'], true)) {
            return ['code' => 1, 'status' => 'active', 'label' => 'Aktif'];
        }

        if (in_array($exculId, $statusIds['waiting'], true)) {
            return ['code' => 0, 'status' => 'waiting', 'label' => 'Menunggu'];
        }

        if (in_array($exculId, $statusIds['rejected'], true)) {
            return ['code' => -1, 'status' => 'rejected', 'label' => 'Ditolak'];
        }

        if (in_array($exculId, $statusIds['exited'], true)) {
            return ['code' => -2, 'status' => 'exited', 'label' => 'Keluar'];
        }

        return ['code' => null, 'status' => 'none', 'label' => 'Belum Terdaftar'];
    }

    protected function activeOrWaitingCount(Siswa $siswa, ?int $exceptExculId = null): int
    {
        return Tkelsis1::query()
            ->where('ids', $siswa->id)
            ->whereIn('sta', [0, 1])
            ->when($exceptExculId !== null, fn ($query) => $query->where('idkel', '!=', $exceptExculId))
            ->count();
    }

    protected function ensureCanRegister(Siswa $siswa, Tkelas $excul, Carbon $monthDate): void
    {
        $existingMonthly = Tdaftarexcul::query()
            ->where('idsis', $siswa->id)
            ->where('idkel', $excul->id)
            ->whereYear('tgl', $monthDate->year)
            ->whereMonth('tgl', $monthDate->month)
            ->whereIn('sta', [0, 1])
            ->first();

        if ($existingMonthly) {
            throw ValidationException::withMessages([
                'bulan_mulai' => ['Anda sudah terdaftar pada ekstrakurikuler ini untuk bulan '.$monthDate->locale('id')->translatedFormat('F Y').'.'],
            ]);
        }

        $hasJoinedThisExcul = Tkelsis1::query()
            ->where('ids', $siswa->id)
            ->where('idkel', $excul->id)
            ->whereIn('sta', [0, 1])
            ->exists();

        $registeredCount = (int) Tkelsis1::query()
            ->where('idkel', $excul->id)
            ->whereIn('sta', [0, 1])
            ->count();

        $quota = $excul->qty !== null ? (int) $excul->qty : null;

        if (! $hasJoinedThisExcul && $quota !== null && $quota > 0 && $registeredCount >= $quota) {
            throw ValidationException::withMessages([
                'excul' => ['Kuota ekstrakurikuler sudah penuh.'],
            ]);
        }

        if (! $hasJoinedThisExcul && $this->activeOrWaitingCount($siswa) >= $this->maxActiveExtracurriculars) {
            throw ValidationException::withMessages([
                'excul' => ['Setiap siswa hanya boleh mengikuti maksimal '.$this->maxActiveExtracurriculars.' ekstrakurikuler.'],
            ]);
        }
    }

    protected function validatedMonthAndNote(
        Request $request,
        string $monthField,
        string $monthMessage,
        string $noteMessage
    ): array {
        $rules = [
            $monthField => ['sometimes', 'date_format:Y-m'],
            'tgl' => ['sometimes', 'date_format:Y-m'],
            'month' => ['sometimes', 'date_format:Y-m'],
            'ket' => ['sometimes', 'string', 'max:500'],
            'note' => ['sometimes', 'string', 'max:500'],
            'reason' => ['sometimes', 'string', 'max:500'],
        ];

        $validated = $request->validate($rules);

        $month = $validated[$monthField] ?? $validated['tgl'] ?? $validated['month'] ?? null;
        $note = $validated['ket'] ?? $validated['note'] ?? $validated['reason'] ?? null;

        if (! $month) {
            throw ValidationException::withMessages([
                $monthField => [$monthMessage],
            ]);
        }

        if (! is_string($note) || trim($note) === '') {
            throw ValidationException::withMessages([
                'ket' => [$noteMessage],
            ]);
        }

        return [$month, trim($note)];
    }

    protected function validatedMonth(Request $request, string $monthField, string $monthMessage): string
    {
        $validated = $request->validate([
            $monthField => ['sometimes', 'date_format:Y-m'],
            'tgl' => ['sometimes', 'date_format:Y-m'],
            'month' => ['sometimes', 'date_format:Y-m'],
        ]);

        $month = $validated[$monthField] ?? $validated['tgl'] ?? $validated['month'] ?? null;

        if (! $month) {
            throw ValidationException::withMessages([
                $monthField => [$monthMessage],
            ]);
        }

        return $month;
    }

    protected function activeMonthlyRecord(Siswa $siswa, Tkelas $excul, Carbon $monthDate): ?Tdaftarexcul
    {
        return Tdaftarexcul::query()
            ->where('idsis', $siswa->id)
            ->where('idkel', $excul->id)
            ->whereYear('tgl', $monthDate->year)
            ->whereMonth('tgl', $monthDate->month)
            ->whereIn('sta', [0, 1])
            ->first();
    }

    protected function monthlyRecordNotFoundResponse(Carbon $monthDate): JsonResponse
    {
        return response()->json([
            'success' => false,
            'message' => 'Tidak ditemukan pendaftaran aktif untuk bulan '.$monthDate->locale('id')->translatedFormat('F Y').'.',
        ], 404);
    }

    protected function otpPhone(Siswa $siswa): string
    {
        $siswa->loadMissing(['indentitas.registrasi']);

        $phone = $siswa->tel ?: $siswa->indentitas?->registrasi?->tel;

        if (! $phone) {
            throw ValidationException::withMessages([
                'phone' => ['Nomor WhatsApp belum terdaftar pada akun ini.'],
            ]);
        }

        return formatPhoneNumber((string) $phone);
    }

    protected function verifyLeaveOtp(string $phone, string $otp): Totps
    {
        $rateKey = 'mobile-excul-leave-verify:'.$phone;

        if (RateLimiter::tooManyAttempts($rateKey, $this->otpMaxAttempts)) {
            throw ValidationException::withMessages([
                'otp' => ['Terlalu banyak percobaan. Silakan coba lagi nanti.'],
            ]);
        }

        $record = Totps::query()->where('phone', $phone)->first();

        if (! $record) {
            RateLimiter::hit($rateKey, $this->otpDecayMinutes * 60);

            throw ValidationException::withMessages([
                'otp' => ['OTP tidak ditemukan. Silakan kirim OTP terlebih dahulu.'],
            ]);
        }

        if (now()->gt($record->expires_at)) {
            $record->delete();

            throw ValidationException::withMessages([
                'otp' => ['OTP telah kedaluwarsa. Silakan kirim OTP ulang.'],
            ]);
        }

        if ((string) $record->otp !== $otp) {
            $record->increment('attempts');
            $remainingAttempts = max(0, $this->otpMaxAttempts - (int) $record->attempts);
            RateLimiter::hit($rateKey, $this->otpDecayMinutes * 60);

            throw ValidationException::withMessages([
                'otp' => ["OTP salah. Sisa percobaan: {$remainingAttempts}"],
                'remaining' => [$remainingAttempts],
            ]);
        }

        $record->forceFill(['verified_at' => now()])->save();

        return $record;
    }

    protected function sendOtpMessage(string $phone, string $message): void
    {
        $userCode = config('wapi.user');
        $deviceId = config('wapi.device');
        $secret = config('wapi.secret');

        if (blank($userCode) || blank($deviceId) || blank($secret)) {
            Log::error('Missing WAPI configuration for extracurricular leave OTP', [
                'has_user_code' => filled($userCode),
                'has_device_id' => filled($deviceId),
                'has_secret' => filled($secret),
            ]);

            throw ValidationException::withMessages([
                'message' => ['Layanan OTP belum terkonfigurasi.'],
            ]);
        }

        $payload = [
            'user_code' => $userCode,
            'device_id' => $deviceId,
            'receiver' => $phone,
            'message' => $message,
            'secret' => $secret,
        ];

        try {
            $response = Http::timeout(30)->post(config('wapi.url'), $payload);

            Log::info('Extracurricular leave OTP provider response', [
                'phone' => $phone,
                'status' => $response->status(),
                'response' => $response->body(),
                'payload' => $payload,
            ]);

            if (! $response->successful()) {
                $providerMessage = data_get($response->json(), 'message')
                    ?: 'Gagal mengirim OTP. Silakan coba lagi.';

                throw ValidationException::withMessages([
                    'message' => [$providerMessage],
                ]);
            }
        } catch (ValidationException $exception) {
            throw $exception;
        } catch (\Throwable $exception) {
            Log::error('Failed sending extracurricular leave OTP', [
                'phone' => $phone,
                'payload' => $payload,
                'error' => $exception->getMessage(),
                'trace' => $exception->getTraceAsString(),
            ]);

            throw ValidationException::withMessages([
                'message' => ['Gagal mengirim OTP. Silakan coba lagi.'],
            ]);
        }
    }

    protected function registrationHistory(Siswa $siswa, int $exculId): array
    {
        return Tdaftarexcul::query()
            ->where('idsis', $siswa->id)
            ->where('idkel', $exculId)
            ->orderByDesc('tgl')
            ->limit(12)
            ->get()
            ->map(function (Tdaftarexcul $record) {
                $monthDate = $record->tgl ? Carbon::parse($record->tgl) : null;

                return [
                    'id' => (int) $record->id,
                    'bulan_mulai' => $monthDate?->format('Y-m'),
                    'bulan_mulai_label' => $monthDate?->locale('id')->translatedFormat('F Y'),
                    'tgl_mulai' => $monthDate?->format('Y-m-d'),
                    'month' => $monthDate?->format('Y-m'),
                    'date' => $monthDate?->format('Y-m-d'),
                    'note' => $record->ket,
                    'status_code' => $record->sta !== null ? (int) $record->sta : null,
                    'status' => $this->statusLabel((int) $record->sta)['status'],
                    'status_label' => $this->statusLabel((int) $record->sta)['label'],
                ];
            })
            ->values()
            ->all();
    }

    protected function stopHistory(Siswa $siswa, int $exculId): array
    {
        return Tstopexcul::query()
            ->where('idsis', $siswa->id)
            ->where('idkel', $exculId)
            ->orderByDesc('tgl')
            ->limit(12)
            ->get()
            ->map(function (Tstopexcul $record) {
                $stopMonthDate = $record->tglex ? Carbon::parse($record->tglex) : null;

                return [
                    'id' => (int) $record->id,
                    'requested_at' => $record->tgl ? Carbon::parse($record->tgl)->toDateTimeString() : null,
                    'bulan_berhenti' => $stopMonthDate?->format('Y-m'),
                    'bulan_berhenti_label' => $stopMonthDate?->locale('id')->translatedFormat('F Y'),
                    'tgl_berhenti' => $stopMonthDate?->format('Y-m-d'),
                    'month' => $stopMonthDate?->format('Y-m'),
                    'reason' => $record->ala,
                    'registration_id' => $record->idks1 ? (int) $record->idks1 : null,
                    'status_code' => $record->sta !== null ? (int) $record->sta : null,
                    'status' => $record->sta !== null ? $this->statusLabel((int) $record->sta)['status'] : null,
                    'status_label' => $record->sta !== null ? $this->statusLabel((int) $record->sta)['label'] : null,
                ];
            })
            ->values()
            ->all();
    }

    protected function statusLabel(int $status): array
    {
        return match ($status) {
            1 => ['status' => 'active', 'label' => 'Aktif'],
            0 => ['status' => 'waiting', 'label' => 'Menunggu'],
            -1 => ['status' => 'rejected', 'label' => 'Ditolak'],
            -2 => ['status' => 'exited', 'label' => 'Keluar'],
            default => ['status' => 'unknown', 'label' => 'Tidak Diketahui'],
        };
    }

    protected function limitsPayload(): array
    {
        return [
            'max_registered' => $this->maxActiveExtracurriculars,
            'registration_period' => 'monthly',
            'registration_month_format' => 'YYYY-MM',
            'minimum_attendance_percent_for_certificate' => 80,
        ];
    }

    protected function importantInfo(): array
    {
        return [
            'Pendaftaran dilakukan per bulan (misal: Juli 2024).',
            'Setiap siswa boleh mengikuti maksimal 2 ekstrakurikuler.',
            'Pembayaran dilakukan setelah pendaftaran disetujui.',
            'Kehadiran minimal 80% untuk mendapatkan sertifikat.',
        ];
    }

    protected function authenticatedAccountId(Request $request): string
    {
        return (string) $request->attributes->get('auth_account_id');
    }

    protected function authenticatedStudent(Request $request): Siswa
    {
        $siswa = $request->attributes->get('auth_siswa');
        $siswa->loadMissing(['indentitas', 'kelsis']);

        return $siswa;
    }

    protected function notFoundResponse(): JsonResponse
    {
        return response()->json([
            'success' => false,
            'message' => 'Ekstrakurikuler tidak ditemukan atau belum aktif.',
        ], 404);
    }
}
