<?php

namespace apexl\Io\modules\userDisplay\controllers;

use apexl\Config\Configuration;
use apexl\Io\exceptions\RecordNotFoundException;
use apexl\Io\includes\Controller;
use apexl\Io\includes\Hook;
use apexl\Io\includes\Routes;
use apexl\Io\includes\System;
use apexl\Io\modules\display\components\BasicLink;
use apexl\Io\modules\display\components\ButtonBar;
use apexl\Io\modules\display\components\CardEntityFilteredDisplayTable;
use apexl\Io\modules\display\components\ContentTitle;
use apexl\Io\modules\display\components\form\CheckboxField;
use apexl\Io\modules\display\components\form\Form;
use apexl\Io\modules\display\components\form\InputField;
use apexl\Io\modules\display\components\FormComponent;
use apexl\Io\modules\display\components\genericComponents;
use apexl\Io\modules\display\components\ImageTile;
use apexl\Io\modules\display\components\RowWrapper;
use apexl\Io\modules\display\services\Render;
use apexl\Io\modules\user\entities\roleEntity;
use apexl\Io\modules\user\entities\userEntity;
use apexl\Io\modules\user\enums\permissions\Role;
use apexl\Io\modules\user\enums\permissions\User;
use apexl\Io\modules\user\services\currentUser;
use apexl\Io\modules\userDisplay\components\dashboardTiles;
use apexl\Io\modules\userDisplay\components\roleEntityDisplayTile;
use apexl\Io\modules\userDisplay\components\userEntityDisplayTile;
use apexl\Io\modules\userDisplay\forms\forgottenPasswordForm;
use apexl\Io\modules\userDisplay\forms\loginForm;
use apexl\Io\services\Output;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

class userController extends Controller
{
    protected $currentUser;
    protected $render;

    public function __construct(Render $render, currentUser $currentUser)
    {
        parent::__construct();
        $this->currentUser = $currentUser::getCurrentUser();
        $this->render = $render;
    }

    /**
     * @param $args
     * @return Response
     * @throws \Exception
     */
    public function userViewSingle(Request $request, Response $response, $args)
    {
        $rowWrapper = (new RowWrapper())->addClass('manageForm-wrapper');
        $rowWrapper->addComponent(dashboardTiles::userViewTile($args['id']));

        $userEntity = new userEntity();
        $userEntity->load($args['id']);

        $buttonsBar = new ButtonBar();
        $buttonsBar->setEntity($userEntity);

        $this->render::setPageTitle('Viewing [' . $userEntity->id . '] - ' . $userEntity->getNiceName());

        return System::asJson($response, $this->render::build([$buttonsBar, $rowWrapper]));
    }

    /**
     * @return Response
     * @throws RecordNotFoundException
     */
    public function userSettingsPage(Request $request, Response $response)
    {
        $form = $this->userEditForm($request, $response, ['id' => $this->currentUser->id]);

        //$form = (new FormComponent())
        //    ->src(Routes::getRoutePattern('user.display.forms.userSettings'))
        //    ->setID('register');

        $this->render::setPageTitle('User Settings');

        return System::asJson($response, $this->render::build([$form]));
    }


