<?php

namespace App\Http\Requests\Api\Auth;

use App\Http\Controllers\OtpController;
use App\Models\Datmas\Siswa;
use App\Services\Auth\MobileStudentResolver;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class LoginWithPinRequest extends FormRequest
{
    protected const MAX_ATTEMPTS = 3;
    protected const THROTTLE_DECAY_MINUTES = 1;

    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'phone' => 'required|string|regex:/^[0-9]+$/|min:10|max:15',
            'pin' => 'required|digits:6|numeric',
            'device_name' => 'required|string|max:100',
        ];
    }

    public function messages(): array
    {
        return [
            'phone.regex' => 'Nomor telepon hanya boleh berisi angka.',
            'pin.digits' => 'PIN harus terdiri dari 6 digit angka.',
        ];
    }

    /**
     * @return array{0: \App\Models\Datmas\Indentitas|null, 1: \App\Models\Datmas\Siswa, 2: string, 3: \Illuminate\Database\Eloquent\Collection}
     */
    public function authenticateStudent(): array
    {
        $this->ensureIsNotRateLimited();

        $phone = $this->normalizedPhone();
        $phoneCandidates = $this->phoneCandidates($phone);
        $resolver = app(MobileStudentResolver::class);

        try {
            $students = $resolver->findByPhoneCandidates($phoneCandidates);

            if ($students->isEmpty()) {
                throw ValidationException::withMessages([
                    'phone' => ['Nomor telepon tidak terdaftar.'],
                    'pin' => ['PIN yang Anda masukkan salah.'],
                ]);
            }

            $pinnedStudents = $students->filter(fn (Siswa $student) => $student->has_pin);

            if ($pinnedStudents->isEmpty()) {
                throw ValidationException::withMessages([
                    'pin' => ['PIN belum dibuat untuk akun ini.'],
                ]);
            }

            $matchedStudents = $pinnedStudents->filter(function (Siswa $student) {
                return Hash::check($this->string('pin')->toString(), $student->pin);
            });

            if ($matchedStudents->isEmpty()) {
                $attempts = RateLimiter::attempts($this->throttleKey());
                $remainingAttempts = self::MAX_ATTEMPTS - $attempts - 1;

                RateLimiter::hit($this->throttleKey(), self::THROTTLE_DECAY_MINUTES * 60 * 10);

                $otpSent = false;
                if ($remainingAttempts <= 0) {
                    try {
                        $studentWithIdentity = $students->first(fn (Siswa $student) => filled($student->indentitas?->nouid));

                        if ($studentWithIdentity) {
                            $request = new Request(['phone' => $phone]);
                            app(OtpController::class)->sendOtp($request, $studentWithIdentity->indentitas->nouid);
                            $otpSent = true;
                        }
                    } catch (\Throwable $exception) {
                        Log::warning('Failed sending OTP after mobile login lockout', [
                            'phone' => $phone,
                            'error' => $exception->getMessage(),
                        ]);
                    }
                }

                $exception = ValidationException::withMessages([
                    'pin' => ['PIN yang Anda masukkan salah.'],
                    'remaining' => [max(0, $remainingAttempts)],
                    'message' => [$remainingAttempts > 0
                        ? "PIN yang Anda masukkan salah. Sisa percobaan: {$remainingAttempts}"
                        : ($otpSent
                            ? 'Terlalu banyak percobaan login. OTP telah dikirim jika nomor tersedia.'
                            : 'Terlalu banyak percobaan login. Silakan coba lagi nanti atau gunakan reset PIN.')],
                ]);

                if ($remainingAttempts <= 0) {
                    $exception->status(429);
                }

                throw $exception;
            }

            // Ambil siswa pertama sebagai siswa aktif utama (pemilik token)
            $siswa = $matchedStudents->first();
            $indentitas = $siswa->indentitas;
            $accountId = $resolver->accountId($siswa);

            RateLimiter::clear($this->throttleKey());

            return [$indentitas, $siswa, $accountId, $matchedStudents];
        } catch (ValidationException $exception) {
            throw $exception;
        } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
            throw ValidationException::withMessages([
                'phone' => ['Data siswa tidak ditemukan.'],
            ]);
        } catch (\Throwable $exception) {
            Log::error('Mobile login failed', [
                'phone' => $phone,
                'error' => $exception->getMessage(),
            ]);

            throw ValidationException::withMessages([
                'message' => ['Terjadi kesalahan saat proses login.'],
            ]);
        }
    }

    public function ensureIsNotRateLimited(): void
    {
        if (!RateLimiter::tooManyAttempts($this->throttleKey(), self::MAX_ATTEMPTS)) {
            return;
        }

        event(new Lockout($this));

        $seconds = RateLimiter::availableIn($this->throttleKey());

        throw ValidationException::withMessages([
            'pin' => ['Terlalu banyak percobaan login.'],
            'remaining' => [0],
            'retry_after' => [$seconds],
            'message' => ["Terlalu banyak percobaan login. Silakan coba lagi dalam {$seconds} detik."],
        ])->status(429);
    }

    public function throttleKey(): string
    {
        return Str::transliterate(Str::lower($this->normalizedPhone()).'|'.$this->ip().'|mobile');
    }

    protected function normalizedPhone(): string
    {
        try {
            return formatPhoneNumber($this->string('phone')->toString());
        } catch (ValidationException $exception) {
            throw ValidationException::withMessages([
                'phone' => data_get($exception->errors(), 'tel', ['Nomor telepon tidak valid.']),
            ]);
        }
    }

    protected function phoneCandidates(string $phone): array
    {
        $localNumber = preg_replace('/^62/', '', $phone);

        return array_values(array_unique(array_filter([
            $phone,
            '0'.$localNumber,
            $localNumber,
            '+'.$phone,
        ])));
    }
}
