<?php

namespace App\Services;

use App\Models\User;
use App\Models\OtpVerification;
use App\Models\SystemSetting;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Cache;
use App\Mail\OtpVerificationMail;

class OtpService
{
    /**
     * OTP length.
     */
    const OTP_LENGTH = 6;

    /**
     * OTP expiration time in minutes.
     */
    const OTP_EXPIRATION = 10;

    /**
     * Maximum verification attempts.
     */
    const MAX_ATTEMPTS = 3;

    /**
     * Check if OTP is enabled system-wide.
     *
     * @return bool
     */
    public function isEnabled(): bool
    {
        return (bool) SystemSetting::get('otp_enabled', false);
    }

    /**
     * Toggle OTP system-wide.
     *
     * @param bool $enabled
     * @return void
     */
    public function toggle(bool $enabled): void
    {
        SystemSetting::set(
            'otp_enabled',
            $enabled,
            SystemSetting::TYPE_BOOLEAN,
            SystemSetting::GROUP_OTP,
            'Enable or disable OTP verification for all logins',
            false
        );
    }

    /**
     * Generate a new OTP for a user.
     *
     * @param User $user
     * @param string $type
     * @return string
     */
    public function generate(User $user, string $type = OtpVerification::TYPE_LOGIN): string
    {
        // Invalidate any existing OTPs of the same type
        OtpVerification::where('user_id', $user->id)
            ->where('type', $type)
            ->whereNull('verified_at')
            ->update(['verified_at' => now()]);

        // Generate new OTP
        $otp = str_pad(random_int(0, pow(10, self::OTP_LENGTH) - 1), self::OTP_LENGTH, '0', STR_PAD_LEFT);

        OtpVerification::create([
            'user_id' => $user->id,
            'otp_code' => $otp,
            'type' => $type,
            'expires_at' => now()->addMinutes(self::OTP_EXPIRATION),
            'attempts' => 0,
        ]);

        return $otp;
    }

    /**
     * Send OTP to user's email.
     *
     * @param User $user
     * @param string $otp
     * @return void
     */
    public function send(User $user, string $otp): void
    {
        Mail::to($user->email)->send(new OtpVerificationMail($user, $otp));
    }

    /**
     * Generate and send OTP in one step.
     *
     * @param User $user
     * @param string $type
     * @return string
     */
    public function generateAndSend(User $user, string $type = OtpVerification::TYPE_LOGIN): string
    {
        $otp = $this->generate($user, $type);
        $this->send($user, $otp);
        return $otp;
    }

    /**
     * Verify an OTP code.
     *
     * @param User $user
     * @param string $otp
     * @param string $type
     * @return array{success: bool, message: string}
     */
    public function verify(User $user, string $otp, string $type = OtpVerification::TYPE_LOGIN): array
    {
        $verification = OtpVerification::where('user_id', $user->id)
            ->where('type', $type)
            ->where('otp_code', $otp)
            ->whereNull('verified_at')
            ->first();

        if (!$verification) {
            return [
                'success' => false,
                'message' => 'Invalid OTP code.',
            ];
        }

        // Check if locked
        if ($verification->isLocked()) {
            return [
                'success' => false,
                'message' => 'Too many failed attempts. Please request a new OTP.',
            ];
        }

        // Check if expired
        if ($verification->isExpired()) {
            return [
                'success' => false,
                'message' => 'OTP has expired. Please request a new one.',
            ];
        }

        // Increment attempts
        $verification->increment('attempts');

        // Verify
        $verification->update(['verified_at' => now()]);

        return [
            'success' => true,
            'message' => 'OTP verified successfully.',
        ];
    }

    /**
     * Check if OTP is expired.
     *
     * @param OtpVerification $verification
     * @return bool
     */
    public function isExpired(OtpVerification $verification): bool
    {
        return $verification->expires_at->isPast();
    }

    /**
     * Clean up expired OTP records.
     *
     * @return int Number of records deleted
     */
    public function cleanup(): int
    {
        return OtpVerification::where('created_at', '<', now()->subHours(24))
            ->delete();
    }

    /**
     * Get remaining attempts for the current OTP.
     *
     * @param User $user
     * @param string $type
     * @return int
     */
    public function remainingAttempts(User $user, string $type = OtpVerification::TYPE_LOGIN): int
    {
        $verification = OtpVerification::where('user_id', $user->id)
            ->where('type', $type)
            ->whereNull('verified_at')
            ->latest()
            ->first();

        if (!$verification) {
            return self::MAX_ATTEMPTS;
        }

        return max(0, self::MAX_ATTEMPTS - $verification->attempts);
    }

    /**
     * Check if user has a valid pending OTP.
     *
     * @param User $user
     * @param string $type
     * @return bool
     */
    public function hasPendingOtp(User $user, string $type = OtpVerification::TYPE_LOGIN): bool
    {
        return OtpVerification::where('user_id', $user->id)
            ->where('type', $type)
            ->whereNull('verified_at')
            ->where('expires_at', '>', now())
            ->exists();
    }

    /**
     * Get seconds until OTP expires.
     *
     * @param User $user
     * @param string $type
     * @return int
     */
    public function expiresInSeconds(User $user, string $type = OtpVerification::TYPE_LOGIN): int
    {
        $verification = OtpVerification::where('user_id', $user->id)
            ->where('type', $type)
            ->whereNull('verified_at')
            ->where('expires_at', '>', now())
            ->latest()
            ->first();

        if (!$verification) {
            return 0;
        }

        return max(0, now()->diffInSeconds($verification->expires_at));
    }
}
