<?php
declare(strict_types=1);

namespace App\Security;

use App\Core\Auth;
use App\Core\Database;
use App\Services\SecurityEventMonitor;

/**
 * SECURITY-ENTERPRISE-001 — Zero Trust Engine.
 *
 * Chaque requête authentifiée est revalidée à partir de l'identité serveur,
 * du contexte de session et de l'empreinte d'autorisation courante.
 */
final class ZeroTrustEngine
{
    private const RECENT_AUTH_SECONDS = 900;
    private const MAX_SESSION_AGE_FOR_SENSITIVE_WRITE = 28800;

    public static function initializeSession(?int $userId = null): void
    {
        $userId ??= (int)($_SESSION['user_id'] ?? 0);
        if ($userId <= 0) return;

        $_SESSION['zero_trust'] = [
            'version' => 1,
            'user_id' => $userId,
            'organisation_id' => (int)($_SESSION['organisation_id'] ?? 0),
            'agent_hash' => self::agentHash(),
            'network_hash' => self::networkHash(),
            'authorization_hash' => self::authorizationFingerprint($userId),
            'established_at' => time(),
            'last_verified_at' => time(),
            'risk_score' => 0,
            'network_change_acknowledged_at' => null,
        ];
    }

    /** @param array<string,mixed> $policy */
    public static function enforce(string $method, string $path, array $policy = []): void
    {
        $userId = (int)($_SESSION['user_id'] ?? 0);
        if ($userId <= 0) return;

        if (!isset($_SESSION['zero_trust']) || !is_array($_SESSION['zero_trust'])) {
            self::initializeSession($userId);
        }
        $state = &$_SESSION['zero_trust'];

        if ((int)($state['user_id'] ?? 0) !== $userId
            || (int)($state['organisation_id'] ?? 0) !== (int)($_SESSION['organisation_id'] ?? 0)) {
            self::terminate('zero_trust.identity_context_changed', 'Contexte d’identité modifié.');
        }

        if (!hash_equals((string)($state['agent_hash'] ?? ''), self::agentHash())) {
            self::terminate('zero_trust.device_changed', 'Contexte appareil modifié.');
        }

        $currentAuthorization = self::authorizationFingerprint($userId);
        if (!hash_equals((string)($state['authorization_hash'] ?? ''), $currentAuthorization)) {
            self::terminate('zero_trust.authorization_changed', 'Les autorisations du compte ont changé.');
        }

        $risk = 0;
        $networkChanged = !hash_equals((string)($state['network_hash'] ?? ''), self::networkHash());
        if ($networkChanged) {
            $risk += 35;
            if (empty($state['network_change_acknowledged_at'])) {
                SecurityEventMonitor::record('zero_trust.network_changed', 'warning', ['user_ref' => self::userReference($userId)]);
                $state['network_change_acknowledged_at'] = time();
            }
        }

        $sessionAge = time() - (int)($_SESSION['session_started_at'] ?? time());
        if ($sessionAge > self::MAX_SESSION_AGE_FOR_SENSITIVE_WRITE) $risk += 20;

        $write = in_array(strtoupper($method), ['POST','PUT','PATCH','DELETE'], true);
        $sensitive = self::isSensitivePath($path) || (($policy['zero_trust_sensitive'] ?? false) === true);
        if ($write && $sensitive) {
            $risk += 20;
            $recentAge = time() - (int)($_SESSION['authenticated_at'] ?? 0);
            if ($networkChanged || $sessionAge > self::MAX_SESSION_AGE_FOR_SENSITIVE_WRITE || $recentAge > self::RECENT_AUTH_SECONDS) {
                SecurityEventMonitor::record('zero_trust.step_up_required', 'warning', [
                    'user_ref' => self::userReference($userId),
                    'path_hash' => hash('sha256', self::normalize($path)),
                    'risk_score' => min(100, $risk),
                ]);
                Auth::requireRecentAuthentication(self::RECENT_AUTH_SECONDS);
                $state['network_hash'] = self::networkHash();
                $state['network_change_acknowledged_at'] = time();
                $risk = max(0, $risk - 35);
            }
        }

        $state['risk_score'] = min(100, $risk);
        $state['last_verified_at'] = time();
    }

