<?php

namespace App\Http\Controllers;

use App\Helpers\SocialHelper\AppleLogin;
use App\Helpers\SocialHelper\FacebookLogin;
use App\Helpers\SocialHelper\GoogleLogin;
use App\Helpers\SocialHelper\SocialLoginInterface;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Facades\JWTFactory;
use Yajny\EventsYj\Events\SendOTP;
use Yajny\Helpers\PostBackIntegration;
use Yajny\MailYj\Mails\PasswordResetOTP;
use Yajny\Member\Models\Member;
use Yajny\Member\Models\OtpVerification;
use Yajny\Member\Models\RegistrationCode;
use Yajny\Member\Models\UserLogin;
use Yajny\Models\Country;

class AuthController extends Controller
{
    /**
     * @param Request $request
     * @return array
     * @throws ValidationException
     */
    public function login(Request $request)
    {

        Validator::make($request->all(), [
            'email' => 'required|email|exists:users,email',
            'password' => 'required'
        ])->validate();

        $member = Member::where('email', $request->email)->first();

        if (Hash::check($request->password, $member->password)) {

            if ($member->active != 1)
                return response()->json(['error' => 'account_suspended'], 406);

            if ($request->region)
                $member->app_region = $request->region;

            if ($request->lang)
                $member->app_lang = $request->lang;

            if ($request->device_advertising_id)
                $member->device_advertising_id = $request->device_advertising_id;
            if ($request->extension) {
                $member->logins()->save(new UserLogin(['user_email' => $member->email, 'referrer_id' => $member->referrer_id, 'action' => "yajny_extension", 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent()]));
            }
            $member->logins()->save(new UserLogin(['user_email' => $member->email, 'referrer_id' => $member->referrer_id, 'action' => "login", 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent()]));

            $member->save();

            return ['is_new_user' => false, 'user' => $member];
        } else
            throw ValidationException::withMessages(['password' => ['invalid']]);
    }

    /**
     * @param Request $request
     * @return array
     */
    public function socialLogin(Request $request)
    {
        Validator::make($request->all(), [
            'email' => 'required|email'
        ])->validate();

        // TODO: [REFACTOR] Check Social ID
        $member = Member::where('email', $request->email)->first();
        if ($member) {
            if ($member->active != 1)
                return response()->json(['error' => 'account_suspended'], 406);

            if (empty($member->gender))
                $member->gender = $request->gender ?? null;

            if (empty($member->photo))
                $member->photo = $request->photo ?? null;

            if ($request->region)
                $member->app_region = $request->region;

            if ($request->lang)
                $member->app_lang = $request->lang;

            if ($request->device_advertising_id)
                $member->device_advertising_id = $request->device_advertising_id;

            $member->save();
            return ['is_new_user' => false, 'user' => $member];
        } else {
            return ['is_new_user' => true, 'user' => $this->commonCreateUser($request)];
        }
    }

    /**
     * @param Request $request
     * @return array
     */
    public function facebookLogin(Request $request)
    {
        Validator::make($request->all(), [
            'access_token' => 'required',
        ])->validate();
        return $this->handleSocialLogin(new FacebookLogin($request->access_token), $request);
    }

    /**
     * @param Request $request
     * @return array
     */
    public function googleLogin(Request $request)
    {
        Validator::make($request->all(), [
            'access_token' => 'required',
        ])->validate();
        return $this->handleSocialLogin(new GoogleLogin($request->access_token), $request);
    }

    /**
     * @param Request $request
     * @return array
     */
    public function appleLogin(Request $request)
    {
        Validator::make($request->all(), [
            'identity_token' => 'required',
        ])->validate();
        return $this->handleSocialLogin(new AppleLogin($request->identity_token), $request);
    }

    /**
     * @param SocialLoginInterface $socialLogin
     * @param Request $request
     * @return array
     */
    private function handleSocialLogin(SocialLoginInterface $socialLogin, Request $request)
    {
        $userData = $socialLogin->getUser();
        if (empty($userData))
            abort(401);
        $request->merge($socialLogin->mapUserData($userData));
        return $this->socialLogin($request);
    }

    /**
     * @param Request $request
     * @return array
     * @throws ValidationException
     */
    public function registerUser(Request $request)
    {
        Validator::make($request->all(), [
            'email' => 'required|email|unique:users,email',
            'password' => 'required|min:8',
        ])->validate();

        return ['is_new_user' => true, 'user' => $this->commonCreateUser($request)];
    }

