<?php

namespace Apexl\Io\User;

use Apexl\Config\Exceptions\MissingConfigException;
use Apexl\Io\Controller;
use Apexl\Io\Interfaces\ProvidesHooksInterface;
use Apexl\Io\Module;
use Apexl\Io\System\Interfaces\ProvidesRoutesInterface;
use Apexl\Io\User\Controllers\EndMasqueradeController;
use Apexl\Io\User\Controllers\ForgotPasswordController;
use Apexl\Io\User\Controllers\ForgotPasswordWithLinkController;
use Apexl\Io\User\Controllers\LoginByKeyController;
use Apexl\Io\User\Controllers\LoginController;
use Apexl\Io\User\Controllers\LogoutController;
use Apexl\Io\User\Controllers\RefreshController;
use Apexl\Io\User\Controllers\RegisterController;
use Apexl\Io\User\Controllers\ResetPasswordController;
use Apexl\Io\User\Controllers\StartMasqueradeController;
use Apexl\Io\User\Controllers\TwoFactorAuth\AuthenticateTwoFactorController;
use Apexl\Io\User\Controllers\TwoFactorAuth\CompleteTwoFactorSetupController;
use Apexl\Io\User\Controllers\TwoFactorAuth\ResetTwoFactorController;
use Apexl\Io\User\Controllers\TwoFactorAuth\SetupTwoFactorController;
use Apexl\Io\User\Controllers\TwoFactorAuth\VerifyRecoveryCodeController;
use Apexl\Io\User\Entities\RoleEntity;
use Apexl\Io\User\Entities\UserEntity;
use Apexl\Io\User\Enums\Permissions\Common;
use Apexl\Io\User\Enums\Permissions\Role;
use Apexl\Io\User\Enums\Permissions\User;
use Apexl\Io\User\Hooks\AddCreatedByToEntitiesOnSave;
use Apexl\Io\User\Hooks\AddMasqueradingWarning;
use Apexl\Io\User\Hooks\AddMiddleware;
use Apexl\Io\User\Hooks\AddModifiedByToEntitiesOnSave;
use Apexl\Io\User\Hooks\AddUserToGlobalPageData;
use Apexl\Io\User\Hooks\ApplyPermissionChecksToEntityFindQuery;
use Apexl\Io\User\Hooks\ConsoleCommands;
use Apexl\Io\User\Hooks\HashRefreshTokenBeforeStore;
use Apexl\Io\User\Hooks\HashUserPasswordBeforeStore;
use Apexl\Io\User\Hooks\RegisterModulePermissions;
use Apexl\Io\User\Hooks\RegisterRolePermissions;
use Apexl\Io\User\Hooks\RegisterTemplateFolder;
use Apexl\Io\User\Hooks\SendNewUserNotification;
use Apexl\Io\User\Interfaces\ProvidesPermissionsInterface;
use Apexl\Io\User\Middleware\RoutePermissionMiddleware;
use Apexl\Io\User\Services\Permissions;
use Apexl\Utils\Strings\Append;
use Slim\App;
use Slim\Routing\RouteCollectorProxy;

/**
 * Base module file, provides required methods to register things like routes and middleware.
 * Class installModule
 * @package apexl\Io\modules\install
 */