    /**
     * @param Request $request
     * @param Response $response
     * @param array $args
     * @return Response
     * @throws RecordNotFoundException
     */
    public function userEditForm(Request $request, Response $response, array $args = [])
    {


        //set form field values
        $user = new userEntity();
        if (isset($args[$user->primaryKey])) {
            $user->load($args[$user->primaryKey]);
        }

        $form = $this->primaryAccountForm(false, $user);
        $form->setSubmitText('Submit');
        if (isset($args[$user->primaryKey])) {
            $form->setMethod('PUT');
        }

        $form->setActionUrl(Routes::getRoutePattern('userEntity.put'));

        if ($this->currentUser->isAllowed(User::CHANGE_ACTIVE_STATE)) {
            $activeField = (new CheckboxField('active'))->setLabel('Active')->setValue($user->active ?? 0);
        } else {
            $activeField = (new InputField('active'))->setInputType('hidden')->setValue($user->active ?? 0);
        }

        if ($id = $args[$user->primaryKey] ?? false) {
            $form->setActionUrl(Routes::getRoutePattern($user->getEntityName() . '.put', ['id' => $id]));
        }

        $form->addField($activeField);

        $form->setFieldsetSetting('roles', 'fieldset_legend', 'Roles');
        $roles = (new roleEntity())->loadMultiple();
        foreach ($roles as $role) {
            //for users that can't change roles, show labels for those that they do have.
            $showLabel = false;
            if ($this->currentUser->isAllowed(Role::UPDATE)) {
                $roleField = (new CheckboxField('role_' . $role->id))
                    ->setLabel($role->name)
                    ->setValue($user->hasRole($role));
            } else {
                $showLabel = true;
                $roleField = (new InputField('role_' . $role->id));
                $roleField->setInputType('hidden');
            }
            if (($user->id ?? false) && $user->hasRole($role)) {
                //allows us to show the label only for those without the role to change.
                //Only shows labels for the roles they have.
                if ($showLabel) {
                    $roleField->setLabel($role->name);
                }
            }
            $form->addField($roleField, 'roles');
        }

        //allow other modules to modify form.
        $form = Hook::processHook('displayFormRender', $form, $user, 'userEditForm');

        return $form;
    }

    /**
     * @param bool $passwordRequired
     */
    protected function primaryAccountForm($passwordRequired = false, $user = null): Form
    {
        $form = new Form();
        $form->setMethod('post');
        $form->setActionUrl(Routes::getRoutePattern('userEntity.post'));

        $form->addField(
            (new InputField('first_name'))->setLabel('First Name')->setPlaceholder('First Name')->isRequired(
            )->setValue(isset($user->first_name) ? $user->first_name : '')
        );
        $form->addField(
            (new InputField('last_name'))->setLabel('Surname')->setPlaceholder('Surname')->isRequired()->setValue(
                isset($user->last_name) ? $user->last_name : ''
            )
        );
        $form->addField(
            (new InputField('email'))->setLabel('Email')->setPlaceholder('Email')->setInputType('email')->isRequired(
            )->setValue(isset($user->email) ? $user->email : '')
        );

        $password = (new InputField('password'))->setLabel('Password')->setPlaceholder('Password')->setInputType(
            'password'
        )->addValidation("minLength", ["min" => 6]);
        $confirmPass = (new InputField('confirm_password'))->setLabel('Confirm Password')->setPlaceholder(
            'Confirm Password'
        )->setInputType('password')->addValidation("minLength", ["min" => 6]);
        if ($passwordRequired) {
            $password->isRequired();
            $confirmPass->isRequired();
        }
        $form->addField($password);
        $form->addField($confirmPass);

        return $form;
    }

