<?php
/**
 * NIS2 Agile — SsoHelper
 *
 * Client SSO leggero per integrazione con AgileHub Tenant MS (http://172.18.0.1:4214).
 * Usa cURL nativo, zero dipendenze Composer.
 *
 * Adattato 2026-05-29 da TRPG /var/www/trpg-agile/shared/SsoHelper.php
 * (Progetto allineamento NIS2 ↔ TRPG — Fase 1 / G02).
 *
 * Workaround PHP-FPM Alpine + musl:
 *   getenv() può ritornare false in worker FPM → leggi anche da $_SERVER / $_ENV,
 *   poi cadi su default hardcoded (stesso pattern usato in VectorService/EmbedService).
 *
 * Modalità (SSO_MODE):
 *   - local:    nessuna chiamata SSO, login solo locale (default sicuro)
 *   - dual:     prova SSO, fallback locale se Tenant MS unreachable
 *   - sso_only: solo SSO, nessun fallback (massima centralizzazione)
 *
 * Configurazione .env:
 *   SSO_ENDPOINT=http://172.18.0.1:4214
 *   SSO_TIMEOUT_MS=3000
 *   SSO_MODE=local
 *   SSO_INTERNAL_KEY=<da vault-steward>
 *
 * Convenzioni di ritorno:
 *   - null      → SSO non raggiungibile (timeout/connessione) → caller deve fare fallback
 *   - array     → risposta JSON parsata (include '_httpStatus' int)
 *                 caller deve controllare ['_httpStatus'] e ['error'] per gestire 401/422/ecc.
 */
class SsoHelper
{
    private string $endpoint;
    private int $timeoutMs;
    private string $mode;
    private string $internalKey;

    public function __construct()
    {
        $this->endpoint = rtrim(
            self::env('SSO_ENDPOINT', 'http://172.18.0.1:4214'),
            '/'
        );
        $this->timeoutMs   = (int) self::env('SSO_TIMEOUT_MS', '3000');
        $this->mode        = self::env('SSO_MODE', 'local');
        $this->internalKey = self::env('SSO_INTERNAL_KEY', '');
    }

    /** Multi-source env lookup (PHP-FPM Alpine workaround). */
    private static function env(string $key, string $default): string
    {
        $v = getenv($key);
        if ($v !== false && $v !== '') return $v;
        if (!empty($_SERVER[$key])) return (string) $_SERVER[$key];
        if (!empty($_ENV[$key]))    return (string) $_ENV[$key];
        return $default;
    }

    public function getMode(): string { return $this->mode; }
    public function isLocalOnly(): bool { return $this->mode === 'local'; }
    public function isSsoOnly(): bool   { return $this->mode === 'sso_only'; }
    public function isDual(): bool      { return $this->mode === 'dual'; }

    /**
     * Login SSO.
     * @return array|null  null = unreachable (fallback locale), array = risposta SSO
     */
    public function login(string $email, string $password, string $product = 'nis2'): ?array
    {
        if ($this->isLocalOnly()) return null;
        return $this->post('/auth/sso/login', [
            'email'    => $email,
            'password' => $password,
            'product'  => $product,
        ]);
    }

    /**
     * Cambio password SSO (richiede JWT Bearer utente).
     */
    public function changePassword(string $jwt, string $currentPassword, string $newPassword): ?array
    {
        if ($this->isLocalOnly()) return null;
        return $this->post('/auth/sso/change-password', [
            'currentPassword' => $currentPassword,
            'newPassword'     => $newPassword,
        ], $jwt);
    }

    /**
     * Verifica password senza emettere JWT (uso interno — server-to-server).
     */
    public function verifyPassword(string $email, string $password): ?array
    {
        if ($this->isLocalOnly()) return null;
        return $this->postInternal('/auth/sso/verify-password', [
            'email'    => $email,
            'password' => $password,
        ]);
    }

    /**
     * Health check: SSO raggiungibile entro 1s?
     */
    public function isAvailable(): bool
    {
        $ch = curl_init($this->endpoint . '/health');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER     => true,
            CURLOPT_TIMEOUT_MS         => 1000,
            CURLOPT_CONNECTTIMEOUT_MS  => 1000,
        ]);
        curl_exec($ch);
        $ok = curl_errno($ch) === 0
              && (int) curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200;
        curl_close($ch);
        return $ok;
    }

    // --- HTTP helpers ---

    private function post(string $path, array $data, ?string $jwt = null): ?array
    {
        $headers = ['Content-Type: application/json'];
        if ($jwt) {
            $headers[] = 'Authorization: Bearer ' . preg_replace('/^Bearer\s+/i', '', $jwt);
        }

        $ch = curl_init($this->endpoint . $path);
        curl_setopt_array($ch, [
            CURLOPT_POST              => true,
            CURLOPT_POSTFIELDS        => json_encode($data),
            CURLOPT_RETURNTRANSFER    => true,
            CURLOPT_TIMEOUT_MS        => $this->timeoutMs,
            CURLOPT_CONNECTTIMEOUT_MS => $this->timeoutMs,
            CURLOPT_HTTPHEADER        => $headers,
        ]);
        $body = curl_exec($ch);
        $errno = curl_errno($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($errno !== 0) return null;

        $result = json_decode($body, true);
        if (is_array($result)) {
            $result['_httpStatus'] = $httpCode;
            return $result;
        }
        return ['_httpStatus' => $httpCode, 'raw' => $body];
    }

    private function postInternal(string $path, array $data): ?array
    {
        if ($this->internalKey === '') return null;

        $ch = curl_init($this->endpoint . $path);
        curl_setopt_array($ch, [
            CURLOPT_POST              => true,
            CURLOPT_POSTFIELDS        => json_encode($data),
            CURLOPT_RETURNTRANSFER    => true,
            CURLOPT_TIMEOUT_MS        => $this->timeoutMs,
            CURLOPT_CONNECTTIMEOUT_MS => $this->timeoutMs,
            CURLOPT_HTTPHEADER        => [
                'Content-Type: application/json',
                'X-Internal-Key: ' . $this->internalKey,
            ],
        ]);
        $body = curl_exec($ch);
        $errno = curl_errno($ch);
        curl_close($ch);

        if ($errno !== 0) return null;
        return json_decode($body, true);
    }
}
