<?php

namespace App\Services;

use App\Models\Backup;
use App\Models\BackupDestination;
use App\Models\BackupActivityLog;
use App\Models\User;
use App\Models\Setting;
use App\Notifications\BackupCreated;
use App\Notifications\BackupRestored;
use App\Notifications\BackupFailed;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use ZipArchive;
use Carbon\Carbon;

class BackupService
{
    protected $backupPath = 'backups';

    /**
     * Creează un backup complet sau incremental
     */
    public function createBackup(array $options = []): Backup
    {
        $type = $options['type'] ?? 'full';
        $includeDatabase = $options['include_database'] ?? true;
        $includeFiles = $options['include_files'] ?? true;
        $createdBy = $options['created_by'] ?? auth()->id();
        $encrypt = $options['encrypt'] ?? false;

        // Creează înregistrarea în DB
        $backup = Backup::create([
            'type' => $type,
            'filename' => $this->generateFilename($type),
            'file_path' => '',
            'includes_database' => $includeDatabase,
            'includes_files' => $includeFiles,
            'status' => 'in_progress',
            'started_at' => now(),
            'created_by' => $createdBy,
            'is_encrypted' => $encrypt,
        ]);

        try {
            $startTime = microtime(true);

            // Creează directorul de backup dacă nu există
            $backupDir = storage_path("app/{$this->backupPath}");
            if (!file_exists($backupDir)) {
                mkdir($backupDir, 0755, true);
                Log::info("Backup directory created", ['path' => $backupDir]);
            }

            $zipPath = storage_path("app/{$this->backupPath}/{$backup->filename}");
            $backup->file_path = "{$this->backupPath}/{$backup->filename}";

            Log::info("Creating backup ZIP", [
                'path' => $zipPath,
                'dir_exists' => file_exists($backupDir),
                'dir_writable' => is_writable($backupDir)
            ]);

            $zip = new ZipArchive();
            $zipResult = $zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE);

            if ($zipResult !== true) {
                $errorMsg = 'Nu s-a putut crea fișierul ZIP';
                switch ($zipResult) {
                    case ZipArchive::ER_EXISTS:
                        $errorMsg .= ' - Fișierul există deja';
                        break;
                    case ZipArchive::ER_INCONS:
                        $errorMsg .= ' - Arhivă inconsistentă';
                        break;
                    case ZipArchive::ER_MEMORY:
                        $errorMsg .= ' - Eroare de memorie';
                        break;
                    case ZipArchive::ER_NOENT:
                        $errorMsg .= ' - Fișierul nu există';
                        break;
                    case ZipArchive::ER_NOZIP:
                        $errorMsg .= ' - Nu este un fișier ZIP';
                        break;
                    case ZipArchive::ER_OPEN:
                        $errorMsg .= ' - Nu se poate deschide fișierul';
                        break;
                    case ZipArchive::ER_READ:
                        $errorMsg .= ' - Eroare de citire';
                        break;
                    case ZipArchive::ER_SEEK:
                        $errorMsg .= ' - Eroare de seek';
                        break;
                }

                Log::error("ZIP creation failed", [
                    'path' => $zipPath,
                    'result' => $zipResult,
                    'error' => $errorMsg
                ]);

                throw new \Exception($errorMsg);
            }

            $metadata = [
                'type' => $type,
                'created_at' => now()->toDateTimeString(),
                'database' => [],
                'files' => [],
            ];

            // Backup bază de date
            if ($includeDatabase) {
                $dbData = $this->backupDatabase($zip, $type, $backup);
                $metadata['database'] = $dbData;
                $backup->tables_count = $dbData['tables'] ?? 0;
                $backup->records_count = $dbData['records'] ?? 0;
            }

            // Backup fișiere
            if ($includeFiles) {
                $filesData = $this->backupFiles($zip, $type, $backup);
                $metadata['files'] = $filesData;
                $backup->files_count = $filesData['count'] ?? 0;
            }

            // Adaugă metadata
            $zip->addFromString('metadata.json', json_encode($metadata, JSON_PRETTY_PRINT));

            $zip->close();

            // Actualizează backup
            $backup->file_size = filesize($zipPath);
            $backup->status = 'completed';
            $backup->completed_at = now();
            $backup->duration_seconds = (int)(microtime(true) - $startTime);
            $backup->save();

            // Upload către destinații
            $this->uploadToDestinations($backup);

            Log::info("Backup creat cu succes", [
                'id' => $backup->id,
                'type' => $type,
                'size' => $backup->file_size_human,
            ]);

            // Log activitate
            BackupActivityLog::logActivity(
                backupId: $backup->id,
                action: 'created',
                userId: $createdBy,
                userName: auth()->check() ? auth()->user()->name : 'System',
                ipAddress: request()->ip(),
                details: [
                    'type' => $type,
                    'size' => $backup->file_size_human,
                    'includes_database' => $includeDatabase,
                    'includes_files' => $includeFiles,
                ]
            );

            // Trimite notificare către admini
            $this->notifyAdmins(new BackupCreated($backup, request()->ip()));

            return $backup;

        } catch (\Exception $e) {
            $backup->status = 'failed';
            $backup->error_message = $e->getMessage();
            $backup->completed_at = now();
            $backup->save();

            Log::error("Backup eșuat", [
                'id' => $backup->id,
                'error' => $e->getMessage(),
            ]);

            // Log activitate eșuată
            BackupActivityLog::logActivity(
                backupId: $backup->id,
                action: 'failed',
                userId: $createdBy,
                userName: auth()->check() ? auth()->user()->name : 'System',
                ipAddress: request()->ip(),
                details: [
                    'error' => $e->getMessage(),
                ]
            );

            // Trimite notificare către admini
            $this->notifyAdmins(new BackupFailed($backup, $e->getMessage(), request()->ip()));

            throw $e;
        }
    }

    /**
     * Backup bază de date
     */
    protected function backupDatabase(ZipArchive $zip, string $type, Backup $backup): array
    {
        $tables = DB::select('SHOW TABLES');
        $dbName = DB::getDatabaseName();
        $tableKey = "Tables_in_{$dbName}";

        $schema = "-- Database Schema\n";
        $data = "-- Database Data\n";
        $totalRecords = 0;

        foreach ($tables as $table) {
            $tableName = $table->$tableKey;

            // Schema
            $createTable = DB::select("SHOW CREATE TABLE `{$tableName}`");
            $schema .= "\n-- Table: {$tableName}\n";
            $schema .= $createTable[0]->{'Create Table'} . ";\n\n";

            // date
            $records = DB::table($tableName)->get();
            $recordCount = count($records);
            $totalRecords += $recordCount;

            if ($recordCount > 0) {
                $data .= "\n-- Data for table: {$tableName}\n";

                foreach ($records as $record) {
                    $values = [];
                    foreach ((array)$record as $value) {
                        if (is_null($value)) {
                            $values[] = 'NULL';
                        } else {
                            $values[] = "'" . addslashes($value) . "'";
                        }
                    }
                    $data .= "INSERT INTO `{$tableName}` VALUES (" . implode(', ', $values) . ");\n";
                }
            }
        }

        $zip->addFromString('database/schema.sql', $schema);
        $zip->addFromString('database/data.sql', $data);

        return [
            'tables' => count($tables),
            'records' => $totalRecords,
            'size_mb' => round((strlen($schema) + strlen($data)) / 1024 / 1024, 2),
        ];
    }

    /**
     * Backup fișiere
     */
    protected function backupFiles(ZipArchive $zip, string $type, Backup $backup): array
    {
        $filesToBackup = [
            'storage' => storage_path('app/public'),
            'uploads' => public_path('uploads'),
        ];

        $totalFiles = 0;
        $totalSize = 0;

        foreach ($filesToBackup as $name => $path) {
            if (!file_exists($path)) {
                Log::info("Backup files: Path not found, skipping", ['path' => $path]);
                continue;
            }

            try {
                $files = new \RecursiveIteratorIterator(
                    new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS),
                    \RecursiveIteratorIterator::LEAVES_ONLY
                );

                foreach ($files as $file) {
                    if (!$file->isDir() && $file->isFile()) {
                        $filePath = $file->getRealPath();
                        $relativePath = 'files/' . $name . '/' . substr($filePath, strlen($path) + 1);

                        // Verifică dacă fișierul există și este accesibil
                        if (file_exists($filePath) && is_readable($filePath)) {
                            $zip->addFile($filePath, $relativePath);
                            $totalFiles++;
                            $totalSize += $file->getSize();
                        }
                    }
                }
            } catch (\Exception $e) {
                Log::warning("Backup files: Error processing path", [
                    'path' => $path,
                    'error' => $e->getMessage()
                ]);
            }
        }

        return [
            'count' => $totalFiles,
            'size_mb' => round($totalSize / 1024 / 1024, 2),
        ];
    }

    /**
     * Upload backup către destinații configurate
     */
    protected function uploadToDestinations(Backup $backup): void
    {
        $destinations = BackupDestination::active()->get();
        $uploadedTo = [];

        foreach ($destinations as $destination) {
            if (!$destination->canUpload()) {
                continue;
            }

            try {
                $this->uploadToDestination($backup, $destination);
                $uploadedTo[] = $destination->id;

                // Actualizează statistici destinație
                $destination->increment('total_backups');
                $destination->total_size_mb += round($backup->file_size / 1024 / 1024, 2);
                $destination->last_backup_at = now();
                $destination->save();

            } catch (\Exception $e) {
                Log::error("Upload eșuat către {$destination->name}", [
                    'backup_id' => $backup->id,
                    'destination_id' => $destination->id,
                    'error' => $e->getMessage(),
                ]);
            }
        }

        $backup->uploaded_to = $uploadedTo;
        $backup->save();
    }

    /**
     * Upload către o destinație specifică
     */
    protected function uploadToDestination(Backup $backup, BackupDestination $destination): void
    {
        $localPath = storage_path("app/{$backup->file_path}");

        switch ($destination->type) {
            case 'ftp':
                $this->uploadToFTP($localPath, $backup->filename, $destination);
                break;

            case 'sftp':
                $this->uploadToSFTP($localPath, $backup->filename, $destination);
                break;

            case 'ssh':
                $this->uploadViaSSH($localPath, $backup->filename, $destination);
                break;

            case 's3':
                $this->uploadToS3($localPath, $backup->filename, $destination);
                break;

            case 'google_drive':
                $this->uploadToGoogleDrive($localPath, $backup->filename, $destination);
                break;
        }
    }

    /**
     * Upload FTP
     */
    protected function uploadToFTP(string $localPath, string $filename, BackupDestination $destination): void
    {
        $config = $destination->config;

        $conn = ftp_connect($config['host'], $config['port'] ?? 21);
        if (!$conn) {
            throw new \Exception('Nu s-a putut conecta la serverul FTP');
        }

        if (!ftp_login($conn, $config['username'], $config['password'])) {
            throw new \Exception('Autentificare FTP eșuată');
        }

        ftp_pasv($conn, true);

        $remotePath = rtrim($config['path'] ?? '/', '/') . '/' . $filename;

        if (!ftp_put($conn, $remotePath, $localPath, FTP_BINARY)) {
            throw new \Exception('Upload FTP eșuat');
        }

        ftp_close($conn);
    }

    /**
     * Upload SFTP (placeholder - necesită phpseclib)
     */
    protected function uploadToSFTP(string $localPath, string $filename, BackupDestination $destination): void
    {
        // TODO: Implementeazăare SFTP cu phpseclib
        throw new \Exception('SFTP nu este încă implementat. Instalează phpseclib/phpseclib.');
    }

    /**
     * Upload SSH/Rsync (placeholder)
     */
    protected function uploadViaSSH(string $localPath, string $filename, BackupDestination $destination): void
    {
        // TODO: Implementeazăare SSH/Rsync
        throw new \Exception('SSH/Rsync nu este încă implementat.');
    }

    /**
     * Upload S3 (placeholder - necesită AWS SDK)
     */
    protected function uploadToS3(string $localPath, string $filename, BackupDestination $destination): void
    {
        // TODO: Implementeazăare S3 cu AWS SDK
        throw new \Exception('S3 nu este încă implementat. Instalează aws/aws-sdk-php.');
    }

    /**
     * Upload Google Drive (placeholder - necesită Google API)
     */
    protected function uploadToGoogleDrive(string $localPath, string $filename, BackupDestination $destination): void
    {
        // TODO: Implementeazăare Google Drive API
        throw new \Exception('Google Drive nu este încă implementat. Instalează google/apiclient.');
    }

    /**
     * Generează nume fișier pentru backup
     */
    protected function generateFilename(string $type): string
    {
        $date = now()->format('Y-m-d_H-i-s');
        return "backup_{$date}_{$type}.zip";
    }

    /**
     * Cleanup backup-uri vechi
     */
    public function cleanupOldBackups(int $days = 10): int
    {
        $cutoffDate = Carbon::now()->subDays($days);

        $oldBackups = Backup::where('created_at', '<', $cutoffDate)
            ->where('status', 'completed')
            ->get();

        $deleted = 0;

        foreach ($oldBackups as $backup) {
            try {
                $backup->delete();
                $deleted++;
            } catch (\Exception $e) {
                Log::error("Eroare la ștergerea backup-ului {$backup->id}: " . $e->getMessage());
            }
        }

        Log::info("Cleanup backup-uri: {$deleted} backup-uri șterse (mai vechi de {$days} zile)");

        return $deleted;
    }

    /**
     * Restore backup
     */
    public function restoreBackup(Backup $backup, int $restoredBy): bool
    {
        if (!$backup->exists()) {
            throw new \Exception('Fișierul de backup nu există');
        }

        $extractPath = storage_path('app/restore_temp');

        try {
            $zipPath = storage_path("app/{$backup->file_path}");

            // Creează director temporar
            if (!file_exists($extractPath)) {
                mkdir($extractPath, 0755, true);
            }

            // Extrage ZIP
            $zip = new ZipArchive();
            if ($zip->open($zipPath) !== true) {
                throw new \Exception('Nu s-a putut deschide fișierul de backup');
            }

            $zip->extractTo($extractPath);
            $zip->close();

            // Restore baza de date
            if ($backup->includes_database && file_exists($extractPath . '/database/data.sql')) {
                // Salvează backup-urile și destinațiile existente pentru a le păstra după restore
                $existingBackups = DB::table('backups')->get()->toArray();
                $existingDestinations = DB::table('backup_destinations')->get()->toArray();

                // Dezactivează constrângerile de integritate
                DB::statement('SET FOREIGN_KEY_CHECKS=0;');

                // Șterge datele existente din toate tabelele (exceptând migrations, sessions, cache)
                $tables = DB::select('SHOW TABLES');
                $databaseName = DB::getDatabaseName();
                $tableKey = "Tables_in_{$databaseName}";

                // Tabelele care NU trebuie șterse la restore
                $excludedTables = [
                    'migrations',
                    'sessions',
                    'password_reset_tokens',
                    'cache',
                    'cache_locks',
                    'backup_activity_logs',  // Păstrăm istoricul activităților
                ];

                foreach ($tables as $table) {
                    $tableName = $table->$tableKey;

                    if (!in_array($tableName, $excludedTables)) {
                        DB::statement("TRUNCATE TABLE `{$tableName}`");
                        Log::info("Tabel șters pentru restore", ['table' => $tableName]);
                    }
                }

                // Execută SQL pe bucăți pentru a evita probleme cu max_allowed_packet
                $sqlFile = $extractPath . '/database/data.sql';
                $handle = fopen($sqlFile, 'r');

                if ($handle) {
                    $query = '';
                    while (($line = fgets($handle)) !== false) {
                        // Ignoră comentariile și liniile goale
                        if (empty(trim($line)) || strpos($line, '--') === 0) {
                            continue;
                        }

                        $query .= $line;

                        // Execută query-ul când găsim ;
                        if (substr(trim($line), -1) === ';') {
                            try {
                                DB::unprepared($query);
                            } catch (\Exception $e) {
                                Log::warning("Eroare la executarea query restore", [
                                    'error' => $e->getMessage(),
                                    'query_preview' => substr($query, 0, 200)
                                ]);
                            }
                            $query = '';
                        }
                    }

                    fclose($handle);
                }

                // Reactivează constrângerile de integritate
                DB::statement('SET FOREIGN_KEY_CHECKS=1;');

                // Re-inserează backup-urile și destinațiile existente (pentru a le păstra)
                // Folosim upsert pentru a actualiza backup-urile care au fost re-importate din SQL
                foreach ($existingBackups as $existingBackup) {
                    $backupArray = (array)$existingBackup;

                    // Verifică dacă backup-ul există deja (importat din SQL)
                    $exists = DB::table('backups')->where('id', $backupArray['id'])->exists();

                    if ($exists) {
                        // Actualizează doar dacă backup-ul existent are date mai noi
                        DB::table('backups')
                            ->where('id', $backupArray['id'])
                            ->update([
                                'status' => $backupArray['status'],
                                'file_size' => $backupArray['file_size'],
                                'completed_at' => $backupArray['completed_at'],
                                'duration_seconds' => $backupArray['duration_seconds'],
                                'tables_count' => $backupArray['tables_count'],
                                'records_count' => $backupArray['records_count'],
                                'files_count' => $backupArray['files_count'],
                                'updated_at' => $backupArray['updated_at'],
                            ]);
                    } else {
                        // Inserează backup-ul nou (creat după backup-ul original)
                        DB::table('backups')->insert($backupArray);
                    }
                }

                foreach ($existingDestinations as $existingDestination) {
                    DB::table('backup_destinations')->insertOrIgnore((array)$existingDestination);
                }

                Log::info("Re-inserate backup-uri existente", ['count' => count($existingBackups)]);
            }

            // Restore fișiers
            if ($backup->includes_files && file_exists($extractPath . '/files')) {
                // TODO: Implementare restore fișiere
            }

            // Cleanup
            $this->deleteDirectory($extractPath);

            // Actualizează backup - folosim update direct pentru a evita probleme cu modelul
            // (backup-ul poate fi re-importat din SQL și trebuie actualizat)
            DB::table('backups')
                ->where('id', $backup->id)
                ->update([
                    'restored_at' => now(),
                    'restored_by' => $restoredBy,
                    'updated_at' => now(),
                ]);

            Log::info("Backup restaurat cu succes", ['backup_id' => $backup->id]);

            // Log activitate restore
            BackupActivityLog::logActivity(
                backupId: $backup->id,
                action: 'restored',
                userId: $restoredBy,
                userName: auth()->check() ? auth()->user()->name : null,
                ipAddress: request()->ip(),
                details: [
                    'backup_filename' => $backup->filename,
                ]
            );

            // Trimite notificare către admini
            $this->notifyAdmins(new BackupRestored($backup, request()->ip()));

            return true;

        } catch (\Exception $e) {
            // Cleanup în caz de eroare
            if (file_exists($extractPath)) {
                $this->deleteDirectory($extractPath);
            }

            Log::error("Eroare la restore backup", [
                'backup_id' => $backup->id,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    /**
     * Șterge un director recursiv
     */
    protected function deleteDirectory(string $dir): bool
    {
        if (!file_exists($dir)) {
            return true;
        }

        if (!is_dir($dir)) {
            return unlink($dir);
        }

        foreach (scandir($dir) as $item) {
            if ($item == '.' || $item == '..') {
                continue;
            }

            if (!$this->deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) {
                return false;
            }
        }

        return rmdir($dir);
    }

    /**
     * Trimite notificare către adresele configurate sau către admini
     */
    protected function notifyAdmins($notification): void
    {
        // Obține adresele de email configurate
        $emailsString = Setting::get('backup_notification_emails', '');

        if (!empty($emailsString)) {
            // Trimite către adresele configurate
            $emails = array_map('trim', explode(',', $emailsString));
            $emails = array_filter($emails); // Elimină valorile goale

            foreach ($emails as $email) {
                if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
                    try {
                        Notification::route('mail', $email)->notify($notification);
                    } catch (\Exception $e) {
                        Log::error("Eroare la trimiterea notificării către email", [
                            'email' => $email,
                            'error' => $e->getMessage(),
                        ]);
                    }
                }
            }
        } else {
            // Fallback: trimite către toți adminii
            $admins = User::where('role', 'admin')->get();

            foreach ($admins as $admin) {
                try {
                    $admin->notify($notification);
                } catch (\Exception $e) {
                    Log::error("Eroare la trimiterea notificării către admin", [
                        'admin_id' => $admin->id,
                        'error' => $e->getMessage(),
                    ]);
                }
            }
        }
    }

    /**
     * Creează backup complet (zone + bază de date)
     */
    public function createFullBackup(array $options = []): Backup
    {
        return $this->createBackup(array_merge($options, [
            'type' => 'full',
            'include_database' => true,
            'include_files' => true,
        ]));
    }

    /**
     * Creează backup doar zone DNS
     */
    public function createZonesBackup(array $options = []): Backup
    {
        return $this->createBackup(array_merge($options, [
            'type' => 'zones',
            'include_database' => false,
            'include_files' => true,
        ]));
    }

    /**
     * Creează backup doar bază de date
     */
    public function createDatabaseBackup(array $options = []): Backup
    {
        return $this->createBackup(array_merge($options, [
            'type' => 'database',
            'include_database' => true,
            'include_files' => false,
        ]));
    }
}