    /**
     * Login page - @return Response
     * @see userDisplayController in io-user
     */
    public function loginPage(Request $request, Response $response, $args)
    {
        if($this->redirectIfLoggedIn($response)){
            return System::asJson($response);
        }

        $this->render::setActive("noUI");
        $this->render::setPageTitle("Login");
        $components = [];

        if ($newComponents = Hook::processHook('login_page_components_before_form', $components)) {
            $components = $newComponents;
        }

        if (isset($args['loginMessageType']) && isset($this->config->app->loginMessageTypes->{$args['loginMessageType']})) {
            $components[] = (new ContentTitle())->addContent(
                $this->config->app->loginMessageTypes->{$args['loginMessageType']}
            )->addClass('text-light');
        }
        $redirect = $request->getQueryParams()['redirect'] ?? null;
        $redirect = $redirect ? sprintf('?redirect=%s', htmlentities($redirect)) : null;

        $form = (new Form())
            ->title(System::getVariable('user_login_description_text') ?? "Sign in with your email address.")
            ->setMethod('post')
            ->setActionUrl(Routes::getRoutePattern('user.login'))
            ->setSubmitText(System::getVariable('user_login_submit_text') ?? 'Sign in')
            ->addClass('login-form');
        if (System::getVariable('user_login_include_clear_button') ?? true) {
            $form->includeClearButton()->setClearButtonText(
                System::getVariable('user_login_include_clear_button_text') ?? 'Cancel'
            );
        }

        if ($request) {
            $redirect = ($request->getQueryParams()['redirect'] ?? null);
            if ($redirect) {
                $form->addField(
                    (new InputField('redirect'))
                        ->setInputType('hidden')
                        ->setValue($redirect)
                );
            }
        }

        $form->addField(
            (new InputField('username'))->setPlaceholder(
                System::getVariable('user_login_email_placeholder') ?? 'Enter your email address'
            )->setLabel('Email')->setInputType('email')->isRequired()
        );
        $form->addField(
            (new InputField('password'))->setPlaceholder(
                System::getVariable('user_login_password_placeholder') ?? 'Enter your password'
            )->setLabel('Password')->setInputType('password')->isRequired()
        );

        $components[] = $form;

        $components[] = (new BasicLink())->addClass('forgottenPasswordLink')->addRoute(
            Routes::getRoutePattern('user.display.forgot-password')
        )->addText(System::getVariable('user_login_forgot_password_text') ?? 'Forgot password?');
        $components[] = (new BasicLink())->addClass('registerLink')->addRoute(
            Routes::getRoutePattern('user.display.register')
        )->addText(System::getVariable('user_login_register_text') ?? 'No account? Register.');

        if ($newComponents = Hook::processHook('login_page_components_after_form', $components)) {
            $components = $newComponents;
        }

        return System::asJson($response, $this->render::build($components));
    }

    /**
     * @param Request $request
     * @param Response $response
     * @param $args
     * @return Response
     */
    public function loginNfcPage(Request $request, Response $response, $args)
    {
        if($this->redirectIfLoggedIn($response)){
            return System::asJson($response);
        }

        $this->render::setActive("noUI");
        $this->render::setPageTitle("Login by NFC");
        $components = [];

        if ($newComponents = Hook::processHook('login_page_components_before_form', $components)) {
            $components = $newComponents;
        }

        if (isset($args['loginMessageType']) && Configuration::get('app.loginMessageTypes.'.$args['loginMessageType'])) {
            $components[] = (new ContentTitle())->addContent(
                Configuration::get('app.loginMessageTypes.'.$args['loginMessageType'])
            )->addClass('text-light');
        }

        $imageTile = new ImageTile();
        $imageTile->src(System::getVariable('user_login_nfc_icon') ?? '/assets/icons/nfc.svg');
        $imageTile->addClass('nfc-logo');

        $components[] = $imageTile;

        $redirect = $request->getQueryParams()['redirect'] ?? null;
        $redirect = $redirect ? htmlentities($redirect) : null;

        $nfc_key = $request->getQueryParams()['nfc_key'] ?? null;
        $nfc_key = $nfc_key ? htmlentities($nfc_key) : null;

        $form = (new Form())
            ->setMethod('post')
            ->setActionUrl(Routes::getRoutePattern('user.login.nfc'))
            ->setSubmitText(System::getVariable('user_login_nfc_submit_text') ?? 'Tap to login')
            ->addClass('login-form');

        if ($redirect) {
            $form->addField(
                (new InputField('redirect'))
                    ->setInputType('hidden')
                    ->setValue($redirect)
            );
        }

        if($nfc_key){
            $form->addField(
                (new InputField('nfc_key'))
                    ->setInputType('hidden')
                    ->setValue($nfc_key)
            );
        } else {
            //No NFC? Remove the submit button.
            $form->removeSubmitButton(true);
            $components[] = (new ContentTitle())->addContent(
                System::getVariable('user_login_nfc_description_text') ?? 'Tap your Access card on the reader to enable login.'
            )->addClass('text-light')->addClass('text-center');

        }

        $components[] = $form;
        $components[] = (new BasicLink())->addClass('forgottenPasswordLink')->addRoute(
            Routes::getRoutePattern('user.display.login')
        )->addText(System::getVariable('user_login_text') ?? 'Go to User Login');

        if ($newComponents = Hook::processHook('login_page_components_after_form', $components)) {
            $components = $newComponents;
        }

        return System::asJson($response, $this->render::build($components));
    }

