<?php

namespace App\Services;

use App\Models\OtpVerification;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

trait OtpVerificationService
{
   /**
    * Store new record
    *
    * @param array $data
    * @return Builder|Model
    */
   public static function store(array $data): Model|Builder
   {
      return OtpVerification::query()->create($data);
   }

   /**
    * Update existing record
    *
    * @param array $data
    * @return mixed
    */
   public function updateService(array $data): mixed
   {
      return tap($this)->update($data);
   }

   /**
    * Create new otp verification entry
    *
    * @param string $otpCode
    * @param string|null $telephone
    * @param string|null $email
    * @param User|null $user
    * @return mixed
    */
   public static function createOrUpdate(string $otpCode, ?string $telephone, ?string $email, User $user = null): mixed
   {
      $result = null;
      $queryBuilder = OtpVerification::query();

      if (filled($telephone)) {
         $result = $queryBuilder->where('telephone', $telephone)->first();
      }

      if (filled($email)) {
         $result = $queryBuilder->where('email', $email)->first();
      }

      $data = [
         'otp' => $otpCode,
         'telephone' => $telephone,
         'email' => $email,
         'send_at' => now(),
         'expired_at' => now()->addMinutes(
            config('torryme.constants.otp_validity')
         ),
         'validated_at' => null,
         'otp_attempts_left' => config('torryme.constants.max_otp_attempts'),
         'user_id' => auth()?->id() ?? $user?->{'id'},
      ];

      if ($result !== null) {
         return $result->updateService($data);
      }

      return self::store($data);
   }

   /**
    * Create new otp verification entry
    *
    * @param string|null $telephone
    * @param string|null $email
    * @return Builder|Model|null
    */
   public static function findByEmailOrTelephone(?string $telephone, ?string $email): Model|Builder|null
   {
      /** @var ?User $user */
      $user = auth()->user();

      return
         OtpVerification::query()
            ->when(filled($telephone), function ($builder) use ($telephone) {
               $builder->where('telephone', $telephone);
            })
            ->when(filled($email), function ($builder) use ($email) {
               $builder->where('email', $email);
            })
            ->when($user !== null, function ($builder) use ($user) {
               $builder->where('user_id', $user->{'id'});
            })
            ->first();
   }

   /**
    * Checks if the code has expired
    *
    * @return bool
    */
   public function expired(): bool
   {
      return now()->greaterThan($this->{'expired_at'});
   }

   /**
    * Check if validated
    *
    * @return bool
    */
   public function isValidated(): bool
   {
      return $this->{'validated_at'} !== null;
   }

   /**
    * Mark as validate
    *
    * @return mixed
    */
   public function markedAsValidate(): mixed
   {
      return $this->updateService([
         'expired_at' => null,
         'validated_at' => now(),
      ]);
   }

   /**
    * Decrement otp attempts
    *
    * @return mixed
    */
   public function decrementAttempt(): mixed
   {
      $attemptsLeft = $this->{'otp_attempts_left'} - config("torryme.constants.default_increment_or_decrement");
      if ($attemptsLeft >= config("torryme.constants.default_zero_number")) {
         $this->updateService(['otp_attempts_left' => $attemptsLeft]);
      }

      return $attemptsLeft;
   }

   /**
    * Rest remaining otp attempts
    *
    */
   public function resetRemainingOtpAttempts()
   {
      $this->updateService(['otp_attempts_left' => config("torryme.constants.max_otp_attempts")]);
   }
}
