<?php

namespace AuthStack\Driver\AUDGeneric\App\Controllers;

use DB;
use Mail;
use Carbon\Carbon;
use GuzzleHttp\Client;
use Illuminate\Http\Response;
use AuthStack\Driver\AUDGeneric\Driver;
use AuthStack\Http\Controllers\Controller;
use AuthStack\Driver\Errors\NoAuthSourceError;
use AuthStack\Driver\AUDGeneric\App\Mail\PasswordTokenSent;
use AuthStack\Driver\AUDGeneric\App\Requests\PasswordReset;
use AuthStack\Driver\AUDGeneric\App\Requests\UpdatePassword;
use AuthStack\Driver\AUDGeneric\App\Requests\PasswordResetWithToken;

class AUDPassword extends Controller
{
    protected $driver;

    public function __construct()
    {
        $this->driver = new Driver();
    }

    /**
     * @return Response
     */
    public function passwordInit()
    {
        return response()->view('aud::password.forgot', [], 200);
    }

    /**
     * @param PasswordReset $request
     * @return Response
     * @throws NoAuthSourceError
     */
    public function passwordReset(PasswordReset $request)
    {
        $connId = $this->driver->getAuthSourceId();
        $user = DB::table('users')
            ->where('conn_id', '=', $connId)
            ->where('username', '=', $request->getUsername())
            ->first();

        //Check if the user exists
        $user = (array)$user;
        if (count($user) < 1) {
            return response()->view('aud::password.message', [
                'title' => 'Whoops!',
                'message' => 'Sorry, username cannot be found.'
            ], 500);
        }

        $json = $user['config'];
        $config = json_decode($json, true);
        $email = $config['attributes']['email'];

        $tokenData = $this->tokenHandler($request, $email, $connId);

        $mail = $this->sendResetEmail($config['attributes'], $tokenData->token);

        if ($mail) {
            return response()->view('aud::password.message', [
                'title' => 'Email sent',
                'message' => 'We have email you, your recovery link.'
            ], 200);
        } else {
            return response()->view('aud::password.message', [
                'title' => 'Whoops!',
                'message' => 'Sorry, something when wrong while sending the email'
            ], 500);
        }
    }

    /**
     * @param PasswordResetWithToken $request
     * @return Response
     * @throws NoAuthSourceError
     */
    public function passwordResetWithToken(PasswordResetWithToken $request)
    {
        $validate = $this->validateToken($request);

        if (!$validate) {
            return response()->view('aud::password.message', [
                'title' => 'Whoops!',
                'message' => 'Sorry, this request is not valid.'
            ], 500);
        }

        return response()->view('aud::password.reset', [
            'email' => $request->getEmail(),
            'token' => $request->getToken(),
        ], 200);
    }

    /**
     * @param UpdatePassword $request
     * @return Response
     * @throws NoAuthSourceError
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function updatePassword(UpdatePassword $request)
    {
        $tokenData = DB::table('password_resets')
            ->where('token', $request->getToken())
            ->first();

        $user = DB::table('users')
            ->where('username', $tokenData->username)
            ->where('conn_id', $this->driver->getAuthSourceId())
            ->first();

        $user = (array)$user;
        $json = $user['config'];
        $config = json_decode($json, true);
        $id = $config['attributes']['id'];

        if ($tokenData) {
            $base = env('AUD_URL');
            $api = "/api/v1/account/$id/password";
            $url = $base . $api;
            $verify = env('AUD_VERIFY_SSL', true);

            $options = [
                'verify' => $verify,
                'form_params' => [
                    'password' => $request->getPassword()
                ]
            ];

            try {
                $client = new Client(['defaults' => ['verify' => $verify]]);
                $res = $client->request('patch', $url, $options);
                $res = $res->getBody()->getContents();

                $response = json_decode($res, true);

                if (isset($response['result']) && $response['result']) {

                    DB::table('password_resets')
                        ->where('token', $request->getToken())
                        ->update(['active' => false]);

                    return response()->view('aud::password.message', [
                        'title' => 'Success!',
                        'message' => 'Your password was successfully updated'
                    ], 200);
                }

            } catch (\Exception $e) {
                \Log::error('Email Failure:', [
                    'message' => $e->getMessage(),
                    'file' => $e->getFile(),
                    'line' => $e->getLine()
                ]);

                return response()->view('aud::password.message', [
                    'title' => 'Whoops!',
                    'message' => 'Something went wrong while trying to update the your password.'
                ], 500);
            }
        }
        return response()->view('aud::password.message', [
            'title' => 'Whoops!',
            'message' => 'Sorry, this request is not valid.'
        ], 500);
    }

    /**
     * @param array $attributes
     * @param string $token
     * @return bool
     */
    private function sendResetEmail(array $attributes, string $token)
    {
        $link = env('APP_URL') . '/aud/password/reset/' . $token . '?email=' . urlencode($attributes['email']);

        try {
            Mail::to($attributes['email'])->send(new PasswordTokenSent($attributes, $link));

            return true;

        } catch (\Exception $e) {

            \Log::error('Email Failure:', [
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine()
            ]);

            return false;
        }
    }

    /**
     * @param PasswordResetWithToken $request
     * @return bool
     * @throws NoAuthSourceError
     */
    private function validateToken(PasswordResetWithToken $request)
    {
        $connId = $this->driver->getAuthSourceId();

        $validate = DB::table('password_resets')
            ->where('email', $request->getEmail())
            ->where('token', $request->getToken())
            ->where('driver_id', $connId)
            ->where('active', true)
            ->whereRAW('UNIX_TIMESTAMP(expires_at) > ?', [Carbon::now()->timestamp])
            ->first();

        $validate = (array)$validate;

        return count($validate) > 0 ? true : false;
    }

    /**
     * @param PasswordReset $request
     * @param $email
     * @param int|null $connId
     * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Query\Builder|object|null
     */
    private function tokenHandler(PasswordReset $request, $email, ?int $connId)
    {
        //Create Password Reset Token
        $token = DB::table('password_resets')->insertGetId([
            'email' => $email,
            'token' => str_random(60),
            'username' => $request->getUsername(),
            'active' => true,
            'driver_id' => $connId,
            'created_at' => Carbon::now(),
            'expires_at' => Carbon::now()->addDay(1),
        ]);

        //Get the token just created above
        $tokenData = DB::table('password_resets')
            ->where('id', $token)
            ->first();

        $oldTokens = DB::table('password_resets')
            ->where('email', $email)
            ->where('active', true)
            ->where('driver_id', $connId)
            ->whereNotIn('id', [$tokenData->id])
            ->pluck('id')
            ->toArray();

        //Disable old tokens
        DB::table('password_resets')
            ->whereIn('id', $oldTokens)
            ->update(['active' => 0]);

        return $tokenData;
    }
}