readonly class UserModule extends Module implements
    ProvidesHooksInterface,
    ProvidesPermissionsInterface,
    ProvidesRoutesInterface
{
    public static function moduleDir(string $append = ''): string
    {
        return Append::append(__DIR__, $append);
    }

    public function routes(): void
    {
        $this->apiGroup('user', function (RouteCollectorProxy $user): void {
            $this->addGlobalRoutes($user);
            $this->addTwoFactorRoutes($user);
            $this->addEntityRoute($user);
        });
    }

    private function addEntityRoute(RouteCollectorProxy $routes): void
    {
        // The one route the entity-route builder ever produced here. It declared four verbs
        // and registered one: get, post and delete were left on `Controller::<verb>`, which
        // is abstract and has none of them, so the builder skipped them — correctly, but
        // only after setting up patterns and callables for all four. Written out, it is the
        // route it always was.
        //
        // The name is load-bearing: RegisterRedirects switches on 'userEntity.put'.
        $routes->put('/entities/user[/{id}]', RegisterController::class)
            ->setName('userEntity.put')
            ->add(new RoutePermissionMiddleware(User::UPDATE_SELF));

        // Removed previously: the entire roleEntity CRUD bundle. Every verb was registered
        // with a callable the invoker cannot resolve, so all four answered 500 for every
        // caller in every consuming app. Role administration lives in apexl/io-permissions.
    }

    /**
     * @throws MissingConfigException
     */
    protected function addGlobalRoutes(RouteCollectorProxy $user): void
    {
        /**
         * Everything the user module exposes under `action`, which is all of these but the
         * forgot-password link below.
         *
         * The prefix and nothing else is shared: the verbs differ, and only two of them are
         * gated, so those two say so and the rest are open — which is what they have always
         * been. `end-masquerade` is deliberately among the open ones: you cannot require a
         * permission to stop pretending to be someone.
         */
        $user->group('/action', function (RouteCollectorProxy $routes): void {
            $routes->get('/logout', LogoutController::class)
                ->setName('user.logout');

            $routes->get('/masquerade/{id}', StartMasqueradeController::class)
                ->setName('user.masquerade')
                ->add(new RoutePermissionMiddleware(User::CAN_MASQUERADE));

            $routes->get('/end-masquerade', EndMasqueradeController::class)
                ->setName('user.masquerade.end');

            $routes->post('/register', RegisterController::class)
                ->setName('user.register')
                ->add(new RoutePermissionMiddleware(User::CAN_REGISTER));

            $routes->post('/login', LoginController::class)
                ->setName('user.login');

            if ($this->config->get('auth.enable_query_string_login')) {
                $routes->post('/login/nfc', LoginByKeyController::class)
                    ->setName('user.login.nfc');
            }

            $routes->post('/forgot-password', ForgotPasswordController::class)
                ->setName('user.forgot-password');

            $routes->post('/refresh', RefreshController::class)
                ->setName('user.refresh');

            $routes->post('/reset-password', ResetPasswordController::class)
                ->setName('user.resetPassword');
        });

        // The only one of these that is not an action: following the emailed link is a read.
        $user->get('/data/forgot-password/h/{hash}', ForgotPasswordWithLinkController::class)
            ->setName('user.forgot-password.hash');
    }

    /**
     * Two-factor authentication action routes (Plan 007). The self-service
     * routes are IS_LOGGED_IN and are on TwoFactorMiddleware's ignore-list so a
     * gated user can still reach them to pass the challenge / enrol. The
     * admin-reset route is deliberately NOT ignored (an enforced-mode admin must
     * pass their own 2FA before resetting another user's) and is guarded by the
     * dedicated RESET_TWO_FACTOR permission.
     *
     * @throws MissingConfigException
     */
    protected function addTwoFactorRoutes(RouteCollectorProxy $user): void
    {
        /**
         * The two-factor endpoints: same prefix, same verb, same gate, so the group says all
         * three once. Signed in but not yet through 2FA is still signed in — TwoFactorMiddleware
         * is what decides whether the second factor has been satisfied, and it runs before
         * anything a route requires.
         *
         * @route POST /api/v{VERSION}/user/action/two-factor-auth/{setup,complete-setup,authenticate,verify-recovery-code}
         */
        $user->group('/action/two-factor-auth', function (RouteCollectorProxy $routes): void {
            $routes->post('/setup', SetupTwoFactorController::class)
                ->setName('user.twoFactor.setup');

            $routes->post('/complete-setup', CompleteTwoFactorSetupController::class)
                ->setName('user.twoFactor.completeSetup');

            $routes->post('/authenticate', AuthenticateTwoFactorController::class)
                ->setName('user.twoFactor.authenticate');

            $routes->post('/verify-recovery-code', VerifyRecoveryCodeController::class)
                ->setName('user.twoFactor.verifyRecoveryCode');
        })->add(new RoutePermissionMiddleware(Common::IS_LOGGED_IN));
        $user->post('/action/admin/users/{id}/two-factor-auth/reset', ResetTwoFactorController::class)
            ->setName('user.admin.resetTwoFactor')
            ->add(new RoutePermissionMiddleware(User::RESET_TWO_FACTOR));
    }

    public function permissions(Permissions $permissions): void
    {
        $permissions->registerPermission(User::VIEW, 'View Users', 'Users');
        $permissions->registerPermission(User::CREATE, 'Create Users', 'Users');
        $permissions->registerPermission(User::UPDATE, 'Update Users', 'Users');
        $permissions->registerPermission(User::UPDATE_SELF, 'Update Self', 'Users');
        $permissions->registerPermission(User::CHANGE_ACTIVE_STATE, 'Change Active State', 'Users');
        $permissions->registerPermission(User::DELETE, 'Delete Users', 'Users');
        $permissions->registerPermission(User::CAN_REGISTER, 'Can Register (For Anon Users)', 'Users');
        $permissions->registerPermission(User::MANAGE, 'Manage All User Accounts');
        $permissions->registerPermission(User::RESET_TWO_FACTOR, 'Reset User Two-Factor Auth', 'Users');

        $permissions->registerPermission(Role::SET, 'Can Set User Roles', 'Users');
        $permissions->registerPermission(Role::VIEW, 'View Roles', 'Roles');
        $permissions->registerPermission(Role::CREATE, 'Create Roles', 'Roles');
        $permissions->registerPermission(Role::UPDATE, 'Update Roles', 'Roles');
        $permissions->registerPermission(Role::DELETE, 'Delete Roles', 'Roles');

    }

    public function hooks(): array
    {
        $hooks = [
            RegisterRolePermissions::class,
            SendNewUserNotification::class,
            RegisterModulePermissions::class,
            RegisterTemplateFolder::class,
            AddMiddleware::class,
            HashRefreshTokenBeforeStore::class,
            HashUserPasswordBeforeStore::class,
            AddCreatedByToEntitiesOnSave::class,
            AddModifiedByToEntitiesOnSave::class,
            ConsoleCommands::class,
        ];

        if (!defined('DAEDALUS') || !DAEDALUS) {
            $hooks[] = ApplyPermissionChecksToEntityFindQuery::class;
        }

        if (class_exists('Apexl\\Io\\Display\\Page')) {
            $hooks[] = AddUserToGlobalPageData::class;
            $hooks[] = AddMasqueradingWarning::class;
        }

        return $hooks;
    }
}
