<?php

namespace App\Http\Controllers\Auth;

use App\Models\User;
use App\Models\Setting;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Carbon;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Hash;
use App\Services\SecurityCheckService;
use Illuminate\Support\Facades\Config;
use App\Providers\RouteServiceProvider;
use Illuminate\Validation\ValidationException;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
     */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');

        $this->maxAttempts  = Config::get('srm_config.login.max_login_failure_attempts');
        $this->decayMinutes = Config::get('srm_config.login.lockout_duration_minutes');
    }

    public function login(Request $request)
    {
        $this->validateLogin($request);

        $this->checkDomPdfFontsDirectory();

        if (checkDemo()) {
            $user = User::first();
            Auth::login($user);

            $this->SendLoginNotification();

        }

        $user = $this->getUser($request);
        $this->checkDeletedAccount($user);

        $this->hasTooManyLoginAttempts($user);

        $this->checkPasswordLength($user, $request, 8, 16);

        $this->checkUserCredentials($user, $request);

        $this->checkPendedAccount($user);

        $this->checkBlockedAccount($user);

        $this->checkActivatedAccount($user);

        if ($this->attemptLogin($request)) {

            $user = $this->guard()->user();

            if ($request->hasSession()) {
                $request->session()->put('auth.password_confirmed_at', time());
            }

            $this->addDataToSession($user);

            $this->removeEnvironmentVariables($user);

            $this->resetLoginAttempts($user);

            $this->updateBaseUrl();

            $this->setBaseUrl();

            return $this->sendLoginResponse($request);
        } else {
            return $this->sendFailedLoginResponse($request);
        }

    }

    protected function validateLogin(Request $request)
    {
        // dd($request);
        $request->validate([
            $this->username() => ['required', 'string', 'email', 'max:255'],
            'password'        => ['required', 'string']
        ], $this->validationMessages());
    }

    protected function sendLoginResponse(Request $request)
    {

        if ($request->remember && checkDemo()) {
            $this->changeRememberMeTokenLife();
        }

        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return $request->wantsJson()
        ? new JsonResponse([], 204)
        : redirect()->intended($this->redirectPath());
    }

    protected function sendFailedLoginResponse($respnonse)
    {
        throw ValidationException::withMessages([
            $this->username() => [trans($respnonse)]
        ]);
    }

    protected function validationMessages()
    {
        return [
            $this->username() . '.required' => 'Please enter both your email and password.',
            'password.required'             => 'Please enter both your email and password.'

        ];
    }

    protected function getUser($request)
    {
        $user = User::where($this->username(), $request->email)->withTrashed()->first();

        if ($user) {

            return $user;
        } else {
            $this->sendFailedLoginResponse('auth.failed');
        }

    }

    protected function checkDeletedAccount($user)
    {

        if ($user && $user->trashed()) {
            $this->sendFailedLoginResponse('auth.account_deleted');
        }

    }

    protected function checkPendedAccount($user)
    {

        if ($user && strtolower($user->status) === "pended") {

            $this->sendFailedLoginResponse('auth.account_pended');
        }

    }

    protected function checkBlockedAccount($user)
    {

        if ($user) {

            if ($user->status == "Blocked") {
                $this->sendFailedLoginResponse('auth.account_blocked');
            }

        }

    }

    protected function checkActivatedAccount($user)
    {

        if ($user) {

            if (!$user->email_verified_at) {
                $this->sendFailedLoginResponse('auth.account_not_activated');
            }

        }

    }

    protected function checkUserCredentials($user, $request)
    {

        if (!$user || !Hash::check($request->password, $user->Password)) {
            $this->incrementLoginAttempts($user);

            $this->sendFailedLoginResponse('auth.failed');
        }

    }

    protected function checkPasswordLength($user, $request, $min, $max)
    {

        if (!$user || (Str::length($request->password) < $min || Str::length($request->password) > $max)) {
            $this->incrementLoginAttempts($user);
            $this->sendFailedLoginResponse('validation.min.string');
        }

    }

    protected function invalidateSession(Request $request)
    {
        $this->guard()->logout();
        $request->session()->invalidate();
    }

    protected function addDataToSession($user)
    {
        $_SESSION["srm_access_session_key"] = [
            "srm_access_role"     => $user->getUserType(),
            "srm_access_userid"   => $user->user_ID,
            "srm_access_username" => $user->user_name,
            "srm_access_email"    => $user->Email,
            "srm_access_group"    => $user->getUserType() === "user" ? $user->group_id : ""
        ];
    }

    protected function hasTooManyLoginAttempts($user)
    {

        if ($user) {

            if ($user->consecutive_login_failures > $this->maxAttempts()) {

                // Check if the user should be locked out
                $lastLoginAttempt = Carbon::parse($user->last_lock_time);
                $blockUntil       = $lastLoginAttempt->addMinutes($this->decayMinutes());

                if (now() < $blockUntil) {
                    // User is still locked out
                    return $this->sendLockoutResponse();
                }

                if (now() > $blockUntil) {
                    $this->resetLoginAttempts($user);
                }

            }

        }

    }

    /**
     * Increment Consecutive login failures.
     * Update last lock time only when Consecutive login failures
     * equal to maxAttempts configure
     *
     * @return void
     */
    protected function incrementLoginAttempts($user)
    {

        if ($user) {

            if ($user->consecutive_login_failures == $this->maxAttempts()) {
                $user->update(["last_lock_time" => now()]);
            }

            $user->increment("consecutive_login_failures");
        }

    }

    protected function resetLoginAttempts($user)
    {
        $user->update(['consecutive_login_failures' => 0, 'last_lock_time' => null]);
    }

    protected function changeRememberMeTokenLife()
    {
        /**
         * These lines changes the default remember me life time
         */
        $RememberMeDurationInMinutes = Config::get("srm_config.login.rememeber_me_token_life_span_days", 30) * 24 * 60;

        $rememberTokenName = Auth::getRecallerName();
        $cookieJar         = $this->guard()->getCookieJar();
        $cookieValue       = $cookieJar->queued($rememberTokenName)->getValue();
        $cookieJar->queue($rememberTokenName, $cookieValue, $RememberMeDurationInMinutes);
    }

    public function redirectTo()
    {
        return route('home');
    }

    protected function sendLockoutResponse()
    {
        throw ValidationException::withMessages([
            $this->username() => [trans('auth.throttle')]
        ])->status(Response::HTTP_TOO_MANY_REQUESTS);
    }

    private function security_check()
    {

        $skip_security_check = Config::get("srm_config.skip_security_check");

        $curl_checks = [
            "composer.json",
            ".htaccess",
            "/config",
            "/storage",
            "/storage/framework/sessions",
            "/storage/logs/laravel.log"
        ];

        if (!$skip_security_check) {

            return SecurityCheckService::checkSiteAccessability($curl_checks) ? true : false;
        }

    }

    /**
     * The user has been authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(Request $request, $user)
    {

        $security_errors = [];

        $this->removeInstallDirectory($user);
        $this->removeInstallationScripts($user);

        if ($user->isAdminOrOwner() && SecurityCheckService::securityCheck()) {
            $security_errors['security_check_error'] = SecurityCheckService::securityCheckMessage();
        }

        if ($user->isAdminOrOwner() && SecurityCheckService::checkSrmInstallExist()) {
            $security_errors['install_check_error'] = "Kindly make sure to remove the /srm/srm_install directory for security reason.";
        }

        if ($user->isAdminOrOwner() && SecurityCheckService::checkInstallScriptExist()) {
            $path                                          = realpath(base_path("../../"));
            $security_errors['install_script_check_error'] = "For security reasons, please delete the installation scripts $path/install.sh and $path/install.ps1.";
        }

        if (SecurityCheckService::checkUserSecurityQuestionExist()) {
            $security_errors['user_security_question_check_error'] = "For security purposes, please add a security question and answer to your account by visiting your <a class='text-primary' href='" . route('profile.index') . "'>profile page</a> Additionally, please change your password for security reasons.";
        }

        return redirect()->route('home')->with('security_errors', $security_errors);

        SecurityCheckService::securityCheckExecute($user);
    }

    /**
     * Log the user out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
     */
    public function logout(Request $request)
    {

        $this->removeRememberToken();

        $this->guard()->logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();
        session_unset();
        session_destroy();

        if ($response = $this->loggedOut($request)) {
            return $response;
        }

        return $request->wantsJson()
        ? new JsonResponse([], 204)
        : redirect(route('login'));
    }

    private function removeRememberToken()
    {
        auth()->user()->remember_token = null;
        auth()->user()->save();

    }

    private function removeEnvironmentVariables()
    {

        if (isDocker()) {
            putenv("DEFAULT_CONNECTION=");
            putenv("DEFAULT_CONNECTION_DB=");
            putenv("DEFAULT_CONNECTION_HOST=");
            putenv("DEFAULT_CONNECTION_USER=");
        }

    }

    private function removeInstallDirectory($user)
    {
        $directoryPath = base_path(
            "../" . config('srm_config.installer.install_dir', 'srm_install')
        );

        if ($user->isOwner() && File::exists($directoryPath) && config('app.env') == 'production') {
            File::deleteDirectory($directoryPath);

        }

    }

    private function removeInstallationScripts($user)
    {
        $baseSrmPath = base_path(
            "../../"
        );

        if ($user->isOwner() && config('app.env') == 'production') {
            File::delete("$baseSrmPath/install.sh", "$baseSrmPath/install.ps1");
        }

    }

    private function checkDomPdfFontsDirectory()
    {
        File::ensureDirectoryExists(
            storage_path('fonts')
        );
    }

    private function SendLoginNotification()
    {

        if (!checkDemo()) {
            return;
        }

        $url = config('srm_config.dashboard.demo.notification_url') . "/login";

        $info = [
            "time"     => now()->toDateTimeString(),
            "ip"       => request()->ip(),
            "referral" => request()->headers->get('referer')
        ];

        try {
            sendApiNotification($url, $info);
        } catch (\Exception $e) {

        }

    }

    protected function attemptLogin(Request $request)
    {
        $rememberMe = checkDemo() ? true : $request->boolean('remember');

        return $this->guard()->attempt(
            $this->credentials($request), $rememberMe
        );
    }

    private function updateBaseUrl()
    {
        $protocol = request()->secure() ? 'https://' : 'http://';

        $baseUrl = $protocol . request()->getHttpHost() . explode("public", explode("/index.php", request()->server->get("PHP_SELF"))[0])[0];

        Setting::updateOrInsert(
            ['settings_key' => 'base_url'],
            ['settings_value' => $baseUrl]
        );

    }

    private function setBaseUrl()
    {
        $baseUrl = Setting::where('settings_key', 'base_url')->first();

        if (!is_null($baseUrl)) {
            config(['app.url' => $baseUrl->settings_value]);
        }

    }

}