<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Database;
use App\Core\Version;
use PDO;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use FilesystemIterator;

final class SystemMaintenanceService
{
    private const PROTECTED_NAMES = ['.gitkeep', '.htaccess', 'index.php', 'web.config'];

    public function cleanupAnalysis(): array
    {
        $groups = [];
        foreach ($this->cleanupTargets() as $key => $target) {
            $stats = $this->scanDirectory($target['path'], $target['minimum_age']);
            $groups[] = ['key' => $key, 'label' => $target['label'], 'path' => $target['relative']] + $stats;
        }
        return $groups;
    }

    public function cleanup(array $selected): array
    {
        $targets = $this->cleanupTargets();
        $deleted = 0; $freed = 0; $errors = [];
        foreach ($selected as $key) {
            if (!isset($targets[$key])) { continue; }
            $target = $targets[$key];
            foreach ($this->eligibleFiles($target['path'], $target['minimum_age']) as $file) {
                $size = (int) @filesize($file);
                if (@unlink($file)) { $deleted++; $freed += $size; }
                else { $errors[] = $file; }
            }
            $this->removeEmptyDirectories($target['path']);
        }
        $this->writeAuditLog(sprintf('CLEANUP deleted=%d freed=%d errors=%d', $deleted, $freed, count($errors)));
        return ['deleted' => $deleted, 'freed' => $freed, 'errors' => $errors];
    }

    public function listLogs(): array
    {
        $logs = [];
        foreach ($this->logDirectories() as $directory) {
            if (!is_dir($directory)) { continue; }
            foreach (new \DirectoryIterator($directory) as $file) {
                if (!$file->isFile()) { continue; }
                $extension = strtolower($file->getExtension());
                if (!in_array($extension, ['log','txt'], true)) { continue; }
                $path = $file->getPathname();
                $id = rtrim(strtr(base64_encode($path), '+/', '-_'), '=');
                $logs[] = [
                    'id' => $id,
                    'name' => $file->getFilename(),
                    'size' => $file->getSize(),
                    'modified_at' => date('Y-m-d H:i:s', $file->getMTime()),
                ];
            }
        }
        usort($logs, fn(array $a,array $b): int => strcmp($b['modified_at'], $a['modified_at']));
        return $logs;
    }

    public function readLog(string $id, string $search = '', string $level = ''): array
    {
        $path = $this->resolveLog($id);
        $lines = $this->tail($path, 500);
        if ($search !== '') {
            $lines = array_values(array_filter($lines, fn(string $line): bool => stripos($line, $search) !== false));
        }
        if ($level !== '') {
            $lines = array_values(array_filter($lines, fn(string $line): bool => stripos($line, $level) !== false));
        }
        return ['name' => basename($path), 'path' => $path, 'lines' => $lines, 'size' => filesize($path) ?: 0];
    }

    public function clearLog(string $id): void
    {
        $path = $this->resolveLog($id);
        if (file_put_contents($path, '') === false) {
            throw new \RuntimeException('Impossible de vider le journal.');
        }
        $this->writeAuditLog('LOG_CLEAR file=' . basename($path));
    }

    public function logPath(string $id): string { return $this->resolveLog($id); }