    /**
     * @param Request $request
     * @return array[]|\Illuminate\Http\JsonResponse
     */
    public function refreshToken(Request $request)
    {
        Validator::make($request->all(), [
            'refresh_token' => 'required'
        ])->validate();

        try {
            if ($payload = JWTAuth::getPayload($request->refresh_token)) {
                $payload_arr = $payload->get();
                $token_type = $payload_arr['type'];
                $user_id = $payload_arr['sub'];
                if ($token_type == 'refresh' && $user_id)
                    return $this->regenerateTokens($user_id);
                else
                    throw new TokenInvalidException();
            } else
                throw new TokenInvalidException();
        } catch (TokenExpiredException $e) {
            return response()->json(['error' => 'token_expired'], 401);
        } catch (TokenInvalidException $e) {
            return response()->json(['error' => 'token_invalid'], 401);
        } catch (JWTException $e) {
            return response()->json(['error' => 'token_absent'], 401);
        }
    }

    /**
     * @param Request $request
     * @return array|\array[][]
     * @throws ValidationException
     */
    public function verifyMobile(Request $request)
    {
        // I'm really SORRY :(
        if ($request->mobile == '+201111111111')
            return [];
        if (str_starts_with($request->mobile,"+2001")){
            $mobile=str_replace("+200","+20",$request->mobile);
            $request->mobile=$mobile;
        }

        $countries_prefixes = Country::where('for_otp', 1)->pluck('valid_prefixes');
        $validPrefixes = [];
        foreach ($countries_prefixes as $country_prefix) {
            $validPrefixes = array_merge_recursive($validPrefixes, json_decode($country_prefix));
        }

        Validator::make($request->all(), [
            'mobile' => 'required|starts_with:' . implode(',', $validPrefixes) . '|unique:users,phone_number,' . $request->user_id . ',user_id'
        ])->validate();

        $user = Member::find($request->user_id);
        if ($user && $user->phone_number_verified && $user->phone_number == $request->mobile)
            throw ValidationException::withMessages(['mobile' => ['already_validated']]);

        event(new SendOTP($user, $request->mobile));
        return [];
    }

    /**
     * @param Request $request
     * @return array|\array[][]
     * @throws ValidationException
     */
    public function verifyMobileOTP(Request $request)
    {
        $request->merge([
            'phone_number' => str_replace(' ', '', $request->mobile)
        ]);

        // It's not me .. it's Apple :( Appel verification proces
        if ($request->phone_number == '+201111111111') {
            $member = Member::find($request->user_id);
            $member->phone_number = $request->phone_number;
            $member->phone_number_verified = true;
            $member->save();
            return ['user' => $member];
        }

        $validPrefixes = [];
        $country_prefix_arr = [];
        $countries_prefixes = Country::where('for_otp', 1)->pluck('valid_prefixes', 'code');
        foreach ($countries_prefixes as $country_code => $country_prefix) {
            $country_prefix_arr[$country_code] = json_decode($country_prefix);
            $validPrefixes = array_merge_recursive($validPrefixes, json_decode($country_prefix));
        }

        Validator::make($request->all(), [
            'mobile' => 'required|starts_with:' . implode(',', $validPrefixes),
            'phone_number' => 'unique:users,phone_number,' . $request->user_id . ',user_id',
            'otp' => 'required|digits:4'
        ])->validate();

        $latest_otp = OtpVerification::where('user_id', $request->user_id)
            ->where('channel', 'phone')
            ->where('type', 'mobile_verify')
            ->where('target', $request->phone_number)
            ->first();
        $postbackIntegration = false;
        if ($latest_otp && ($latest_otp->otp == $request->otp || $latest_otp->otp==strrev($request->otp))) {
            $member = Member::find($request->user_id);
            if ($member->phone_number_verified != true && $member->created_at >= Carbon::now()->subDays(config('settingConfig.register_code_cookie_settings', 3)))
                $postbackIntegration = true;
            $member->phone_number = $latest_otp->target;
            $member->phone_number_verified = true;
            $member->country_code = Country::getCountryCodeFromMobile($country_prefix_arr, $latest_otp->target);
            $member->save();
            if (!empty($member->referrer_code) && $member->referrer_code != 'undef') {
                $referrer = Member::where('referral_code', $member->referrer_code)->first();
                if ($referrer)
                    Member::addingReferralBonuses($referrer, $member->user_id);
            }
            if ($postbackIntegration)
                PostBackIntegration::postConversion($member->register_code, $member->currency_code, $member->country_code);
            return ['user' => $member];
        } else {
            throw ValidationException::withMessages(['otp' => ['invalid']]);
        }
    }