    /** @return array<string,mixed> */
    public static function status(): array
    {
        $userId = (int)($_SESSION['user_id'] ?? 0);
        $state = is_array($_SESSION['zero_trust'] ?? null) ? $_SESSION['zero_trust'] : [];
        return [
            'enabled' => true,
            'user_bound' => $userId > 0 && (int)($state['user_id'] ?? 0) === $userId,
            'organisation_bound' => (int)($state['organisation_id'] ?? 0) === (int)($_SESSION['organisation_id'] ?? 0),
            'device_bound' => isset($state['agent_hash']) && hash_equals((string)$state['agent_hash'], self::agentHash()),
            'authorization_current' => $userId > 0 && isset($state['authorization_hash'])
                && hash_equals((string)$state['authorization_hash'], self::authorizationFingerprint($userId)),
            'network_consistent' => isset($state['network_hash']) && hash_equals((string)$state['network_hash'], self::networkHash()),
            'risk_score' => (int)($state['risk_score'] ?? 0),
            'last_verified_at' => (int)($state['last_verified_at'] ?? 0),
            'recent_auth_seconds' => self::RECENT_AUTH_SECONDS,
        ];
    }

    private static function authorizationFingerprint(int $userId): string
    {
        try {
            $pdo = Database::connection();
            $roles = $pdo->prepare(
                "SELECT CONCAT(r.id, ':', r.code, ':', r.actif)
                 FROM utilisateur_roles ur
                 INNER JOIN roles r ON r.id=ur.role_id
                 WHERE ur.utilisateur_id=:user_id AND r.deleted_at IS NULL
                 ORDER BY r.id"
            );
            $roles->execute(['user_id' => $userId]);

            $rolePermissions = $pdo->prepare(
                "SELECT CONCAT(rp.role_id, ':', p.id, ':', p.code, ':', p.actif)
                 FROM utilisateur_roles ur
                 INNER JOIN role_permissions rp ON rp.role_id=ur.role_id
                 INNER JOIN permissions p ON p.id=rp.permission_id
                 WHERE ur.utilisateur_id=:user_id
                 ORDER BY rp.role_id, p.id"
            );
            $rolePermissions->execute(['user_id' => $userId]);

            $overrides = $pdo->prepare(
                "SELECT CONCAT(up.permission_id, ':', up.accordee, ':', COALESCE(up.perimetre, ''))
                 FROM utilisateur_permissions up
                 WHERE up.utilisateur_id=:user_id
                 ORDER BY up.permission_id"
            );
            $overrides->execute(['user_id' => $userId]);

            $material = json_encode([
                $roles->fetchAll(\PDO::FETCH_COLUMN),
                $rolePermissions->fetchAll(\PDO::FETCH_COLUMN),
                $overrides->fetchAll(\PDO::FETCH_COLUMN),
                (int)($_SESSION['security_version'] ?? 0),
            ], JSON_UNESCAPED_SLASHES);
            return hash('sha256', (string)$material);
        } catch (\Throwable $e) {
            SecurityEventMonitor::record('zero_trust.authorization_check_failed', 'critical');
            return hash('sha256', 'unavailable:' . $userId . ':' . (int)($_SESSION['security_version'] ?? 0));
        }
    }

    private static function isSensitivePath(string $path): bool
    {
        $path = self::normalize($path);
        return (bool)preg_match('#^/(administration|studio|mises-a-jour|sauvegardes|utilisateurs|roles|permissions|audit|facturation|comptabilite|caisses|remises|avoirs)(?:/|$)#', $path);
    }

    private static function agentHash(): string
    {
        return hash('sha256', (string)($_SERVER['HTTP_USER_AGENT'] ?? 'unknown'));
    }

    private static function networkHash(): string
    {
        $ip = (string)($_SERVER['REMOTE_ADDR'] ?? 'unknown');
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            $parts = explode('.', $ip);
            $ip = implode('.', array_slice($parts, 0, 3)) . '.0/24';
        } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            $parts = explode(':', $ip);
            $ip = implode(':', array_slice($parts, 0, 4)) . '::/64';
        }
        return hash('sha256', $ip);
    }

    private static function userReference(int $userId): string
    {
        return substr(hash('sha256', 'axiom-zero-trust:' . $userId), 0, 20);
    }

    private static function terminate(string $event, string $message): never
    {
        SecurityEventMonitor::record($event, 'critical', ['user_ref' => self::userReference((int)($_SESSION['user_id'] ?? 0))]);
        Auth::logout();
        http_response_code(401);
        header('Content-Type: text/plain; charset=utf-8');
        exit('Session interrompue par le moteur Zero Trust. ' . $message . ' Reconnectez-vous.');
    }

    private static function normalize(string $path): string
    {
        $path = str_replace('/public', '', $path) ?: '/';
        $path = '/' . trim($path, '/');
        return $path === '/' ? '/' : rtrim($path, '/');
    }
}
