<?php

namespace Diamond\Application\User;

use Core\Application\Loader;
use Core\Concrete\AuthFactory;
use Core\Concrete\PropelSessionHandler;
use Core\Concrete\RequestAwareTrait;
use Core\Contracts\UserAuthInterface;
use Core\Contracts\UserInterface;
use Core\Helpers\Text;
use Core\Menu\AdminMenu;
use Core\Security\Access;
use Diamond\Application\User\Exceptions\UserException;
use Diamond\Domain\User\Contracts\UserEntityInterface;
use Diamond\Infrastructure\Cache\StaticCache;
use Diamond\Infrastructure\Logging\RuntimeLogger;
use Diamond\Infrastructure\Models\User\UserQuery;
use Diamond\Repositories\User\UserRepository;
use Diamond\Application\User\Exceptions\UserNotAuthException;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\HttpFoundation\Session\Session;
use Core\Contracts\RequestAwareInterface;

class UserService implements LoggerAwareInterface, UserAuthInterface, RequestAwareInterface
{
    use LoggerAwareTrait;
    use RequestAwareTrait;

    const ACCESS_READ = "read";

    public const RULE_READ = 'read';
    public const RULE_DELETE = 'delete';
    public const RULE_WRITE = 'write';

    const USER_KEY = 'user_id';

    private UserEntityInterface|null $authUser = null;

    public function __construct(protected UserRepository       $userRepository,
                                protected AuthFactory          $authFactory,
                                protected \Diamond\Config\Main $mainConf,
                                protected Access               $access,
                                private AdminMenu              $adminMenu,
                                private PropelSessionHandler   $sessionDb,
                                private StaticCache            $staticCache,
                                protected Session              $session)
    {
    }

    /**
     * @return UserRepository
     */
    public function getUserRepository(): UserRepository
    {
        return $this->userRepository;
    }


    /**
     * Получение авторизации
     * @return \Aura\Auth\Auth|null
     */
    public function getAuth(): ?\Aura\Auth\Auth
    {
        if (!$lifeTime = $this->mainConf->getSessionLife()) {
            $lifeTime = 1;
        }

        //@todo регулирование жизни (30дней) повышенной сессии можно вынести в настройки
        if ((int)$this->request->cookies->get('remember') === 1) {
            $lifeTime = 24 * 30;
        }

        $resumeService = $this->authFactory->newResumeService(null, $lifeTime * 60 * 60, $lifeTime * 60 * 60);

        //объект авторизации
        $authService = $this->authFactory->newInstance();

        try {
            $resumeService->resume($authService);
        } catch (\PDOException $ex) {
            if (str_contains($ex->getMessage(), '_sessions')) {
                $this->sessionDb->createTable();
            }
        }


        //если срок сессии вышел очистим ее
        if ($authService->isExpired()) {
            $this->logout();
        } elseif ($authService->isValid()) {
            return $authService;
        }

        return null;
    }

    /**
     * @return bool
     */
    public function isAuth(): bool
    {
        try {
            $this->getAuthUser();
            return true;
        } catch (UserNotAuthException $ex) {
            return false;
        }
    }

    /**
     * Получить авторизованного пользователя
     * @return UserInterface
     */
    public function getAuthUser(): UserInterface
    {

        //для файлового манагера не доступно
//        if (false === strpos($this->request->getPathInfo(), '/admin')) {
//            throw new UserAccessException('path not admin');
//        }

        if ($auth = $this->getAuth()) {

            $userdata = $auth->getUserData();

            $sessionRemoteIp = $userdata['remote_addr']??null;


            $userKey = $userdata[self::USER_KEY] ?? null;

            /**
             * Если ключ найден и совпадает адрес клиента
             */
            if ($userKey && $sessionRemoteIp === $this->request->getClientIp()) {

                $authUser = $this->staticCache->getValue(StaticCache::CLIENT_AUTH_DATA . $userKey);

                if (!$authUser) {
                    $authUser = UserQuery::create()->filterByUsrDelete(0)->filterById((int)$userKey)->findOne();
                    if ($authUser) {
                        $this->staticCache->setValue(StaticCache::CLIENT_AUTH_DATA . $userKey, $authUser);
                        return $authUser;
                    }
                } else {
                    return $authUser;
                }
            }
        }

        throw new UserNotAuthException('user not auth');
    }

