<?php
class Controller extends Application
{
    protected $_controller, $_action;
    public $view;

    public function __construct($controller, $action)
    {
        parent::__construct();
        $this->_controller = $controller;
        $this->_action     = $action;
        $this->view        = new View();
    }

    public function loadModel($class_name)
    {
        return new $class_name();
    }

    /**
     * roleLayout() — always returns the shared shell 'layout_main'. The shell
     * itself picks the correct sidebar partial from the session role.
     * Add per-role wrapper layouts here only if a role needs a divergent shell.
     */
    protected function roleLayout(): string
    {
        return 'layout_main';
    }

    /** Slug of the current user's role — used by the shell to pick a sidebar. */
    public static function currentRoleSlug(): string
    {
        $g = strtolower(trim($_SESSION['user_group_name'] ?? ''));
        return preg_replace('/[^a-z0-9]+/', '_', $g);
    }

    protected function currentUserId(): int
    {
        return (int)($_SESSION['user_id'] ?? 0);
    }

    protected function currentUserGroup(): string
    {
        return $_SESSION['user_group_name'] ?? '';
    }

    protected function isAdministrator(): bool
    {
        return strtolower(trim($_SESSION['user_group_name'] ?? '')) === 'administrator';
    }

    protected function isExecutive(): bool
    {
        return strtolower(trim($_SESSION['user_group_name'] ?? '')) === 'executive';
    }

    protected function jsonResponse(array $data): void
    {
        header('Content-Type: application/json');
        echo json_encode($data);
        exit();
    }

    /**
     * csrfToken() — returns (and creates on first call) a per-session CSRF token.
     * Emit into every POST form via csrf_field() helper.
     */
    protected function csrfToken(): string
    {
        $k = defined('CSRF_TOKEN_KEY') ? CSRF_TOKEN_KEY : 'csrf_token';
        if (empty($_SESSION[$k])) {
            $_SESSION[$k] = bin2hex(random_bytes(32));
        }
        return $_SESSION[$k];
    }

    /**
     * verifyCsrf() — called at the top of every POST action. On mismatch,
     * emits 403 and exits. Use hash_equals to prevent timing attacks.
     */
    protected function verifyCsrf(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') return;
        $k = defined('CSRF_TOKEN_KEY') ? CSRF_TOKEN_KEY : 'csrf_token';
        $expected = $_SESSION[$k] ?? '';
        $got      = $_POST['csrf_token'] ?? ($_SERVER['HTTP_X_CSRF_TOKEN'] ?? '');
        if (!$expected || !$got || !hash_equals($expected, $got)) {
            http_response_code(403);
            header('Content-Type: text/plain; charset=utf-8');
            echo "CSRF validation failed.";
            exit();
        }
    }

    /**
     * clientIp() — best-effort client IP for audit and rate-limit tables.
     * Ignores XFF unless we are behind a known proxy (add trusted proxies here later).
     */
    protected function clientIp(): string
    {
        return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    }

    /**
     * Ops-gate: every Goods / Services / Works action that touches a project
     * must call this first. Rejects unless the project has passed CRM
     * onboarding (crm_status = APPROVED) OR is marked migrated_flag = 1.
     * On rejection, renders a 403 page and exits — the caller does not
     * need to handle the return value in the happy path.
     */
    protected function assertProjectOnboarded(int $project_id): void
    {
        $db = new DB();
        $rs = $db->mySelect(
            "SELECT project_no, crm_status, migrated_flag
               FROM c_projects
              WHERE c0d = :id AND deleted = 0
              LIMIT 1",
            [':id' => $project_id]
        );
        $p = $rs[0] ?? null;

        if (!$p) {
            $this->view->setLayout($this->roleLayout());
            $this->view->render('generals/fb_page', [
                'page_title' => 'Not found',
                'fb_msg'     => '<div class="alert alert-danger">Project not found.</div>'
            ]);
            exit();
        }
        if ($p['crm_status'] === 'APPROVED' || (int)$p['migrated_flag'] === 1) {
            return; // gate passes
        }
        $this->view->setLayout($this->roleLayout());
        $this->view->render('generals/fb_page', [
            'page_title' => 'Blocked · CRM onboarding required',
            'fb_msg'     => '<div class="alert alert-warning"><strong>Project ' . htmlspecialchars($p['project_no']) . '</strong> has not completed CRM onboarding (status: <code>' . htmlspecialchars($p['crm_status']) . '</code>). Ops actions are blocked until CRM submits and Exec approves the onboarding chain.</div>'
        ]);
        exit();
    }
}
