<?php
declare(strict_types=1);

namespace App\Services\Reporting;

use App\Core\Database;
use App\Services\Certification\PackageCertificationService;
use App\Services\Compatibility\PackageCompatibilityService;
use App\Services\Policy\PackagePolicyService;
use App\Services\Trust\TrustEngineService;
use PDO;
use RuntimeException;
use ZipArchive;

final class EnterpriseCompatibilityReportService
{
    public function evaluatePackageId(int $packageId, ?int $userId = null, bool $persist = true): array
    {
        if ($packageId <= 0) {
            throw new RuntimeException('Identifiant de package invalide.');
        }
        $pdo = Database::connection();
        $stmt = $pdo->prepare('SELECT id,nom_package,fichier_original,version_cible,version_package,statut,checksum_sha256,importe_at 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.');
        }

        $manifest = $this->readManifest((string)$package['fichier_original']);
        $trust = (new TrustEngineService())->evaluatePackageId($packageId, $userId, false);
        $compatibility = (new PackageCompatibilityService())->evaluatePackageId($packageId, $userId, false);
        $policy = (new PackagePolicyService())->evaluatePackageId($packageId, $userId, false);
        $certificate = $this->activeCertificate($packageId);

        $issues = [];
        $warnings = [];
        $risks = [];

        $trustDecision = (string)($trust['decision'] ?? 'REJECTED');
        $compatibilityDecision = (string)($compatibility['decision'] ?? 'INCOMPATIBLE');
        $policyDecision = (string)($policy['decision'] ?? 'DENIED');
        $certificateStatus = (string)($certificate['status'] ?? 'ABSENT');
        $certificateDecision = (string)($certificate['decision'] ?? 'ABSENT');

        if ($trustDecision === 'REJECTED') {
            $issues[] = 'Le Trust Engine rejette le package.';
        } elseif ($trustDecision === 'WARNING') {
            $warnings[] = 'Le Trust Engine signale des avertissements.';
        }
        if ($compatibilityDecision === 'INCOMPATIBLE') {
            $issues[] = 'Le Compatibility Engine déclare le package incompatible.';
        } elseif ($compatibilityDecision === 'WARNING') {
            $warnings[] = 'Le Compatibility Engine signale des avertissements.';
        }
        if ($policyDecision === 'DENIED') {
            $issues[] = 'La Package Policy interdit l’installation.';
        } elseif ($policyDecision === 'WARNING') {
            $warnings[] = 'La Package Policy autorise avec avertissements.';
        }
        if ($certificate && in_array($certificateStatus, ['REVOKED','COMPROMISED','EXPIRED'], true)) {
            $issues[] = 'Le certificat du package est '.$certificateStatus.'.';
        }
        if ($certificate && $certificateDecision === 'REJECTED') {
            $issues[] = 'Le certificat contient une décision de rejet.';
        }
        if (!$certificate) {
            $warnings[] = 'Aucun certificat actif n’est associé au package.';
        }

        foreach ((array)($trust['issues'] ?? []) as $issue) $risks[] = 'Confiance : '.(string)$issue;
        foreach ((array)($compatibility['issues'] ?? []) as $issue) $risks[] = 'Compatibilité : '.(string)$issue;
        foreach ((array)($policy['issues'] ?? []) as $issue) $risks[] = 'Politique : '.(string)$issue;
        foreach ((array)($trust['warnings'] ?? []) as $warning) $warnings[] = (string)$warning;
        foreach ((array)($compatibility['warnings'] ?? []) as $warning) $warnings[] = (string)$warning;
        foreach ((array)($policy['warnings'] ?? []) as $warning) $warnings[] = (string)$warning;

        $trustScore = (int)($trust['score'] ?? 0);
        $compatibilityScore = (int)($compatibility['score'] ?? 0);
        $certificationScore = (int)($certificate['certification_score'] ?? 0);
        $policyScore = (int)($policy['score'] ?? 0);
        $certificateWeight = $certificate ? $certificationScore : 0;
        $score = (int)round(($trustScore * 0.30) + ($compatibilityScore * 0.30) + ($certificateWeight * 0.20) + ($policyScore * 0.20));
        $score = max(0, min(100, $score));

        $decision = $issues ? 'INSTALLATION_REFUSEE' : (($warnings || $risks) ? 'AUTORISEE_AVEC_AVERTISSEMENTS' : 'INSTALLATION_AUTORISEE');
        $previous = $this->latestReport($packageId);
        $comparison = $previous ? [
            'previous_report_id' => (int)$previous['id'],
            'previous_score' => (int)$previous['global_score'],
            'score_delta' => $score - (int)$previous['global_score'],
            'previous_decision' => (string)$previous['decision'],
            'decision_changed' => (string)$previous['decision'] !== $decision,
            'previous_created_at' => (string)$previous['created_at'],
        ] : null;

