<?php
declare(strict_types=1);
namespace App\Services\Certification;
use App\Core\Database;
use App\Services\Compatibility\PackageCompatibilityService;
use App\Services\Package\PackageDescriptor;
use App\Services\Trust\TrustEngineService;
use PDO;
use RuntimeException;
final class PackageCertificationService
{
    public function certifyPackage(int $packageId, ?int $userId): array
    {
        if ($packageId <= 0) {
            throw new RuntimeException('Identifiant de package invalide.');
        }
        $pdo = Database::connection();
        $stmt = $pdo->prepare('SELECT id, nom_package, version_package, version_cible, checksum_sha256, statut FROM cda_update_packages WHERE id = :id LIMIT 1');
        $stmt->execute(['id' => $packageId]);
        $package = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$package) {
            throw new RuntimeException('Package introuvable.');
        }

        $fileStmt = $pdo->prepare('SELECT fichier_original FROM cda_update_packages WHERE id=:id LIMIT 1');
        $fileStmt->execute(['id' => $packageId]);
        $fileName = $fileStmt->fetchColumn();
        if (!is_string($fileName) || $fileName === '') { throw new RuntimeException('Archive du package introuvable.'); }
        $zipPath = BASE_PATH.'/storage/updates/packages/'.basename($fileName);
        $descriptor = PackageDescriptor::fromZip($zipPath, $packageId);
        $trust = (new TrustEngineService())->evaluatePackageFile($zipPath, $packageId, $userId, false);
        $compatibility = (new PackageCompatibilityService())->evaluatePackageFile($zipPath, $packageId, $userId, false);
        $trustDecision = (string)($trust['decision'] ?? 'REJECTED');
        $compatibilityDecision = (string)($compatibility['decision'] ?? 'INCOMPATIBLE');
        $trustScore = (int)($trust['score'] ?? 0);
        $compatibilityScore = (int)($compatibility['score'] ?? 0);
        $score = (int)round(($trustScore * 0.55) + ($compatibilityScore * 0.45));
        $decision = 'CERTIFIED';
        $status = 'ACTIVE';
        if ($trustDecision === 'REJECTED' || $compatibilityDecision === 'INCOMPATIBLE') {
            $decision = 'REJECTED';
            $status = 'REJECTED';
        } elseif ($trustDecision === 'WARNING' || $compatibilityDecision === 'WARNING') {
            $decision = 'CERTIFIED_WITH_WARNINGS';
            $status = 'ACTIVE';
        }

        $certificateUuid = $this->uuidV4();
        $packageChecksum = strtolower($descriptor->checksum());
        $evidence = [
            'package_id' => $packageId,
            'package_name' => (string)$package['nom_package'],
            'package_version' => (string)($package['version_package'] ?: $package['version_cible']),
            'package_checksum' => $packageChecksum,
            'trust' => $trust,
            'compatibility' => $compatibility,
            'decision' => $decision,
            'score' => $score,
            'issued_at' => gmdate('c'),
        ];
        $evidenceJson = json_encode($evidence, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($evidenceJson)) {
            throw new RuntimeException('Rapport de certification invalide.');
        }
        $certificateFingerprint = hash('sha256', $certificateUuid."\n".$evidenceJson);

        $pdo->beginTransaction();
        try {
            $supersede = $pdo->prepare("UPDATE system_package_certificates SET status = 'SUPERSEDED', updated_at = NOW() WHERE package_id = :package_id AND status = 'ACTIVE'");
            $supersede->execute(['package_id' => $packageId]);
            $insert = $pdo->prepare('INSERT INTO system_package_certificates (certificate_uuid, package_id, package_name, package_version, package_checksum_sha256, certificate_fingerprint_sha256, trust_score, compatibility_score, certification_score, decision, status, authority_code, evidence_json, issued_by, issued_at, updated_at) VALUES (:uuid, :package_id, :package_name, :package_version, :package_checksum, :fingerprint, :trust_score, :compatibility_score, :certification_score, :decision, :status, :authority_code, :evidence_json, :issued_by, NOW(), NOW())');
            $insert->execute([
                'uuid' => $certificateUuid,
                'package_id' => $packageId,
                'package_name' => (string)$package['nom_package'],
                'package_version' => (string)($package['version_package'] ?: $package['version_cible']),
                'package_checksum' => $packageChecksum,
                'fingerprint' => $certificateFingerprint,
                'trust_score' => $trustScore,
                'compatibility_score' => $compatibilityScore,
                'certification_score' => $score,
                'decision' => $decision,
                'status' => $status,
                'authority_code' => 'AXIOM_INTERNAL',
                'evidence_json' => $evidenceJson,
                'issued_by' => $userId,
            ]);
            $certificateId = (int)$pdo->lastInsertId();
            $event = $pdo->prepare('INSERT INTO system_package_certificate_events (certificate_id, event_type, reason, actor_user_id, created_at) VALUES (:certificate_id, :event_type, :reason, :actor_user_id, NOW())');
            $event->execute([
                'certificate_id' => $certificateId,
                'event_type' => $decision === 'REJECTED' ? 'REJECTED' : 'ISSUED',
                'reason' => $decision,
                'actor_user_id' => $userId,
            ]);
            $pdo->commit();
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $e;
        }
        return ['decision' => $decision, 'score' => $score, 'certificate_uuid' => $certificateUuid];
    }

    public function revokeCertificate(int $certificateId, string $reason, ?int $userId): void
    {
        if ($certificateId <= 0) {
            throw new RuntimeException('Certificat invalide.');
        }
        if ($reason === '') {
            throw new RuntimeException('Le motif de révocation est obligatoire.');
        }
        $pdo = Database::connection();
        $pdo->beginTransaction();
        try {
            $update = $pdo->prepare("UPDATE system_package_certificates SET status = 'REVOKED', revoked_reason = :reason, revoked_by = :user_id, revoked_at = NOW(), updated_at = NOW() WHERE id = :id AND status = 'ACTIVE'");
            $update->execute(['reason' => $reason, 'user_id' => $userId, 'id' => $certificateId]);
            if ($update->rowCount() !== 1) {
                throw new RuntimeException('Certificat actif introuvable.');
            }
            $event = $pdo->prepare('INSERT INTO system_package_certificate_events (certificate_id, event_type, reason, actor_user_id, created_at) VALUES (:certificate_id, :event_type, :reason, :actor_user_id, NOW())');
            $event->execute(['certificate_id' => $certificateId, 'event_type' => 'REVOKED', 'reason' => $reason, 'actor_user_id' => $userId]);
            $pdo->commit();
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $e;
        }
    }

    public function dashboard(): array
    {
        $pdo = Database::connection();
        $stmt = $pdo->prepare('SELECT id, certificate_uuid, package_id, package_name, package_version, certification_score, decision, status, authority_code, issued_at, revoked_at, revoked_reason FROM system_package_certificates ORDER BY id DESC LIMIT 100');
        $stmt->execute();
        return ['certificates' => $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [], 'generated_at' => date('Y-m-d H:i:s')];
    }

    private function uuidV4(): string
    {
        $data = random_bytes(16);
        $data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
        $data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
        $hex = bin2hex($data);
        return substr($hex, 0, 8).'-'.substr($hex, 8, 4).'-'.substr($hex, 12, 4).'-'.substr($hex, 16, 4).'-'.substr($hex, 20, 12);
    }
}