    public function registerPage(Request $request, Response $response, $args)
    {
        if($this->redirectIfLoggedIn($response)){
            return System::asJson($response);
        }

        $this->render::setActive("noUI");
        $this->render::setPageTitle("Register");
        $components = [];

        $components[] = (new FormComponent())->src(Routes::getRoutePattern('user.display.forms.register'))
            ->title(System::getVariable('user_register_description_text') ?? "Register an account.")
            ->addClass('register-form')->setID('register');
        $components[] = (new BasicLink())
            ->addClass('loginLink')
            ->addRoute(Routes::getRoutePattern('user.display.login'))
            ->addText(System::getVariable('user_register_login_text') ?? 'Got an account? Log in.');

        return System::asJson($response, $this->render::build($components));
    }

    protected function redirectIfLoggedIn(Response $response): bool
    {
        if (currentUser::getCurrentUser()->isLoggedIn()) {
            Output::addMetadata('user.login.redirect', 'redirect', '/');
            return true;
        }
        return false;
    }

    /**
     * @return Response
     */
    public function loginForm(Request $request, Response $response)
    {
        return System::asJson($response, (new loginForm())->loginForm($request));
    }

    /**
     * Forgotten Password Page
     * @return Response
     */
    public function forgotPasswordPage(Request $request, Response $response)
    {
        if (currentUser::getCurrentUser()->isLoggedIn()) {
            Output::addMetadata('user.login.redirect', 'redirect', '/');
            return System::asJson($response);
        }

        $this->render::setActive("noUI");
        $this->render::setPageTitle("Forgotten Password");

        $form = (new Form())
            ->setMethod('post')
            ->setActionUrl(Routes::getRoutePattern('user.forgot-password'))
            ->setSubmitText('Get Reset Link')
            ->addField(
                (new inputField('email'))->setPlaceholder(
                    System::getVariable('user_login_email_placeholder') ?? 'Enter your email address'
                )->setLabel('Email Address')->setInputType('email')->isRequired()
            )
            ->title("Enter your email to reset your password.")
            ->addClass('forgotten-password-form')->setID('forgottenPassword');
        $components[] = $form;

        $components[] = (new BasicLink())->addClass('forgottenPasswordLink')->addRoute(
            Routes::getRoutePattern('user.display.login')
        )->addText(System::getVariable('user_login_return_login_text') ?? 'Return to login');

        $this->output::addResponse($request, $this->render::build($components));

        return System::asJson($response);
    }

    /**
     * @return Response
     */
    public function forgotPasswordCheckEmail(Request $request, Response $response)
    {
        $this->render::setActive("noUI");
        $this->render::setPageTitle("Check Email");

        $components[] = (new ContentTitle())
            ->addContent(
                "Please check your email for further instructions. The email may take upto a few minutes to arrive."
            )
            ->setColWidth('md', 12)->setID('forgottenPassword');

        $components[] = (new BasicLink())->addClass('forgottenPasswordLink')->addRoute(
            Routes::getRoutePattern('user.display.login')
        )->addText(System::getVariable('user_login_return_login_text') ?? 'Return to login');

        $this->output::addResponse($request, $this->render::build($components));

        return System::asJson($response);
    }

    /**
     * @return Response
     */
    public function forgotPasswordForm(Request $request, Response $response)
    {
        return System::asJson($response, (new forgottenPasswordForm())->forgottenPasswordForm());
    }

