<?php

declare(strict_types=1);

namespace App\Services;

require_once __DIR__ . '/../../vendor/autoload.php';

use PDO;
use Database;
use Session;
use App\Services\LogService;

class FileService {
    private const BASE_UPLOADS_DIR = '/var/www/geosector/api/uploads';

    // Permissions pour écriture web
    private const FILE_PERMS = 0666;
    private const DIR_PERMS = 0775;
    private const OWNER_GROUP = 'nginx:nobody';

    private \PDO $db;

    public function __construct() {
        $this->db = Database::getInstance();
    }

    /**
     * Crée un dossier dans l'arborescence uploads
     * 
     * @param int $entiteId ID de l'entité
     * @param string $path Chemin relatif à partir de BASE_UPLOADS_DIR (ex: '/5/operations/2644/export')
     * @return string Le chemin complet du dossier créé
     */
    public function createDirectory(int $entiteId, string $path): string {
        // Construire le chemin complet
        $fullPath = self::BASE_UPLOADS_DIR . $path;

        LogService::log('Création de dossier', [
            'level' => 'info',
            'entiteId' => $entiteId,
            'path' => $path,
            'fullPath' => $fullPath,
        ]);

        // Créer le dossier avec tous les dossiers parents si nécessaire
        if (!is_dir($fullPath)) {
            if (!mkdir($fullPath, self::DIR_PERMS, true)) {
                LogService::log('Erreur création dossier', [
                    'level' => 'error',
                    'fullPath' => $fullPath,
                ]);
                throw new Exception("Impossible de créer le dossier: {$fullPath}");
            }

            // Appliquer les permissions et propriétaire
            $this->setDirectoryPermissions($fullPath);

            LogService::log('Dossier créé avec succès', [
                'level' => 'info',
                'fullPath' => $fullPath,
                'permissions' => decoct(self::DIR_PERMS),
                'owner' => self::OWNER_GROUP,
            ]);
        }

        return $fullPath;
    }

    /**
     * Applique les permissions et propriétaire sur un dossier
     */
    private function setDirectoryPermissions(string $path): void {
        // Appliquer les permissions
        chmod($path, self::DIR_PERMS);

        // Changer le propriétaire et le groupe séparément pour plus de fiabilité
        $chownUserCommand = "chown nginx " . escapeshellarg($path);
        exec($chownUserCommand, $output, $returnCode);

        if ($returnCode !== 0) {
            LogService::log('Avertissement: Impossible de changer le propriétaire', [
                'level' => 'warning',
                'path' => $path,
                'command' => $chownUserCommand,
                'return_code' => $returnCode,
            ]);
        }

        $chgrpCommand = "chgrp nobody " . escapeshellarg($path);
        exec($chgrpCommand, $output, $returnCode);

        if ($returnCode !== 0) {
            LogService::log('Avertissement: Impossible de changer le groupe', [
                'level' => 'warning',
                'path' => $path,
                'command' => $chgrpCommand,
                'return_code' => $returnCode,
            ]);
        }
    }

    /**
     * Applique les permissions sur un fichier
     */
    public function setFilePermissions(string $filepath): void {
        // Appliquer les permissions fichier
        chmod($filepath, self::FILE_PERMS);

        // Changer le propriétaire et le groupe séparément pour plus de fiabilité
        $chownUserCommand = "chown nginx " . escapeshellarg($filepath);
        exec($chownUserCommand, $output, $returnCode);

        if ($returnCode !== 0) {
            LogService::log('Avertissement: Impossible de changer le propriétaire du fichier', [
                'level' => 'warning',
                'filepath' => $filepath,
                'command' => $chownUserCommand,
                'return_code' => $returnCode,
            ]);
        }

        $chgrpCommand = "chgrp nobody " . escapeshellarg($filepath);
        exec($chgrpCommand, $output, $returnCode);

        if ($returnCode !== 0) {
            LogService::log('Avertissement: Impossible de changer le groupe du fichier', [
                'level' => 'warning',
                'filepath' => $filepath,
                'command' => $chgrpCommand,
                'return_code' => $returnCode,
            ]);
        }
    }

