<?php
/**
 * @author: Uldis Zvirbulis <uldis.zvirbulis@mtcmedia.co.uk>
 * @version: 25.05.2018
 */

namespace Mtc\Plugins\AccountVerifier\Classes;

use Carbon\Carbon;
use Illuminate\Support\Facades\App;
use Mtc\Modules\Members\Models\Member;
use Mtc\Plugins\SMS\Classes\SMS;

class AccountVerifier
{

    public $error = '';
    /**
     * Number of minutes the code expires in
     */
    const VERIFICATION_CODE_EXPIRY_MINUTES = 15;

    /**
     * AccountVerifier constructor.
     */
    public function __construct()
    {

    }

    /**
     * Verifies email address by the code provided
     *
     * @param $code
     * @param Member|null $member
     * @return bool|AccountVerifications
     */
    public function verifyCode($code, Member $member = null)
    {
        $query = (new AccountVerifications())
            ->where('code', $code)
            ->where('created_at', '>', Carbon::now()->subMinutes(self::VERIFICATION_CODE_EXPIRY_MINUTES));
        if (!empty($member->id)) {
            $query->where('member_id', $member->id);
        }
        if ($verification = $query->first()) {
            $verification->verified = Carbon::now();
            $verification->save();
            return $verification;
        }
        return false;
    }

    /**
     * Sends the verification email and saves it in DB
     *
     * @param $member
     * @param $email
     * @return bool
     */
    public function sendVerificationEmail($member, $email)
    {
        if (!self::validateEmail($email)) {
            $this->setError('Please provide a valid email address!');
            return false;
        };

        $twig = App::make('twig');
        $verification_code = self::generateCode(10, false);
        $content = $twig->render('AccountVerifier/emails/verification_email.twig', [
            'member' => $member,
            'verification_code' => $verification_code,
        ]);

        AccountVerifications::query()->create([
            'code' => $verification_code,
            'type' => 'email',
            'email' => $email,
            'member_id' => $member->id,
        ]);

        $subject = config('app.name') . ' Email Verification';

        email($email, $subject, $content);
        return true;
    }

    /**
     * Sends the SMS with the verification code and saves it in DB
     *
     * @param $member
     * @param $phone
     * @return bool
     */
    public function sendVerificationSMS($member, $phone)
    {
        if (!$phone = self::cleanPhoneNumber($phone)) {
            $this->setError('Please provide a valid phone number!');
            return false;
        };

        $verification_code = self::generateCode(6, true);

        AccountVerifications::query()->create([
            'code' => $verification_code,
            'type' => 'phone',
            'phone' => $phone,
            'member_id' => $member->id,
        ]);

        $message = "{$verification_code} is your " . config('app.name') . ' verification code.';

        try {
            SMS::sendMessage($phone, $message);
        } catch (\Exception $e) {
            $this->setError('An error occured. Please make sure the number is correct!');
        }
        return true;
    }

    /**
     * Generates a unique code (within the expiry period)
     *
     * @param int $length
     * @param bool $numbers_only
     * @return int|string
     */
    public static function generateCode($length = 10, $numbers_only = false)
    {
        if ($numbers_only) {
            // E.g., if $length == 3, this will produce a number between 100 and 999
            return mt_rand(10**($length - 1), 10**$length - 1);
        }
        $chars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
        $attempts = 0;
        do {
            $code = '';
            for ($i = 0; $i < $length; $i++) {
                $code .= $chars[mt_rand(0, strlen($chars) - 1)];
            }
            ++$attempts;
        } while (AccountVerifications::codeExists($code) && $attempts < 1000);
        return $code;

    }

    /**
     * Cleans the phone number from white spaces
     *
     * @param $phone
     * @return mixed
     */
    public static function cleanPhoneNumber($phone)
    {
        return str_replace(' ', '', $phone);
    }

    /**
     * Validates email address
     *
     * @param $email
     * @return mixed
     */
    public static function validateEmail($email)
    {
        return filter_var($email, FILTER_VALIDATE_EMAIL);
    }

    /**
     * Error setter
     *
     * @param $message
     */
    private function setError($message)
    {
        $this->error = $message;
    }

    /**
     * Determines whether the Member has been verified
     *
     * @param string $type Values: email / phone
     * @param $member
     * @return bool
     */
    public static function memberVerified($type, $member)
    {
        if (empty($member->id)) {
            return false;
        }

        $query = AccountVerifications::query()
            ->where('type', $type)
            ->whereNotNull('verified');

        if ($type === 'email') {
            $query->where('email', $member->email);
        } elseif ($type === 'phone') {
            // Account for both possibilities - phone includes prefix already or doesn't
            $query->where(function($query) use ($member) {
                $query->where('phone', str_replace(' ', '', $member->contact_no))
                    ->orWhere('phone', str_replace(' ', '', $member->phone_prefix . $member->contact_no));
            });
        }

        return $query->count() > 0;
    }

    /**
     * Whether the email is verified
     *
     * @param $email
     * @return bool
     */
    public static function emailVerified($email)
    {
        if (empty($email)) {
            return false;
        }

        if ($member = (new Member())->where('email', $email)->first()) {
            return $member->isVerified('email');
        }

        return AccountVerifications::query()
                ->where('type', 'email')
                ->where('email', $email)
                ->whereNotNull('verified')
                ->count() > 0;
    }

    /**
     * Whether the phone number is verified
     *
     * @param $phone
     * @return bool
     */
    public static function phoneVerified($phone)
    {
        if (empty($phone)) {
            return false;
        }
        $phone = self::cleanPhoneNumber($phone);
        return AccountVerifications::query()
                ->where('type', 'phone')
                ->where('phone', $phone)
                ->whereNotNull('verified')
                ->count() > 0;
    }
}