    /**
     * @return Response
     */
    public function registerForm(Request $request, Response $response)
    {
        $form = $this->primaryAccountForm(true);

        return System::asJson($response, $form->getBuiltFormArray());
    }

    /**
     * @return Response
     */
    public function userHome(Request $request, Response $response)
    {
        $rowWrapper = (new RowWrapper())->setColWidth('sm', 12)->addClass('manageForm-wrapper');
        $rowWrapper->setColWidth('sm', 12)->addClass('manageForm-wrapper');
        $rowWrapper->addComponent(new userEntityDisplayTile());
        if ($this->currentUser->isAllowed(Role::VIEW)) {
            $rowWrapper->addComponent(new roleEntityDisplayTile());
        }

        $this->render::setPageTitle('User Dashboard');
        $this->output::addResponse($request, $this->render::build([$rowWrapper]));

        return System::asJson($response);
    }

    /**
     * @return Response
     * @throws \Exception
     */
    public function userList(Request $request, Response $response)
    {
        //$filterForm = (new EntityFilteredDisplayTable((new userEntity()), [], false))->addEditButton('userEntity.display.put');
        //$filterForm->addFilterForm('user.display.forms.filters.userFilter');

        $components = [];

        $currentUser = currentUser::getCurrentUser();
        if ($currentUser && ($currentUser->isAllowed(User::CREATE))) {
            $createButton = (new BasicLink())
                ->addText('Create User')
                ->addRoute(Routes::getRoutePattern('userEntity.display.post'))
                ->addClass('btn btn-primary');
            $components[] = $createButton;
        }

        $components[] = (new CardEntityFilteredDisplayTable(new userEntity(), [], false, 'dropmenu'))
            ->displayPagination(FALSE)
            ->displayShortTotals(FALSE)
            ->hideIdColumn()
            ->addDropMenuItem('userEntity.display.put', 'Edit')
            ->addTitle('User List');

        //render
        $this->render::setPageTitle('User List');
        $this->output::addResponse($request, $this->render::build($components));

        return System::asJson($response);
    }

    public function create(Request $request, Response $response, $args)
    {
        $title = 'Create new user';
        if ($id = $args['id'] ?? false) {
            $user = new userEntity();
            $user->load($id);
            $title = 'Update ' . $user->getNiceName();
        }

        $form = $this->userEditForm($request, $response, $args);
        $this->render::setPageTitle($title);
        $this->output::addResponse($request, $this->render::build([genericComponents::dashboardBlockWrapper([$form])]));

        return System::asJson($response);
    }

    /**
     * @param array $args
     * @return Response
     * @throws \Exception
     */
    public function updateView(Request $request, Response $response, $args = [])
    {
        $title = 'Create a user';
        if ($id = $args['id'] ?? false) {
            $userEntity = new userEntity();
            $userEntity->load($id);
            $title = 'Edit (' . $id . ') ' . $userEntity->getNiceName();
        }

        $form = $this->userEditForm($request, $response, $args);
        $this->render::setPageTitle($title);
        $this->output::addResponse($request, $this->render::build($form));

        return System::asJson($response);
    }

