<?php

namespace App\Presentation\User;

use App\Model\AuthenticatorManager;
use App\Model\AuthorizatorManager;
//use App\Model\ProfileManager;
use App\Presentation\BasePresenter;
use Nette\Http\Request;
use Nette\Security\AuthenticationException;
use Nette\Security\Permission;

class UserPresenter extends BasePresenter
{
    //private ProfileManager $profileManager;
    private AuthenticatorManager $authenticatorManager;
    private AuthorizatorManager $authorizatorManager;
    private Permission $acl;
    private Request $httpRequest;

    public function __construct(/*ProfileManager $profileManager, */AuthenticatorManager $authenticatorManager, AuthorizatorManager $authorizatorManager, Request $httpRequest)
    {
        parent::__construct();
        //$this->profileManager = $profileManager;
        $this->authenticatorManager = $authenticatorManager;
        $this->authorizatorManager = $authorizatorManager;
        $this->acl = $this->authorizatorManager->getAcl();
        $this->httpRequest = $httpRequest;
    }

    public function actionChangepassword()
    {
        if (!$this->getUser()->isLoggedIn())
        {
            $this->error("User is not logged in", 403);
        }

        $payload = $this->getPayloadJsonAndCheckRequest(['password']);

        $this->authenticatorManager->changePassword($this->getUser()->getId(), $payload['password']);
        $this->sendJson(['success' => true]);
    }

    public function renderLogout()
    {
        $this->getUser()->logout();
        $this->sendJson(['ok']);
    }

    public function renderGetuser()
    {
        $user = $this->getUser();
        if (!$user->isLoggedIn()) {
            $this->sendJson(['status' => "NotLogged"]);
        }

        $user = $this->authenticatorManager->getUser($user->getId())->fetch();

        $output = [
            'status' => 'Logged',
            'user' => [
                'id' => $this->getUser()->getId(),
                'username' => $user->username,
                'email' => $user->email,
                //'school_type' => $user->school_type,
                'profile_image' => $user->profile_image,
                'bio' => $user->bio,
            ],
            'permissions' => $this->getUserPrivileges(),
            'roles' => $this->getUser()->getRoles()
        ];

        $this->sendJson($output);
    }

    public function actionRegister(): void
    {
        if ($this->getUser()->isLoggedIn()) {
            $this->error('User is already logged in.', 400);
        }

        $payload = $this->getPayloadJsonAndCheckRequest(['username', 'password', 'email', 'bio']);
        if (strlen($payload['username']) < 3 || strlen($payload['password']) < 3)
            $this->error("Nickname or password is too short", 400);

        try {
            $registerResult = $this->authenticatorManager->createUser($payload);
        }catch (\Exception) {
            $this->error("User with this name or email already exists.", 409);
        }

        if (!$registerResult) {// User already exists
            $this->error('User with this name already exists.', 409);
        }
        $this->sendJson(['createdUserId' => $registerResult]);
    }

    public function actionLogin(): void
    {
        $payload = $this->getPayloadJsonAndCheckRequest(['username', 'password']);

        try {

            $this->getUser()->login($payload['username'], $payload['password']);

            $identity = $this->getUser()->getIdentity();

            $this->sendJson([
                'success' => true,
                'status' => 'Logged',
                'user' => [
                    'id' => $this->getUser()->getId(),
                    'username' => $identity->username,
                    'email' => $identity->email,
                    'school_type' => $identity->school_type,
                    'profile_image' => $identity->profile_image,
                    'bio' => $identity->bio,
                ],
                'permissions' => $this->getUserPrivileges(),
                'roles' => $this->getUser()->getRoles()
            ]);
        } catch (AuthenticationException $e) {
            $this->error("Invalid credentials", 401);
        }
    }

    /**
     * Vrátí pole ['resource' => ['privilege', …]], hvězdička ['*'] značí plný přístup.
     */
    protected function getUserPrivileges(): array
    {
        $roles = $this->user->getRoles();      // pole stringů
        $result = [];
        $allPrivsMap = $this->collectAllPrivileges(); // ['article' => ['create', …]]

        foreach ($this->acl->getResources() as $resource) {
            // === plný přístup na resource? ===
            if ($this->rolesAllow($roles, $resource)) {
                $result[$resource] = ['*'];
                continue;
            }

            $allowed = [];
            foreach ($allPrivsMap[$resource] ?? [] as $privilege) {
                if ($this->rolesAllow($roles, $resource, $privilege)) {
                    $allowed[] = $privilege;
                }
            }

            if ($allowed !== []) {
                sort($allowed);
                $result[$resource] = $allowed;
            }
        }

        return $result;
    }

    /**
     * TRUE, pokud libovolná role z pole $roles má přístup k resource(/privilege).
     */
    private function rolesAllow(array $roles, string $resource, ?string $privilege = null): bool
    {
        foreach ($roles as $role) {
            if ($this->acl->isAllowed($role, $resource, $privilege)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Z interního pole $rules sesbírá seznam všech privilegí v ACL,
     * roztříděný podle resource.
     *
     * @return array<string, string[]>
     */
    /**
     * Vrátí mapu všech privilegí definovaných v ACL
     *     ['resource' => ['priv1', 'priv2', …]]
     * Hvězdička ['*'] značí plný přístup k celému resource.
     */
    private function collectAllPrivileges(): array
    {
        $ref = new \ReflectionClass($this->acl);
        $prop = $ref->getProperty('rules');
        $prop->setAccessible(true);
        $rules = $prop->getValue($this->acl);

        $out = [];

        $dig = static function (string $resource, array $ruleNode, array &$out): void {
            if (!empty($ruleNode['allPrivileges'])) {          // allow/deny bez privilege ⇒ *
                $out[$resource] = ['*'];
                return;
            }
            if (!empty($ruleNode['byPrivilege'])) {            // konkrétní privilegia
                $out[$resource] = array_merge(
                    $out[$resource] ?? [],
                    array_keys($ruleNode['byPrivilege']),
                );
            }
        };

        /* ---------- 1) Pravidla definovaná ke konkrétnímu resource ---------- */
        foreach ($rules['byResource'] ?? [] as $resource => $branch) {

            // a) pravidla platná pro všechny role
            if (isset($branch['allRoles'])) {
                $dig($resource, $branch['allRoles'], $out);
            }

            // b) pravidla vázaná na konkrétní roli
            if (isset($branch['byRole'])) {
                foreach ($branch['byRole'] as $roleNode) {
                    $dig($resource, $roleNode, $out);
                }
            }
        }

        /* ---------- 2) Pravidla platná pro všechny resource ----------------- */
        foreach ($rules['allResources'] ?? [] as $branch) {

            $targets = (array)$this->acl->getResources();   // bude potřeba prokopírovat níž

            // a) allRoles
            if (isset($branch['allRoles'])) {
                foreach ($targets as $res) {
                    $dig($res, $branch['allRoles'], $out);
                }
            }

            // b) byRole
            if (isset($branch['byRole'])) {
                foreach ($branch['byRole'] as $roleNode) {
                    foreach ($targets as $res) {
                        $dig($res, $roleNode, $out);
                    }
                }
            }
        }

        /* ---------- 3) Odstranění duplicit ---------------------------------- */
        foreach ($out as &$privs) {
            $privs = array_values(array_unique($privs));
        }

        return $out;
    }

}