    /**
     * Авторизация клиента
     * @param $login
     * @param $password
     * @param bool $remeber
     * @return bool
     */
    public function authUser(string $login, string $password, $remeber = false)
    {

        if (!$User = UserQuery::create()
            ->filterByEmail($login)
            ->filterByUsrDelete(0)
            ->filterByPassword($password)
            ->findOne()) {

            return false;
        }

        $userdata = array(
            'name' => $User->getName(),
            self::USER_KEY => $User->getId(),
            'remote_addr' => $this->request->getClientIp(),
            'email' => $User->getEmail()
        );

        $userName = md5(serialize($userdata));

        //авторизующий серсис
        $login_service = $this->authFactory->newLoginService();

        //объект авторизации
        $auth = $this->authFactory->newInstance();

        if ($auth->isAnon() || $auth->getUserName() !== $userName) {

            $login_service->forceLogin($auth, $userName, $userdata);
        }
        return true;
    }

    /**
     * разлогивание клиента
     * @return bool
     */
    function logout(): bool
    {

        $logout_service = $this->authFactory->newLogoutService();

        //объект авторизации
        $auth = $this->authFactory->newInstance();

        $login_service = $this->authFactory->newLoginService();

        //объект авторизации
        //$auth = $this->authFactory->newInstance();
        //@todo разобратся почему не работает разлогивание в публике а в админке работает
        $login_service->forceLogin($auth, time(), []);

        return $auth->isAnon();
    }


    /**
     * @param int $userId
     * @param string $password
     * @todo перенести в суность
     * смена пароля
     */
    public function changeUserPassword(int $userId, string $password): void
    {

        if (!$user = $this->userRepository->findById($userId)) {
            throw new UserException('user not found by id ' . $userId);
        }

        $user->setPassword(md5($password));
        $this->userRepository->save($user);
    }

    /**
     * Права дсотупа к объекту для тек пользоватля
     * @param string $obj
     * @param string $act
     * @return bool
     * @throws \Casbin\Exceptions\CasbinException
     */
    public function isUserAccessMenu(string $obj, string $act): bool
    {
        //проверка меню может быть в публичной части сайта
        try {
            $authUser = $this->getAuthUser();
            $userId = $authUser->getId();
        } catch (UserAccessException|UserNotAuthException $ex) {
            $userId = 0;
        }


        //подменить текущий домен
        return $this->access->isAccess('.*', $userId, 'menu-' . $obj, $act);
    }

    /**
     * права доступа к Url
     * @param string $url
     * @param string $act
     * @return bool
     * @throws \Casbin\Exceptions\CasbinException
     */
    public function isUserAccessUrl(string $url, string $act): bool
    {
        $authUser = $this->getAuthUser();

        $permissions = $this->adminMenu->getPermissionsByUrl($url, 'read');

        if (!count($permissions)) {
            $object = 'page-' . $url;
        } else {
            $object = str_replace('.read', '', $permissions[0]);//разршение для роли как правило одно, берем первую
        }

//        try {
//
//        } catch (UserAccessException | UserNotAuthException $ex) {
//            $user = 'guest';
//        }

        //@todo подменить текущий домен
        return $this->access->isAccess('.*', $authUser->getId(), $object, $act);
    }


    /**
     * @param int $length
     * @return string
     */
    public function generatePassword($length = 6): string
    {
        return Text::generatePassword($length);
    }


    /**
     * @param $name
     * @param $email
     * @param $groupId
     * @return \Diamond\Infrastructure\Models\User\User
     * @throws \Propel\Runtime\Exception\PropelException
     */
    public function creatOrUpdateUser($name, $email, $groupId): \Diamond\Infrastructure\Models\User\User
    {
        if (!$group = \Diamond\Infrastructure\Models\UserGroup\UserGroupQuery::create()
            ->findOneById($groupId)
        ) {
            throw new UserException(sprintf(
                'User group `%s`, not found'
                , $groupId));
        }


        $user = \Diamond\Infrastructure\Models\User\UserQuery::create()
            ->findOneByEmail($email);


        if (!$user) {
            $user = (new \Diamond\Infrastructure\Models\User\User)
                ->setEmail($email)
                ->setName($name)
                ->setGroupId($group->getId());


            //создаем дубли страниц

            //создаем дубли промоблоков

        } else {
            $user
                ->setName($name)
                ->setGroupId($group->getId());
        }

        $user->save();

        return $user;
    }

}