<?php

namespace apexl\Io\modules\user\entities;

use apexl\entityCore\enums\Casts;
use apexl\entityCore\interfaces\EntityOperatorInterface;
use apexl\entityCore\traits\hasCasts;
use apexl\Io\exceptions\RecordNotFoundException;
use apexl\Io\includes\Entity;
use apexl\Io\includes\System;
use apexl\Io\includes\Utils;
use apexl\Io\modules\user\collections\permissionCollection;
use apexl\Io\modules\user\collections\roleCollection;
use apexl\Io\modules\user\collections\userMetaCollection;
use apexl\Io\modules\user\entities\operators\userOperator;
use apexl\Io\modules\user\enums\permissions\Common;
use apexl\Io\modules\user\interfaces\PermissionInterface;
use apexl\Io\modules\user\services\userMetaManagerService;
use apexl\Io\modules\user\services\userPermissionManagerService;
use apexl\Io\modules\user\services\userRoleManagerService;
use DateTimeImmutable;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;

/**
 * @property ?int $id
 * @property string $first_name
 * @property string $last_name
 * @property bool $active
 * @property string $email
 * @property string $password
 * @property ?DateTimeImmutable $last_login
 * @property ?DateTimeImmutable $created
 * @property ?string $created_by
 * @property ?DateTimeImmutable $modified
 * @property ?string $modified_by
 * @property string $salt
 * @property string nfc_key
 * @mixin userOperator
 */
class userEntity extends Entity
{
    use hasCasts {
        casts as traitCasts;
    }

    final public const TABLE = 'users';

    /** @var userOperator */
    protected EntityOperatorInterface $operator;

    private userMetaManagerService $userMetaManager;
    private userPermissionManagerService $permissionManager;
    private userRoleManagerService $roleManager;

    /**
     * @throws NotFoundExceptionInterface
     * @throws ContainerExceptionInterface
     * @throws Exception
     */
    public function __construct()
    {
        parent::__construct(self::TABLE);
        $this->setOperator(new userOperator(self::TABLE));
        $this->setSensitiveData(['password', 'salt']);
        $this->setRequiredData(['email', 'first_name', 'last_name', 'password']);
        $this->addReadOnlyField('salt')->addReadOnlyField('created');

        $this->userMetaManager = container()->make(userMetaManagerService::class, [
            'user' => $this,
        ]);
    }

    /**
     * @throws RecordNotFoundException
     */
    public static function fromEmail(string $email): userEntity
    {
        return self::from('email', $email);
    }

    /**
     * @throws RecordNotFoundException
     */
    public static function fromNfcKey(string $nfcKey): userEntity
    {
        return self::from('nfc_key', $nfcKey);
    }

    /**
     * @param array $conditions
     * @param array $orderBy
     * @param false $limit
     * @param false $offset
     * @param bool $asEntities
     * @return array
     * @throws Exception
     */
    public function loadMultiple(
        array $conditions = [],
        array $orderBy = [],
        $limit = false,
        $offset = false,
        bool $asEntities = true
    ): array {
//        if ($currentUser->isAllowed('ViewUsers')) {
        $conditions = array_merge($conditions, ["id" => ["users.id", null, "IS NOT NULL"]]);

//        }

        return parent::loadMultiple($conditions, $orderBy, $limit, $offset, $asEntities);
    }

    public function casts(): array
    {
        return [
            ...$this->traitCasts(),
            'last_login' => Casts::DATETIME_STAMP,
        ];
    }

    public function isAllowed(PermissionInterface|array $permissions): bool
    {
        $permissions = is_array($permissions) ? $permissions : [$permissions];

        foreach ($permissions as $permission) {
            //we have a few core permissions to account for 'standard' functionality
            return match ($permission) {
                Common::CAN_DO_NOTHING => false,
                Common::ALLOW_ALL => true,
                Common::IS_LOGGED_IN => $this->isLoggedIn(),
                default => $this->permissionManager()->can($permission),
            };
        }

        //no access? Return FALSE;
        return false;
    }

    public function isLoggedIn(): bool
    {
        return ($this->id !== null && !empty($this->id));
    }

