<?php
/**
 * Manager Group Protection Plugin
 *
 * Protege el grupo "manager" y auto-asigna grupo "sie":
 * - Grupo "manager": Solo usuarios "sie*" pueden tenerlo, se oculta de la UI
 * - Grupo "sie": Se auto-asigna a "sie*", pero otros usuarios tambien pueden tenerlo, es visible en UI
 * - A usuarios NO "sie*" solo se les quita "manager", nunca "sie"
 */

use dokuwiki\Extension\ActionPlugin;
use dokuwiki\Extension\EventHandler;
use dokuwiki\Extension\Event;

class action_plugin_managerprotect extends ActionPlugin
{
    /** Grupo protegido (solo sie* pueden tenerlo) */
    const PROTECTED_GROUP = 'manager';

    /** Grupo auto-asignado a sie* (pero cualquier usuario puede tenerlo) */
    const SIE_GROUP = 'sie';

    /** Prefijo requerido para tener los grupos (case insensitive) */
    const REQUIRED_PREFIX = 'sie';

    /**
     * Registrar eventos
     */
    public function register(EventHandler $controller)
    {
        // Interceptar cambios de usuario (crear, modificar)
        $controller->register_hook('AUTH_USER_CHANGE', 'BEFORE', $this, 'handleUserChange');

        // Verificar grupo en cada login
        $controller->register_hook('AUTH_LOGIN_CHECK', 'AFTER', $this, 'handleLoginCheck');

        // Inyectar CSS para ocultar el grupo en la interfaz (en <head>)
        $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'addHideStyles');

        // Inyectar JS al final del body (robusto con defer_js=1)
        $controller->register_hook('TPL_ACT_RENDER', 'AFTER', $this, 'addInlineScript');

        // Interceptar la lista de grupos mostrados
        $controller->register_hook('HTML_USERMANAGERFORM_OUTPUT', 'BEFORE', $this, 'filterGroupsInForm');