    public function resetPasswordPage(Request $request, Response $response, $args)
    {
        $this->render::setActive("noUI");
        $this->render::setPageTitle("Forgotten Password");

        $hash = $args['hash'] ?? false;
        $valid = false;
        $components = [];
        if ($hash) {
            $user = new userEntity();
            $match = $user->getForgottenPasswordLink($hash);
            if (!empty($match) && ((time() - $match['created']) <= 86400 && $match['used'] == 0)) {
                $valid = true;
                $form = new Form();
                $form-- > setSubmitText('Reset Password Now')
                    ->setMethod('post')
                    ->setActionUrl(Routes::getRoutePattern('user.resetPassword'))
                    ->addField(
                        (new InputField('password'))->setLabel('Password')->setPlaceholder('Password')->setInputType(
                            'password'
                        )->addValidation("minLength", ["min" => 6])->isRequired()
                    )
                    ->addField(
                        (new InputField('confirm_password'))->setLabel('Confirm Password')->setPlaceholder(
                            'Confirm Password'
                        )->setInputType('password')->addValidation("minLength", ["min" => 6])->isRequired()
                    )
                    ->addField((new InputField('hash'))->setInputType('hidden')->setValue($args['hash'])->isRequired())
                    ->title("Reset your password using the fields below:")
                    ->addClass('forgotten-password-form');


                $components[] = $form;

                $components[] = (new BasicLink())->addClass(
                    'forgottenPasswordLink return mt-5 text-decoration-underline'
                )->addRoute(Routes::getRoutePattern('user.display.login'))->addText(
                    System::getVariable('user_login_return_login_text') ?? 'Return to login'
                );
            }
        }

        if (!$valid) {
            $components[] = (new ContentTitle())->addTitle('Your reset link has expired')->addContent(
                'Please request a new password reset link using the button below:'
            )->addClass('mb-5');
            $components[] = (new BasicLink())->addClass('btn btn-primary')->addText('Go to Password Reset')->addRoute(
                Routes::getRoutePattern('user.display.forgot-password')
            );
        }

        $this->output::addResponse($request, $this->render::build($components));

        return System::asJson($response);
    }

    public function resetPasswordSuccess(Request $request, Response $response, $args)
    {
        $this->render::setActive("noUI");
        $this->render::setPageTitle("Your password has been reset");

        $components = [
            (new ContentTitle())->addTitle('Password reset successful')->addContent(
                'Please login to the site using your new credentials:'
            )->addClass('mb-5'),
            (new BasicLink())->addClass('btn btn-primary')->addText('Go to login page')->addRoute(
                Routes::getRoutePattern('user.display.login')
            )
        ];

        $this->output::addResponse($request, $this->render::build($components));

        return System::asJson($response);
    }

    public function resetPasswordForm(Request $request, Response $response, $args)
    {
        $form = new formEntity();
        $form->setSubmitText('Reset Password Now');
        $form->setMethod('post');
        $form->setActionUrl(Routes::getRoutePattern('user.resetPassword'));
        $form->addField(
            (new inputField('password'))->setLabel('Password')->setPlaceholder('Password')->setInputType(
                'password'
            )->addValidation("minLength", ["min" => 6])->isRequired()
        );
        $form->addField(
            (new inputField('confirm_password'))->setLabel('Confirm Password')->setPlaceholder(
                'Confirm Password'
            )->setInputType('password')->addValidation("minLength", ["min" => 6])->isRequired()
        );
        $form->addField((new inputField('hash'))->setInputType('hidden')->setValue($args['hash'])->isRequired());

        return System::asJson($response, $form->getBuiltFormArray());
    }

    /**
     * @param $args
     * @return Response
     */
    public function userSummery(Request $request, Response $response, $args)
    {
        $structure = [
            'tableHeader' => [
                'User ID',
                'Name',
                'Active',
                'Email',
                'Last Logged In',
                'Roles',
            ],
            'fields' => [
                'id',
                'first_name',
                'active',
                'email',
                'last_login',
                'roles',
            ],
            'callables' => [
                'field__last_login' => function ($key, $entityId, $row): string {
                    return \apexl\Io\modules\userDisplay\callbacks\tableRowAlters::userShortTable(
                        $key,
                        $entityId,
                        $row
                    );
                },
                'field__first_name' => function ($key, $entityId, $row): string {
                    return \apexl\Io\modules\userDisplay\callbacks\tableRowAlters::userShortTable(
                        $key,
                        $entityId,
                        $row
                    );
                },
                'field__roles' => function ($key, $entityId, $row): string {
                    return \apexl\Io\modules\userDisplay\callbacks\tableRowAlters::userShortTable(
                        $key,
                        $entityId,
                        $row
                    );
                },
            ],
        ];

        return $this->getEntityData($request, $response, $args, (new userEntity()), [], $structure);
    }
}