    private function permissionManager(): userPermissionManagerService
    {
        if (!isset($this->permissionManager)) {
            $this->permissionManager = System::makeRegisteredService(userPermissionManagerService::class, [
                'user' => $this,
                'roles' => $this->roles(),
            ]);
        }

        return $this->permissionManager;
    }

    public function roles(): roleCollection
    {
        return $this->roleManager()->roles();
    }

    private function roleManager(): userRoleManagerService
    {
        if (!isset($this->roleManager)) {
            $this->roleManager = System::makeRegisteredService(userRoleManagerService::class, [
                'user' => $this,
            ]);
        }

        return $this->roleManager;
    }

    public function getUsersByRoleName($name)
    {
        $roles = new roleEntity();
        $roles->loadByName($name);

        return $this->operator->getUsersByRoleId($roles->id);
    }

    public function addRole(string|roleEntity|int $role): userEntity
    {
        $this->roleManager()->add($role);

        return $this;
    }

    public function removeRoles(): userEntity
    {
        $this->roleManager()->clear();
        $this->data['roles'] = [];

        return $this;
    }

    public function getNiceName(): string
    {
        return $this->id !== null ? sprintf('%s %s', $this->first_name, $this->last_name) : 'Missing User';
    }

    public function hasRole(string|int|roleEntity $roleName): bool
    {
        return $this->roleManager()->has($roleName);
    }

    public function allMeta(): userMetaCollection
    {
        return $this->userMetaManager->allMeta();
    }

    public function hasMeta(string $key): bool
    {
        return $this->userMetaManager->hasMeta($key);
    }

    public function meta(string $key): ?userMetaEntity
    {
        return $this->userMetaManager->meta($key);
    }

    public function removeMeta(string $key): void
    {
        $this->userMetaManager->removeMeta($key);
    }

    public function addMeta($key, $value): void
    {
        $this->userMetaManager->addMeta($key, $value);
    }

    public function permissions(bool $includeRoles = true): permissionCollection
    {
        return $this->permissionManager()->permissions($includeRoles);
    }

    public function addPermission(PermissionInterface $permission): void
    {
        $this->permissionManager()->add($permission);
    }

    public function removePermission(PermissionInterface $permission): void
    {
        $this->permissionManager()->remove($permission);
    }

    protected function fieldConfig(): array
    {
        return $this->withCasts([
            "id" => [
                'name' => "ID",
            ],
            "first_name" => [
                'name' => "First Name",
            ],
            "last_name" => [
                'name' => "Last Name",
            ],
            "active" => [
                'name' => "Active",
                'display' => function ($active) {
                    return $active ? 'Yes' : 'No';
                },
            ],
            "email" => [
                'name' => "Email Address",
            ],
            "password" => [
                'name' => "Password",
                'display' => function (): string {
                    return 'Hidden';
                },
            ],
            "last_login" => [
                'name' => "Last Login",
                'display' => function ($lastLogin) {
                    return $lastLogin ? date('d-m-Y H:i:s', $lastLogin) : 'Never';
                },
            ],
            "salt" => [
                'name' => "Salt",
                'display' => function (): string {
                    return 'Hidden';
                },
            ],
            "modified" => [
                'name' => "Modified",
                'display' => function () {
                    return Utils::HIDE_FROM_DISPLAY;
                },
            ],
            "modified_by" => [
                'name' => "Modified By",
                'display' => function () {
                    return Utils::HIDE_FROM_DISPLAY;
                },
            ],
            "roles" => [
                'name' => "Roles",
                'display' => function ($roles) {
                    $niceNames = [];
                    $roleEntity = new roleEntity();
                    if (is_string($roles)) {
                        $roles = unserialize($roles) ?: [];
                    }
                    if (!empty($roles) && is_array($roles)) {
                        foreach ($roles as $rid) {
                            $roleEntity->load($rid);
                            $niceNames[] = $roleEntity->name;
                        }
                    }

                    return $niceNames === [] ? 'No Roles' : implode(', ', $niceNames);
                },
            ],
        ]);
    }
}