    /**
     * Supprime un fichier
     * 
     * @param string $filePath Chemin complet vers le fichier ou chemin relatif depuis BASE_UPLOADS_DIR
     * @param string $fileName Nom du fichier (pour les logs)
     * @return bool True si suppression réussie, false sinon
     */
    public function deleteFile(string $filePath, string $fileName): bool {
        // Si le chemin ne commence pas par /, on considère qu'il est relatif à BASE_UPLOADS_DIR
        if (!str_starts_with($filePath, '/')) {
            $fullPath = self::BASE_UPLOADS_DIR . '/' . $filePath;
        } else {
            $fullPath = $filePath;
        }

        LogService::log('Tentative de suppression de fichier', [
            'level' => 'info',
            'fileName' => $fileName,
            'filePath' => $filePath,
            'fullPath' => $fullPath,
        ]);

        // Vérifier que le fichier existe
        if (!file_exists($fullPath)) {
            LogService::log('Fichier non trouvé pour suppression', [
                'level' => 'warning',
                'fileName' => $fileName,
                'fullPath' => $fullPath,
            ]);
            return false;
        }

        // Vérifier que c'est bien un fichier (pas un dossier)
        if (!is_file($fullPath)) {
            LogService::log('Le chemin ne pointe pas vers un fichier', [
                'level' => 'error',
                'fileName' => $fileName,
                'fullPath' => $fullPath,
            ]);
            return false;
        }

        // Supprimer d'abord les enregistrements dans la table medias
        $deletedMediaRecords = $this->deleteMediaRecordsByFile($fullPath, $fileName);

        // Tenter la suppression du fichier physique
        if (unlink($fullPath)) {
            LogService::log('Fichier supprimé avec succès', [
                'level' => 'info',
                'fileName' => $fileName,
                'fullPath' => $fullPath,
                'deletedMediaRecords' => $deletedMediaRecords,
            ]);
            return true;
        } else {
            LogService::log('Erreur lors de la suppression du fichier', [
                'level' => 'error',
                'fileName' => $fileName,
                'fullPath' => $fullPath,
            ]);
            return false;
        }
    }

    /**
     * Supprime un dossier et tout son contenu
     * 
     * @param string $filePath Chemin complet vers le dossier ou chemin relatif depuis BASE_UPLOADS_DIR
     * @return bool True si suppression réussie, false sinon
     */
    public function deleteDir(string $filePath): bool {
        // Si le chemin ne commence pas par /, on considère qu'il est relatif à BASE_UPLOADS_DIR
        if (!str_starts_with($filePath, '/')) {
            $fullPath = self::BASE_UPLOADS_DIR . '/' . $filePath;
        } else {
            $fullPath = $filePath;
        }

        LogService::log('Tentative de suppression de dossier', [
            'level' => 'info',
            'filePath' => $filePath,
            'fullPath' => $fullPath,
        ]);

        // Vérifier que le dossier existe
        if (!file_exists($fullPath)) {
            LogService::log('Dossier non trouvé pour suppression', [
                'level' => 'warning',
                'fullPath' => $fullPath,
            ]);
            return false;
        }

        // Vérifier que c'est bien un dossier
        if (!is_dir($fullPath)) {
            LogService::log('Le chemin ne pointe pas vers un dossier', [
                'level' => 'error',
                'fullPath' => $fullPath,
            ]);
            return false;
        }

        // Supprimer d'abord les enregistrements dans la table medias pour ce dossier
        $deletedMediaRecords = $this->deleteMediaRecordsByDirectory($fullPath);

        // Supprimer récursivement le contenu du dossier
        if ($this->deleteDirectoryRecursive($fullPath)) {
            LogService::log('Dossier supprimé avec succès', [
                'level' => 'info',
                'fullPath' => $fullPath,
                'deletedMediaRecords' => $deletedMediaRecords,
            ]);
            return true;
        } else {
            LogService::log('Erreur lors de la suppression du dossier', [
                'level' => 'error',
                'fullPath' => $fullPath,
            ]);
            return false;
        }
    }