        // Hook adicional para el formulario de edicion
        $controller->register_hook('FORM_USERMANAGER_EDITFORM_ADDITEM', 'BEFORE', $this, 'filterFormItem');
    }

    /**
     * Verifica y corrige el grupo manager en cada login
     */
    public function handleLoginCheck(Event $event, $param)
    {
        global $auth, $USERINFO;

        // Solo si el login fue exitoso
        if (!$event->result) {
            return;
        }

        $username = $event->data['user'];
        if (empty($username) || !$auth) {
            return;
        }

        // Obtener info actual del usuario
        $userinfo = $auth->getUserData($username);
        if (!$userinfo || !isset($userinfo['grps'])) {
            return;
        }

        $currentGroups = $userinfo['grps'];
        $newGroups = $this->enforceManagerRule($username, $currentGroups);

        // Si hay cambios, actualizar
        if ($currentGroups != $newGroups) {
            $auth->modifyUser($username, ['grps' => $newGroups]);
        }
    }

    /**
     * Maneja cambios de usuario (crear, modificar)
     * Fuerza la logica del grupo manager basada en el nombre de usuario
     */
    public function handleUserChange(Event $event, $param)
    {
        // $event->data contiene: ['type', 'params', 'modification_result']
        // type puede ser: 'create', 'modify', 'delete'
        // params contiene: [user, password, name, email, groups, ...]

        $type = $event->data['type'];

        if ($type === 'create' || $type === 'modify') {
            $params = &$event->data['params'];

            // El nombre de usuario esta en diferentes posiciones segun el tipo
            if ($type === 'create') {
                // create: [user, password, name, email, groups]
                $username = $params[0];
                $groupsIndex = 4;

                // === PUERTA DE CONTRASENA PARA USUARIOS SIE ===
                if (stripos($username, self::REQUIRED_PREFIX) === 0) {
                    // Cargar el hash almacenado
                    $storedHash = admin_plugin_managerprotect::getSieHash();

                    // Fail-safe: si no hay contrasena configurada, bloquear toda creacion sie*
                    if ($storedHash === null) {
                        msg('Creacion de usuario SIE bloqueada: no hay contrasena SIE configurada. Configure una en Admin > Proteccion Grupo Manager.', -1);
                        $event->preventDefault();
                        $event->stopPropagation();
                        return;
                    }

                    // Detectar si es auto-registro (no admin) — bloquear siempre
                    global $INPUT;
                    $isRegistration = ($INPUT->post->has('save') && !$INPUT->post->has('userid'));
                    if ($isRegistration) {
                        msg('El registro de usuarios con nombre que inicie con "sie" no esta permitido.', -1);
                        $event->preventDefault();
                        $event->stopPropagation();
                        return;
                    }

                    // Para creacion desde usermanager: verificar contrasena SIE
                    $siePassword = $INPUT->post->str('sie_creation_password');
                    if (empty($siePassword)) {
                        msg('Se requiere la contrasena SIE para crear usuarios con nombre que inicie con "sie".', -1);
                        $event->preventDefault();
                        $event->stopPropagation();
                        return;
                    }

                    if (!password_verify($siePassword, $storedHash)) {
                        msg('Contrasena SIE incorrecta. La creacion del usuario ha sido bloqueada.', -1);
                        $event->preventDefault();
                        $event->stopPropagation();
                        return;
                    }
                }
                // === FIN PUERTA DE CONTRASENA ===

            } else {
                // modify: [user, changes[]]
                $username = $params[0];
                $changes = &$params[1];

                // Si hay cambios de grupos, procesarlos
                if (isset($changes['grps'])) {
                    $changes['grps'] = $this->enforceManagerRule($username, $changes['grps']);
                }
                return; // Para modify, ya procesamos arriba
            }

            // Para create, procesar grupos
            if (isset($params[$groupsIndex])) {
                $groups = $params[$groupsIndex];
                if (is_array($groups)) {
                    $params[$groupsIndex] = $this->enforceManagerRule($username, $groups);
                }
            }
        }
    }

    /**
     * Aplica las reglas de grupos basada en el nombre de usuario
     *
     * Reglas:
     * - Grupo 'manager': SOLO usuarios "sie*" pueden tenerlo. Se auto-quita a los demas.
     * - Grupo 'sie': Se auto-asigna a usuarios "sie*", pero otros usuarios tambien pueden tenerlo.
     *
     * @param string $username Nombre de usuario
     * @param array $groups Grupos actuales
     * @return array Grupos modificados
     */
    protected function enforceManagerRule($username, $groups)
    {
        if (!is_array($groups)) {
            $groups = [];
        }

        $usernameStartsWithSie = (stripos($username, self::REQUIRED_PREFIX) === 0);

        if ($usernameStartsWithSie) {
            // Usuario "sie*" DEBE tener grupo 'manager' y grupo 'sie'
            if (!in_array(self::PROTECTED_GROUP, $groups)) {
                $groups[] = self::PROTECTED_GROUP;
            }
            if (!in_array(self::SIE_GROUP, $groups)) {
                $groups[] = self::SIE_GROUP;
            }
        } else {
            // Usuario NO "sie*" NO puede tener grupo 'manager' (se quita)
            // Pero SI puede conservar grupo 'sie' (no se toca)
            if (in_array(self::PROTECTED_GROUP, $groups)) {
                $groups = array_filter($groups, function($g) {
                    return $g !== self::PROTECTED_GROUP;
                });
                $groups = array_values($groups);
            }
        }

        return $groups;
    }

    /**
     * Inyecta CSS/JS para ocultar el grupo manager en formularios
     */
    public function addHideStyles(Event $event, $param)
    {
        global $ACT, $INPUT;

        // Solo en paginas de administracion de usuarios
        if ($ACT !== 'admin') {
            return;
        }

        $page = $INPUT->str('page');
        if ($page !== 'usermanager') {
            return;
        }

        // CSS para ocultar grupo manager
        $css = '
            /* Respaldo CSS para ocultar grupo manager (sie es visible) */
            input[value="manager"]:not([type="hidden"]),
            label:has(input[value="manager"]),
            option[value="manager"] {
                display: none !important;
                visibility: hidden !important;
            }
        ';

        $event->data['style'][] = [
            'type' => 'text/css',
            '_data' => $css
        ];
    }

    /**
     * Inyecta JS inline para ocultar grupo manager (robusto con defer_js=1)
     */
    public function addInlineScript(Event $event, $param)
    {
        global $ACT, $INPUT;

        if ($ACT !== 'admin') return;
        if ($INPUT->str('page') !== 'usermanager') return;

        echo '<script>
        (function() {
            var HIDDEN_GROUPS = ["manager"];
            function hideProtectedGroups() {
                HIDDEN_GROUPS.forEach(function(g) {
                    document.querySelectorAll("input[type=checkbox]").forEach(function(el) {
                        if (el.value === g) {
                            var c = el.closest("label") || el.parentElement;
                            if (c) c.style.display = "none";
                            el.style.display = "none";
                        }
                    });
                    document.querySelectorAll("select option").forEach(function(el) {
                        if (el.value === g || el.textContent.trim() === g) { el.style.display = "none"; el.disabled = true; }
                    });
                    document.querySelectorAll("label").forEach(function(l) {
                        var inp = l.querySelector("input");
                        if ((inp && inp.value === g) || l.textContent.trim().toLowerCase() === g) l.style.display = "none";
                    });
                });
            }
            hideProtectedGroups();
            setTimeout(hideProtectedGroups, 500);
            var obs = new MutationObserver(function() { hideProtectedGroups(); });
            obs.observe(document.body, { childList: true, subtree: true });

            // Ocultar "manager" de la columna Grupos en la lista de usuarios
            document.querySelectorAll("td").forEach(function(td) {
                var text = td.textContent.trim();
                if (text.indexOf("manager") !== -1 && text.indexOf(",") !== -1) {
                    td.innerHTML = td.innerHTML.replace(/,?\s*manager\s*,?/g, function(match) {
                        if (match.indexOf(",") === 0 && match.lastIndexOf(",") > 0) return ",";
                        return "";
                    }).replace(/^,\s*/, "").replace(/,\s*$/, "");
                }
            });
        })();
        </script>';
    }

    /**
     * Filtra grupos en el formulario de usuario
     */
    public function filterGroupsInForm(Event $event, $param)
    {
        // Este hook puede no existir en todas las versiones de DokuWiki
        // Es un fallback adicional
    }

    /**
     * Filtra items del formulario
     */
    public function filterFormItem(Event $event, $param)
    {
        // Este hook puede no existir en todas las versiones de DokuWiki
        // Es un fallback adicional
    }

    /**
     * Verifica si un usuario deberia tener el grupo manager (y auto-recibir grupo sie)
     *
     * @param string $username
     * @return bool
     */
    public static function shouldHaveManagerGroup($username)
    {
        return (stripos($username, self::REQUIRED_PREFIX) === 0);
    }
}