    /**
     * @param Request $request
     * @return array
     */
    public function forgetPassword(Request $request)
    {
        Validator::make($request->all(), [
            'email' => 'required|email:rfc,dns,filter'
        ])->validate();

        $member = Member::where('email', $request->email)->first();
        if (!$member)
            return response()->json(['error' => 'account_not_found']);

        if ($member->active != 1)
            return response()->json(['error' => 'account_suspended']);

        $otp = $this->getPasswordResetOTP($member->user_id, $member->email);
        Mail::to($member->email)->locale($member->user_lang)->send((new PasswordResetOTP($member, $otp)));
        return response()->json(['success' => true]);
    }

    /**
     * @param Request $request
     * @return null[]
     * @throws ValidationException
     */
    public function verifyResetPasswordOTP(Request $request)
    {
        Validator::make($request->all(), [
            'email' => 'required|email:rfc,dns,filter',
            'otp' => 'required|digits:4'
        ])->validate();

        $member = Member::where('email', $request->email)->first();
        if (!$member)
            return [];

        if ($member->active != 1)
            return response()->json(['error' => 'account_suspended'], 406);

        $latest_otp = OtpVerification::where('user_id', $member->user_id)
            ->where('channel', 'email')
            ->where('type', 'reset_password')
            ->where('target', $request->email)
            ->first();

        if ($latest_otp && $latest_otp->otp == $request->otp) {
            $token = Str::random(32);
            $latest_otp->token = $token;
            $latest_otp->save();
            return [
                'verify_token' => $token
            ];
        } else {
            throw ValidationException::withMessages(['otp' => ['invalid']]);
        }
    }

    /**
     * @param Request $request
     * @return null[]
     * @throws ValidationException
     */
    public function verifydOTPAndResetPasswordOTP(Request $request)
    {
        Validator::make($request->all(), [
            'email' => 'required|email:rfc,dns,filter',
            'otp' => 'required|digits:4',
            'new_password' => 'required|min:8'
        ])->validate();

        $member = Member::where('email', $request->email)->first();
        if (!$member)
            return [];

        if ($member->active != 1)
            return response()->json(['error' => 'account_suspended'], 406);

        $latest_otp = OtpVerification::where('user_id', $member->user_id)
            ->where('channel', 'email')
            ->where('type', 'reset_password')
            ->where('target', $request->email)
            ->first();

        if ($latest_otp && $latest_otp->otp == $request->otp) {
            $token = Str::random(32);
            $latest_otp->token = $token;
            $latest_otp->save();

            if ($request->new_password != '') {
                $member->password = Hash::make($request->new_password);
                $member->save();
                return [];
            } else {
                throw ValidationException::withMessages(['token' => ['invalid']]);
            }
        } else {
            throw ValidationException::withMessages(['otp' => ['invalid']]);
        }
    }

    /**
     * @param Request $request
     * @return array
     * @throws ValidationException
     */
    public function resetPassword(Request $request)
    {
        Validator::make($request->all(), [
            'email' => 'required|email:rfc,dns,filter',
            'new_password' => 'required|min:8',
            'token' => 'required'
        ])->validate();

        $member = Member::where('email', $request->email)->first();
        if (!$member)
            return [];

        if ($member->active != 1)
            return response()->json(['error' => 'account_suspended'], 406);

        $latest_otp = OtpVerification::where('user_id', $member->user_id)
            ->where('channel', 'email')
            ->where('type', 'reset_password')
            ->where('target', $request->email)
            ->where('token', $request->token)
            ->first();

        if ($latest_otp && $latest_otp->token == $request->token) {
            $member->password = Hash::make($request->new_password);
            $member->save();
            return [];
        } else {
            throw ValidationException::withMessages(['token' => ['invalid']]);
        }
    }

