<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\BaseController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use App\Models\User;
use App\Models\Subuser;
use App\Models\FailedLogin;
use App\Models\Suborganisation;
use Carbon\Carbon;
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;

class AuthController extends BaseController
{
    use AuthenticatesUsers;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login', 'reset', 'refresh']]);
    }

    // Fetch the proper IP address
    public function real_ip()
    {
        foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key){
            if (array_key_exists($key, $_SERVER) === true){
                foreach (explode(',', $_SERVER[$key]) as $ip){
                    $ip = trim($ip); // just to be safe
                    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false){
                        return $ip;
                    }
                }
            }
        }
        return request()->ip();   
    }

    public function add_failed_login($ip, $user_email, $user_agent)
    {
        $user = User::where('email', '=', $user_email)->first() ?? null;

        if($user != null) {
            FailedLogin::create([
                'ip'        => $ip,
                'user_agent'=> $user_agent,
                'user_id'   => $user->id
            ]);
        }
    }

    public function check_excessive_account_attempts($user_email)
    {
        $user = User::where('email', '=', $user_email)->first() ?? null;
        $count = 0;

        if($user != null) {
            if($user->id) {
                $count = FailedLogin::where( [
                    ['user_id', '=', $user->id],
                    ['created_at', '>', Carbon::now()->subMinutes(config('security.account_lockout_time'))] 
                ] )->count();    
            }
        }

        return $count >= config('security.account_login_attempts');
    }

    // Hardened login handling
    public function login(Request $_request)
    {
        $real_ip = $this->real_ip();
        $response = null;

        try {
            // Only perform our general login procedure if there hasn't been an excessive amount of attempts against a given account.
            if(!$this->check_excessive_account_attempts($_request->input('email'))) {
                $executed = RateLimiter::attempt(
                    "login-attempt:$real_ip",
                    $perMinute = config('security.client_login_attempts'),
                    function() use($_request, &$response, $real_ip) {
                        
                        $valid_params = $_request->validate([
                            'email' => 'required',
                            'password' => 'required',
                        ]);

                        if($valid_params) {
                            $expiration = Carbon::now()->timestamp + (config('jwt.ttl') * 60);
                            $token = Auth::attempt($_request->only('email', 'password'));
            
                            // Attempt with the provided credentials.
                            if ($token) {
                                $response = response()->json([
                                    'id' => Auth::user()->id,
                                    'email' => Auth::user()->email,
                                    'name' => Auth::user()->name,
                                    'organisation_id' => Auth::user()->organisation_id,
                                    'access_token' => $token,
                                    'expiration' => $expiration,
                                    'permissions' => Auth::user()->getPermissions(),
                                ], 200);
                            }
                            else {
                                // Invalid login attempt has occurred object.
                                $response = response()->json(['detail' => 'Invalid credentials'], 401);
            
                                // Add a failed login attempt...
                                $this->add_failed_login($real_ip, $_request->input('email'), $_request->userAgent());
                            }  
                        }
                        else {
                            $response = response()->json(['detail' => 'Invalid credentials'], 401);
                        }
                    }
                );
        
                // If there has been too many login attempts, redirect the user to the login screen...
                if (!$executed) {
                    // Get the seconds until the next login attempt
                    $seconds = RateLimiter::availableIn("login-attempt:$real_ip");
        
                    // Login attempts succeeded redirect object.
                    $response = response()->json(['detail' => "Attempts exceeded. Try again in $seconds seconds"], 429);
                }
            }
            else {
                $response = response()->json(['detail' => "Attempts exceeded. Try again later"], 429);
            }
        }
        catch(Exception $e) {
            $response = response()->json(['detail' => 'Server error'], 500);
        }

        return $response;
    }

    public function reset(Request $_request)
    {
        try {
            if($user = User::where('email', '=', $_request->input('email'))->get()->first()) {
                $user->sendResetEmail();
            }
        }
        catch(Exception $e) {
            return response()->json(['detail' => 'Server error'], 500);
        }

        // Return a 200 on success so that the end client cannot determine anything about the emails known.
        response()->json([], 200);
    }

    public function refresh()
    {
        try {
            $token = Auth::refresh();
            $expiration = Carbon::now()->timestamp + (config('jwt.ttl') * 60);

            return response()->json([
                'access_token' => $token,
                'expiration' => $expiration,
            ], 200);
        } catch (Exception $ex) {
            return response()->json([], 400);
        }
    }

    public function me()
    {
        $user = Auth::user();

        return response()->json([
            'id' => $user->id,
            'email' => $user->email,
            'name' => $user->name,
            'permissions' => $user->getPermissions()
        ]);
    }

    public function logout()
    {
        Auth::logout();

        return response()->json([
            'detail' => 'Logged out',
        ], 200);
    }

    public function act_as($id) {
        $requested_user = User::find($id);
        $response = response()->json(['detail' => 'Cannot act as another user.'], 401);

        if($requested_user) {
            Log::debug('Found requested user for acting...');
            // Check if the user is already impersonating another user.
            $current_token = JWTAuth::parseToken()->getPayload();

            Log::debug(json_encode($current_token));

            if(!(isset($current_token['acting']) && $current_token['acting'] === 'true')) {
                // Check if the user has the relevant admin permission - This should be on the routes, but this could be catastrophic so it doesn't hurt to assume the route bouncer is broken.
                if(auth::user()->canDo('act-as.admin')) {
                    $response = response()->json([
                        'access_token' => auth()->claims(['acting' => 'true'])->login($requested_user),
                        'expiration' => $current_token['exp'],
                    ], 200);
                }
            }
        }

        return $response;
    }

    public function end_act($user) {

    }
}
