<?php

declare(strict_types=1);

final class SystemHealthService
{
    public static function report(): array
    {
        $checks = [];
        $root = dirname(__DIR__, 2);
        $db = DB::pdo();
        foreach (InstallationService::runtimeChecks() as $c) {
            $checks[] = self::check(
                $c['ok'] ? 'ok' : ($c['level'] === 'required' ? 'error' : 'warning'),
                'environment',
                $c['label'],
                $c['detail'],
            );
        }
        try {
            $integrity = (string) $db->query('PRAGMA integrity_check')->fetchColumn();
            $checks[] = self::check(
                $integrity === 'ok' ? 'ok' : 'error',
                'database',
                t('health.sqlite_integrity'),
                $integrity === 'ok' ? t('health.sqlite_ok') : $integrity,
            );
        } catch (Throwable $e) {
            $checks[] = self::check('error', 'database', t('health.sqlite_integrity'), $e->getMessage());
        }
        try {
            $fk = $db->query('PRAGMA foreign_key_check')->fetchAll(PDO::FETCH_ASSOC);
            $checks[] = self::check(
                !$fk ? 'ok' : 'error',
                'database',
                t('health.foreign_keys'),
                !$fk ? t('health.no_fk_error') : t('health.fk_errors', ['count' => count($fk)]),
            );
        } catch (Throwable $e) {
            $checks[] = self::check('error', 'database', t('health.foreign_keys'), $e->getMessage());
        }
        $migrationFiles = array_map('basename', glob($root . '/migrations/*.sql') ?: []);
        $applied = $db->query('SELECT name FROM _migrations')->fetchAll(PDO::FETCH_COLUMN);
        $pending = array_values(array_diff($migrationFiles, $applied));
        $checks[] = self::check(
            !$pending ? 'ok' : 'warning',
            'database',
            t('health.migrations'),
            !$pending
                ? t('health.migrations_ok', ['count' => count($applied)])
                : t('health.migrations_pending', ['count' => count($pending)]),
        );
        $dbFile = $root . '/data/refuge.sqlite';
        $checks[] = self::check(
            is_file($dbFile) && is_readable($dbFile) ? 'ok' : 'error',
            'storage',
            t('health.database_file'),
            is_file($dbFile) ? self::bytes((int) filesize($dbFile)) : t('health.file_missing'),
        );
        $free = @disk_free_space($root);
        $status =
            $free === false
                ? 'warning'
                : ($free >= 1_073_741_824
                    ? 'ok'
                    : ($free >= 268_435_456
                        ? 'warning'
                        : 'error'));
        $checks[] = self::check(
            $status,
            'storage',
            t('health.disk_space'),
            $free === false ? t('health.unknown') : self::bytes((int) $free),
        );
        $backups = BackupService::list();
        if (!$backups) {
            $checks[] = self::check('error', 'continuity', t('health.last_backup'), t('health.no_backup'));
        } else {
            $latest = $backups[0];
            $date = DateTimeImmutable::createFromFormat(
                'd/m/Y H:i:s',
                (string) $latest['date'],
                new DateTimeZone('Europe/Paris'),
            );
            $days = $date
                ? (int) $date->diff(new DateTimeImmutable('now', new DateTimeZone('Europe/Paris')))->format('%a')
                : 999;
            $checks[] = self::check(
                $days <= 7 ? 'ok' : ($days <= 30 ? 'warning' : 'error'),
                'continuity',
                t('health.last_backup'),
                t('health.backup_age', ['date' => $latest['date'], 'days' => $days]),
            );
        }
        [$missing, $orphans] = self::photoConsistency($db, $root);
        $checks[] = self::check(
            $missing === 0 ? 'ok' : 'warning',
            'files',
            t('health.missing_photos'),
            t('health.file_count', ['count' => $missing]),
        );
        $checks[] = self::check(
            $orphans === 0 ? 'ok' : 'warning',
            'files',
            t('health.orphan_photos'),
            t('health.file_count', ['count' => $orphans]),
        );
        $display = (string) ini_get('display_errors');
        $production = getenv('APP_ENV') !== 'development';
        $checks[] = self::check(
            !$production || !in_array(strtolower($display), ['1', 'on', 'yes', 'true'], true) ? 'ok' : 'warning',
            'security',
            t('health.display_errors'),
            $production ? t('health.production_value', ['value' => $display ?: 'Off']) : t('health.development_mode'),
        );
        $logErrors = filter_var(ini_get('log_errors'), FILTER_VALIDATE_BOOL);
        $errorLog = trim((string) ini_get('error_log'));
        $checks[] = self::check(
            $logErrors ? 'ok' : 'warning',
            'security',
            t('health.error_log'),
            $logErrors ? ($errorLog ?: t('health.system_log')) : t('health.logging_disabled'),
        );
        $applicationLog = $root . '/storage/logs/php-errors.log';
        $recent = self::recentLogLines($applicationLog, 7);
        $checks[] = self::check(
            $recent === 0 ? 'ok' : 'warning',
            'security',
            t('health.recent_errors'),
            $recent ? t('health.recent_error_count', ['count' => $recent]) : t('health.no_recent_error'),
        );
        $summary = ['ok' => 0, 'warning' => 0, 'error' => 0];
        foreach ($checks as $c) {
            $summary[$c['status']]++;
        }
        return [
            'checks' => $checks,
            'summary' => $summary,
            'generated_at' => (new DateTimeImmutable('now', new DateTimeZone('Europe/Paris')))->format('d/m/Y H:i:s'),
            'php' => PHP_VERSION,
            'sqlite' => (string) $db->query('SELECT sqlite_version()')->fetchColumn(),
        ];
    }
    private static function photoConsistency(PDO $db, string $root): array
    {
        $expected = [];
        $missing = 0;
        try {
            foreach ($db->query('SELECT animal_id,filename FROM animal_photos')->fetchAll(PDO::FETCH_ASSOC) as $row) {
                $path =
                    $root .
                    '/public/media/animals/' .
                    (int) $row['animal_id'] .
                    '/' .
                    basename((string) $row['filename']);
                $expected[$path] = true;
                if (!is_file($path)) {
                    $missing++;
                }
            }
        } catch (Throwable) {
            return [0, 0];
        }
        $orphans = 0;
        foreach (glob($root . '/public/media/animals/*/*') ?: [] as $file) {
            if (is_file($file) && !isset($expected[$file]) && !str_starts_with(basename($file), '.')) {
                $orphans++;
            }
        }
        return [$missing, $orphans];
    }
    private static function check(string $status, string $group, string $title, string $detail): array
    {
        return compact('status', 'group', 'title', 'detail');
    }
    private static function recentLogLines(string $path, int $days): int
    {
        if (!is_file($path) || filesize($path) === 0) {
            return 0;
        }
        $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
        $dates = [];
        for ($i = 0; $i < $days; $i++) {
            $dates[] = (new DateTimeImmutable("-$i days"))->format('d-M-Y');
        }
        $count = 0;
        foreach (array_slice($lines, -1000) as $line) {
            foreach ($dates as $date) {
                if (str_contains($line, $date)) {
                    $count++;
                    break;
                }
            }
        }
        return $count;
    }
    private static function bytes(int $bytes): string
    {
        foreach (['o', 'Ko', 'Mo', 'Go', 'To'] as $unit) {
            if ($bytes < 1024 || $unit === 'To') {
                return number_format($bytes, $unit === 'o' ? 0 : 1, ',', ' ') . ' ' . $unit;
            }
            $bytes = (int) round($bytes / 1024);
        }
        return '';
    }
}