    public function systemInformation(): array
    {
        $pdo = Database::connection();
        $database = (string)$pdo->query('SELECT DATABASE()')->fetchColumn();
        $schema = $pdo->prepare('SELECT DEFAULT_CHARACTER_SET_NAME charset, DEFAULT_COLLATION_NAME collation FROM information_schema.SCHEMATA WHERE SCHEMA_NAME=:db');
        $schema->execute(['db'=>$database]);
        $schemaData = $schema->fetch(PDO::FETCH_ASSOC) ?: [];
        $size = $pdo->prepare('SELECT COALESCE(SUM(DATA_LENGTH+INDEX_LENGTH),0) FROM information_schema.TABLES WHERE TABLE_SCHEMA=:db');
        $size->execute(['db'=>$database]);
        $tables = $pdo->prepare('SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=:db AND TABLE_TYPE="BASE TABLE"');
        $tables->execute(['db'=>$database]);
        $mysqlVersion = (string)$pdo->query('SELECT VERSION()')->fetchColumn();
        $extensions = [];
        foreach (['pdo_mysql','mbstring','openssl','json','zip','gd','curl','fileinfo','intl','xml','opcache'] as $extension) {
            $extensions[$extension] = extension_loaded($extension);
        }
        $permissions = [];
        foreach (['storage','storage/logs','storage/cache','storage/tmp','storage/backups','storage/documents'] as $relative) {
            $path = BASE_PATH . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $relative);
            $permissions[$relative] = ['exists'=>is_dir($path),'writable'=>is_dir($path) && is_writable($path)];
        }
        return [
            'application' => Version::fullLabel(),
            'version' => Version::number(), 'edition'=>Version::edition(), 'build'=>Version::build(), 'schema'=>Version::schema(),
            'php_version'=>PHP_VERSION, 'php_sapi'=>PHP_SAPI, 'architecture'=>PHP_INT_SIZE === 8 ? '64 bits' : '32 bits',
            'memory_limit'=>(string)ini_get('memory_limit'), 'upload_max_filesize'=>(string)ini_get('upload_max_filesize'),
            'post_max_size'=>(string)ini_get('post_max_size'), 'max_execution_time'=>(string)ini_get('max_execution_time').' s',
            'timezone'=>date_default_timezone_get(), 'extensions'=>$extensions,
            'mysql_version'=>$mysqlVersion, 'database'=>$database, 'charset'=>$schemaData['charset'] ?? 'Indisponible',
            'collation'=>$schemaData['collation'] ?? 'Indisponible', 'table_count'=>(int)$tables->fetchColumn(), 'database_size'=>(int)$size->fetchColumn(),
            'server'=>$_SERVER['SERVER_SOFTWARE'] ?? 'Indisponible', 'os'=>PHP_OS_FAMILY.' — '.PHP_OS,
            'project_path'=>BASE_PATH, 'app_url'=>$_ENV['APP_URL'] ?? '',
            'disk_free'=>disk_free_space(BASE_PATH) ?: 0, 'disk_total'=>disk_total_space(BASE_PATH) ?: 0,
            'permissions'=>$permissions,
        ];
    }

    private function cleanupTargets(): array
    {
        $definitions = [
            'cache'=>['Cache applicatif','storage/cache',0],
            'tmp'=>['Fichiers temporaires','storage/tmp',0],
            'temp'=>['Dossier temporaire','storage/temp',0],
            'exports'=>['Exports temporaires de plus de 24 h','storage/exports',86400],
            'imports'=>['Imports temporaires de plus de 24 h','storage/imports',86400],
        ];
        $targets=[];
        foreach ($definitions as $key=>[$label,$relative,$age]) {
            $targets[$key]=['label'=>$label,'relative'=>$relative,'path'=>BASE_PATH.DIRECTORY_SEPARATOR.str_replace('/',DIRECTORY_SEPARATOR,$relative),'minimum_age'=>$age];
        }
        return $targets;
    }

    private function scanDirectory(string $path, int $minimumAge): array
    {
        $files=$this->eligibleFiles($path,$minimumAge); $size=0; $oldest=null;
        foreach($files as $file){$size+=(int)@filesize($file);$mtime=@filemtime($file);if($mtime && ($oldest===null||$mtime<$oldest)){$oldest=$mtime;}}
        return ['file_count'=>count($files),'size'=>$size,'oldest_at'=>$oldest?date('Y-m-d H:i:s',$oldest):null,'exists'=>is_dir($path),'writable'=>is_dir($path)&&is_writable($path)];
    }

    private function eligibleFiles(string $path, int $minimumAge): array
    {
        if(!is_dir($path)){return [];}
        $result=[];$now=time();
        $iterator=new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path,FilesystemIterator::SKIP_DOTS));
        foreach($iterator as $file){if(!$file->isFile()||in_array($file->getFilename(),self::PROTECTED_NAMES,true)){continue;} if($minimumAge>0 && ($now-$file->getMTime())<$minimumAge){continue;} $result[]=$file->getPathname();}
        return $result;
    }

    private function removeEmptyDirectories(string $path): void
    {
        if(!is_dir($path)){return;}
        $iterator=new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path,FilesystemIterator::SKIP_DOTS),RecursiveIteratorIterator::CHILD_FIRST);
        foreach($iterator as $item){if($item->isDir() && iterator_count(new FilesystemIterator($item->getPathname()))===0){@rmdir($item->getPathname());}}
    }

    private function logDirectories(): array { return [BASE_PATH.'/storage/logs', BASE_PATH.'/logs', BASE_PATH.'/storage/install']; }

    private function resolveLog(string $id): string
    {
        $decoded=base64_decode(strtr($id,'-_','+/'),true);
        if(!is_string($decoded)||!is_file($decoded)){throw new \InvalidArgumentException('Journal introuvable.');}
        $real=realpath($decoded);
        if($real===false){throw new \InvalidArgumentException('Journal introuvable.');}
        foreach($this->logDirectories() as $directory){$root=realpath($directory);if($root!==false && str_starts_with($real,$root.DIRECTORY_SEPARATOR)){return $real;}}
        throw new \RuntimeException('Accès au journal refusé.');
    }

    private function tail(string $path, int $maxLines): array
    {
        $file=new \SplFileObject($path,'r');$file->seek(PHP_INT_MAX);$last=$file->key();$start=max(0,$last-$maxLines);$lines=[];
        $file->seek($start);while(!$file->eof()){$line=rtrim((string)$file->fgets(),"\r\n");if($line!==''){$lines[]=$line;}}
        return $lines;
    }

    private function writeAuditLog(string $message): void
    {
        $dir=BASE_PATH.'/storage/logs';if(!is_dir($dir)){@mkdir($dir,0775,true);} @file_put_contents($dir.'/maintenance.log','['.date('Y-m-d H:i:s').'] '.$message.PHP_EOL,FILE_APPEND|LOCK_EX);
    }
}