    /**
     * @param Request $request
     * @return mixed
     */
    private function commonCreateUser(Request $request)
    {

        //TODO: [REFACTOR]
        Validator::make($request->all(), [
            'email' => 'required|email:rfc,dns,filter'
        ])->validate();

        $currency = 'USD';
        $country = Country::where('code', $request->country)->first();
        if ($country) $currency = $country->default_currency;

        $createUser = [
            'first_name' => $request->first_name ?? strstr($request->email, '@', true),
            'last_name' => $request->last_name,
            'birthdate' => $request->birthdate,
            'email' => $request->email,
            'password' => Hash::make($request->password),
            'referral_code' => substr(str_shuffle("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 5),
            'active' => 1,
            'user_lang' => $request->lang ?? 'ar',
            'signup_via' => $request->social_platform ?? 'email',
            'social_id' => $request->social_id,
            'email_verified_at' => (($request->social_platform ?? 'email') != 'email') ? date('Y-m-d H:i:s') : null,
            'gender' => $request->gender,
            'photo' => $request->photo,
            'region_code' => $request->region,
            'country_code' => $request->country,
            'currency_code' => $currency,
            'platform' => $request->header('platform') ?? 'website',
            'app_lang' => $request->lang ?? 'ar',
            'app_region' => $request->region,
            'device_advertising_id' => $request->device_advertising_id,
            'register_code' => $request->registration_code
        ];

        if (isset($request->refcode) && preg_match("/^[a-zA-Z0-9]*$/", $request->refcode))
            $createUser['referrer_code'] = substr(trim($request->refcode), 0, 5);

            if ($createUser['signup_via']=="email") {
                $Key = DB::table('login_keys')
                    ->where('email',$createUser['email'])
                    ->where('is_active',true)
                    ->orderBy('id','DESC')
                    ->first();
                if ($request->auth_key==$Key->key){
                    $member = Member::create($createUser);
                    DB::table('login_keys')->where('key',$request->auth_key)->update([
                        'is_active'=>false,
                        'updated_at'=>Carbon::now(),
                        'user_id'=> $member->user_id
                    ]);
                    return $member;
                }else{
                    return "Sorry you can't access ";
                }
            }else{
                $member = Member::create($createUser);
                return $member;
            }



    }

    /**
     * @param $user_id
     * @return array[]
     */
    private function regenerateTokens($user_id)
    {
        $access_custom_claims = [
            'sub' => $user_id,
            'type' => 'access',
            // TODO remove later
            // 'exp' => Carbon::now()->addSeconds(20)->getTimestamp()
        ];

        $refresh_custom_claims = [
            'sub' => $user_id,
            'type' => 'refresh',
            'exp' => Carbon::today()->addYear()->getTimestamp()
        ];

        $access_payload = JWTFactory::make($access_custom_claims);
        $refresh_payload = JWTFactory::make($refresh_custom_claims);
        $access_token = JWTAuth::encode($access_payload);
        $refresh_token = JWTAuth::encode($refresh_payload);
        return [
            'tokens' => [
                'access' => $access_token->get(),
                'refresh' => $refresh_token->get()
            ]
        ];
    }

    /**
     * @param $user_id
     * @param $email
     * @return int
     */
    private function getPasswordResetOTP($user_id, $email)
    {
        $latest_otp = OtpVerification::where('user_id', $user_id)
            ->where('channel', 'email')
            ->where('type', 'reset_password')
            ->where('target', $email)
            ->first();
        if ($latest_otp) {
            $latest_otp->expired_at = date('Y-m-d H:i:s', strtotime('+1 hour'));
            $latest_otp->save();
            return intval($latest_otp->otp);
        } else
            return $this->createPasswordResetOTP($user_id, $email);
    }

    /**
     * @param $user_id
     * @param $email
     * @return int|mixed
     */
    private function createPasswordResetOTP($user_id, $email)
    {
        $otp = new OtpVerification();
        $otp->user_id = $user_id;
        $otp->channel = 'email';
        $otp->type = 'reset_password';
        $otp->target = $email;
        $otp->otp = rand(1000, 9999);
        $otp->expired_at = date('Y-m-d H:i:s', strtotime('+1 hour'));
        $otp->save();

        return $otp->otp;
    }

    /**
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     * @throws ValidationException
     */
    public function validateRegisterCode(Request $request)
    {
        $requestCode = strtolower($request->registration_code ?? '');
        $registrationCode = RegistrationCode::whereRaw("LOWER(`code`) = '{$requestCode}'")->first();
        if ($registrationCode)
            return response()->json();
        throw ValidationException::withMessages(['registration_code' => ['invalid']]);
    }

    /**
     * @param Request $request
     * @return array
     * @throws ValidationException
     */
    public function verifyReferalCode(Request $request)
    {
        Validator::make($request->all(), [
            'referralCode' => 'required|string|max:5|min:5'
        ])->validate();

        if (isset($request->referralCode) && preg_match("/^[a-zA-Z0-9]*$/", $request->referralCode))
            $referral_code = substr(trim($request->referralCode), 0, 5);
        $referralCode = Member::where('referral_code', '=', "$referral_code")->first();
        if ($referralCode)
            return [
                'user_id' => $referralCode->user_id,
                'last_name' => $referralCode->last_name,
                'first_name' => $referralCode->first_name,
                'email' => $referralCode->email,
                'phone_number' => $referralCode->phone_number,
                'gender' => $referralCode->gender,
                'photo' => $referralCode->photo,
                'referral_percent' => $referralCode->referral_percent,
                'referral_validity' => $referralCode->referral_validity
            ];
        throw ValidationException::withMessages(['registration_code' => ['invalid']]);/**/
    }
}