    /**
     * Supprime les enregistrements medias correspondant à un fichier spécifique
     * 
     * @param string $fullPath Chemin complet du fichier
     * @param string $fileName Nom du fichier (pour les logs)
     * @return int Nombre d'enregistrements supprimés
     */
    private function deleteMediaRecordsByFile(string $fullPath, string $fileName): int {
        // Convertir le chemin complet en chemin relatif pour la recherche en base
        $relativePath = str_replace(getcwd() . '/', '', $fullPath);

        // Rechercher les enregistrements correspondants
        $stmt = $this->db->prepare('
            SELECT id, fichier, file_path FROM medias 
            WHERE file_path = ? OR fichier = ?
        ');
        $stmt->execute([$relativePath, $fileName]);
        $mediaRecords = $stmt->fetchAll(PDO::FETCH_ASSOC);

        if (empty($mediaRecords)) {
            LogService::log('Aucun enregistrement media trouvé pour le fichier', [
                'level' => 'info',
                'fileName' => $fileName,
                'relativePath' => $relativePath,
            ]);
            return 0;
        }

        // Supprimer les enregistrements
        $stmt = $this->db->prepare('DELETE FROM medias WHERE file_path = ? OR fichier = ?');
        $stmt->execute([$relativePath, $fileName]);
        $deletedCount = $stmt->rowCount();

        LogService::log('Enregistrements medias supprimés pour fichier', [
            'level' => 'info',
            'fileName' => $fileName,
            'deletedCount' => $deletedCount,
            'mediaRecords' => array_column($mediaRecords, 'id'),
        ]);

        return $deletedCount;
    }

    /**
     * Supprime les enregistrements medias correspondant à un dossier et ses sous-dossiers
     * 
     * @param string $fullPath Chemin complet du dossier
     * @return int Nombre d'enregistrements supprimés
     */
    private function deleteMediaRecordsByDirectory(string $fullPath): int {
        // Convertir le chemin complet en chemin relatif pour la recherche en base
        $relativePath = str_replace(getcwd() . '/', '', $fullPath);

        // Rechercher tous les enregistrements dont le chemin commence par le dossier
        $stmt = $this->db->prepare('
            SELECT id, fichier, file_path FROM medias 
            WHERE file_path LIKE ?
        ');
        $stmt->execute([$relativePath . '%']);
        $mediaRecords = $stmt->fetchAll(PDO::FETCH_ASSOC);

        if (empty($mediaRecords)) {
            LogService::log('Aucun enregistrement media trouvé pour le dossier', [
                'level' => 'info',
                'relativePath' => $relativePath,
            ]);
            return 0;
        }

        // Supprimer les enregistrements
        $stmt = $this->db->prepare('DELETE FROM medias WHERE file_path LIKE ?');
        $stmt->execute([$relativePath . '%']);
        $deletedCount = $stmt->rowCount();

        LogService::log('Enregistrements medias supprimés pour dossier', [
            'level' => 'info',
            'relativePath' => $relativePath,
            'deletedCount' => $deletedCount,
            'mediaRecords' => array_column($mediaRecords, 'id'),
        ]);

        return $deletedCount;
    }

    /**
     * Supprime récursivement un dossier et tout son contenu
     * 
     * @param string $dir Chemin complet vers le dossier
     * @return bool True si suppression réussie, false sinon
     */
    private function deleteDirectoryRecursive(string $dir): bool {
        if (!is_dir($dir)) {
            return false;
        }

        $files = array_diff(scandir($dir), ['.', '..']);
        $deletedFiles = 0;
        $totalFiles = count($files);

        foreach ($files as $file) {
            $filePath = $dir . DIRECTORY_SEPARATOR . $file;

            if (is_dir($filePath)) {
                // Récursion pour les sous-dossiers
                if ($this->deleteDirectoryRecursive($filePath)) {
                    $deletedFiles++;
                    LogService::log('Sous-dossier supprimé', [
                        'level' => 'debug',
                        'subDir' => $filePath,
                    ]);
                } else {
                    LogService::log('Erreur suppression sous-dossier', [
                        'level' => 'error',
                        'subDir' => $filePath,
                    ]);
                }
            } else {
                // Supprimer le fichier
                if (unlink($filePath)) {
                    $deletedFiles++;
                    LogService::log('Fichier supprimé du dossier', [
                        'level' => 'debug',
                        'file' => $filePath,
                    ]);
                } else {
                    LogService::log('Erreur suppression fichier du dossier', [
                        'level' => 'error',
                        'file' => $filePath,
                    ]);
                }
            }
        }

        // Supprimer le dossier lui-même s'il est vide
        if ($deletedFiles === $totalFiles && rmdir($dir)) {
            return true;
        } else {
            LogService::log('Impossible de supprimer le dossier principal', [
                'level' => 'error',
                'dir' => $dir,
                'deletedFiles' => $deletedFiles,
                'totalFiles' => $totalFiles,
            ]);
            return false;
        }
    }

    /**
     * Enregistre le fichier dans la table medias
     */
    public function saveToMediasTable(int $entiteId, int $operationId, string $filename, string $filepath, string $fileType, string $description, string $fileCategory = 'export'): int {
        $stmt = $this->db->prepare('
            INSERT INTO medias (
                support, support_id, fichier, file_type, file_category, file_size, mime_type,
                original_name, fk_entite, fk_operation, file_path, description,
                created_at, fk_user_creat
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?)
        ');

        // Déterminer le type MIME selon l'extension
        $mimeTypes = [
            'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'json' => 'application/json',
            'enc' => 'application/octet-stream'
        ];
        $mimeType = $mimeTypes[$fileType] ?? 'application/octet-stream';

        $relativePath = str_replace(getcwd() . '/', '', $filepath);
        $userId = Session::getUserId() ?? 1; // Fallback si pas de session

        $stmt->execute([
            'operation',
            $operationId,
            $filename,
            $fileType,
            $fileCategory,
            filesize($filepath),
            $mimeType,
            $filename,
            $entiteId,
            $operationId,
            $relativePath,
            $description,
            $userId
        ]);

        return (int)$this->db->lastInsertId();
    }
}