        $result = [
            'package' => $package,
            'manifest' => [
                'package' => (string)($manifest['package'] ?? $package['nom_package']),
                'package_id' => (string)($manifest['package_id'] ?? ''),
                'package_uuid' => (string)($manifest['package_uuid'] ?? ''),
                'package_revision' => (string)($manifest['package_revision'] ?? ''),
                'version' => (string)($manifest['version'] ?? $package['version_cible']),
                'publisher' => (string)($manifest['publisher'] ?? ''),
                'trust_level' => (string)($manifest['trust_level'] ?? ''),
                'build_number' => (string)($manifest['build_number'] ?? ''),
                'signature_algorithm' => (string)($manifest['signature_algorithm'] ?? ''),
            ],
            'trust' => $trust,
            'compatibility' => $compatibility,
            'certificate' => $certificate,
            'policy' => $policy,
            'scores' => [
                'trust' => $trustScore,
                'compatibility' => $compatibilityScore,
                'certification' => $certificationScore,
                'policy' => $policyScore,
                'global' => $score,
            ],
            'decision' => $decision,
            'issues' => array_values(array_unique($issues)),
            'warnings' => array_values(array_unique($warnings)),
            'risks' => array_values(array_unique($risks)),
            'comparison' => $comparison,
            'evaluated_at' => gmdate('c'),
        ];

        if ($persist) {
            $result['report_id'] = $this->persist($packageId, $userId, $result);
        }
        return $result;
    }

    public function dashboard(?int $reportId = null): array
    {
        $pdo = Database::connection();
        $stmt = $pdo->prepare('SELECT r.*,p.nom_package,p.version_cible FROM system_package_enterprise_reports r LEFT JOIN cda_update_packages p ON p.id=r.package_id ORDER BY r.id DESC LIMIT 100');
        $stmt->execute();
        $reports = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        $selected = null;
        if ($reportId !== null && $reportId > 0) {
            $selected = $this->report($reportId);
        }
        $packageStmt = $pdo->prepare('SELECT id,nom_package,version_cible,statut,importe_at FROM cda_update_packages ORDER BY id DESC LIMIT 250');
        $packageStmt->execute();
        $packages = $packageStmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        return ['reports' => $reports, 'selected' => $selected, 'packages' => $packages, 'generated_at' => date('Y-m-d H:i:s')];
    }

    public function report(int $reportId): array
    {
        $stmt = Database::connection()->prepare('SELECT r.*,p.nom_package,p.version_cible,p.fichier_original FROM system_package_enterprise_reports r LEFT JOIN cda_update_packages p ON p.id=r.package_id WHERE r.id=:id LIMIT 1');
        $stmt->execute(['id' => $reportId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) throw new RuntimeException('Rapport introuvable.');
        $decoded = json_decode((string)$row['report_json'], true);
        $row['report'] = is_array($decoded) ? $decoded : [];
        return $row;
    }

    public function csvRows(): array
    {
        $stmt = Database::connection()->prepare('SELECT r.id,r.created_at,p.nom_package,p.version_cible,r.global_score,r.trust_score,r.compatibility_score,r.certification_score,r.policy_score,r.decision FROM system_package_enterprise_reports r LEFT JOIN cda_update_packages p ON p.id=r.package_id ORDER BY r.id DESC LIMIT 5000');
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    private function readManifest(string $storedName): array
    {
        $path = BASE_PATH.'/storage/updates/packages/'.basename($storedName);
        if (!is_file($path)) return [];
        $zip = new ZipArchive();
        if ($zip->open($path) !== true) return [];
        try {
            $raw = $zip->getFromName('manifest.json');
            if (!is_string($raw)) return [];
            $manifest = json_decode($raw, true);
            return is_array($manifest) ? $manifest : [];
        } finally {
            $zip->close();
        }
    }

    private function activeCertificate(int $packageId): ?array
    {
        $stmt = Database::connection()->prepare("SELECT * FROM system_package_certificates WHERE package_id=:package_id ORDER BY id DESC LIMIT 1");
        $stmt->execute(['package_id' => $packageId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    }

    private function latestReport(int $packageId): ?array
    {
        $stmt = Database::connection()->prepare('SELECT id,global_score,decision,created_at FROM system_package_enterprise_reports WHERE package_id=:package_id ORDER BY id DESC LIMIT 1');
        $stmt->execute(['package_id' => $packageId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    }

    private function persist(int $packageId, ?int $userId, array $result): int
    {
        $scores = (array)$result['scores'];
        $stmt = Database::connection()->prepare('INSERT INTO system_package_enterprise_reports(report_uuid,package_id,global_score,trust_score,compatibility_score,certification_score,policy_score,decision,issues_json,warnings_json,risks_json,report_json,evaluated_by,created_at) VALUES(:uuid,:package_id,:global_score,:trust_score,:compatibility_score,:certification_score,:policy_score,:decision,:issues,:warnings,:risks,:report,:user_id,NOW())');
        $stmt->execute([
            'uuid' => $this->uuid(),
            'package_id' => $packageId,
            'global_score' => (int)($scores['global'] ?? 0),
            'trust_score' => (int)($scores['trust'] ?? 0),
            'compatibility_score' => (int)($scores['compatibility'] ?? 0),
            'certification_score' => (int)($scores['certification'] ?? 0),
            'policy_score' => (int)($scores['policy'] ?? 0),
            'decision' => (string)$result['decision'],
            'issues' => json_encode($result['issues'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
            'warnings' => json_encode($result['warnings'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
            'risks' => json_encode($result['risks'], JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
            'report' => json_encode($result, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),
            'user_id' => $userId,
        ]);
        return (int)Database::connection()->lastInsertId();
    }

    private function uuid(): 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);
    }
}
