<?php

declare(strict_types=1);

namespace App\Http\Controllers\Admin;

use App\Domain\Backup\Exceptions\BackupException;
use App\Domain\Backup\Models\Backup;
use App\Domain\Backup\Models\BackupDestination;
use App\Domain\Backup\Models\BackupSchedule;
use App\Domain\Backup\Services\BackupDestinationManager;
use App\Domain\Backup\Services\BackupService;
use App\Http\Controllers\Controller;
use App\Jobs\RunBackupScheduleJob;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Inertia\Inertia;
use Inertia\Response;

/**
 * Admin Backup Manager — server-wide backup yönetimi.
 *
 * - Dashboard: özet + son yedek durumları
 * - Schedules: cron tabanlı yedek profilleri
 * - Destinations: local/S3/restic/SFTP hedefleri
 * - History: tüm geçmiş yedekler + restore
 */
class BackupController extends Controller
{
    public function dashboard(Request $request): Response
    {
        $totalBackups = Backup::count();
        $totalSize = Backup::sum('size_bytes');
        $last24h = Backup::where('created_at', '>=', now()->subDay())->count();
        $failed = Backup::where('status', 'failed')->where('created_at', '>=', now()->subWeek())->count();

        $recentBackups = Backup::with(['account:id,username,email'])
            ->latest()
            ->take(10)
            ->get()
            ->map(fn ($b) => [
                'id' => $b->id,
                'account_username' => $b->account?->username,
                'account_email' => $b->account?->email,
                'type' => $b->type,
                'size_bytes' => $b->size_bytes,
                'size_human' => $this->humanBytes($b->size_bytes),
                'status' => $b->status,
                'created_at' => $b->created_at->format('d.m.Y H:i'),
            ]);

        $schedules = BackupSchedule::with('destination:id,name,driver')
            ->whereNull('deleted_at')
            ->get()
            ->map(fn ($s) => [
                'id' => $s->id,
                'name' => $s->name,
                'is_active' => $s->is_active,
                'cron' => $s->cron,
                'human_cron' => $s->humanCron(),
                'scope' => $s->scope,
                'destination_name' => $s->destination?->name,
                'destination_driver' => $s->destination?->driver,
                'last_run_at' => $s->last_run_at?->format('d.m.Y H:i'),
                'last_run_status' => $s->last_run_status,
                'next_run_at' => $s->next_run_at?->format('d.m.Y H:i'),
            ]);

        $destinations = BackupDestination::whereNull('deleted_at')
            ->get()
            ->map(fn ($d) => [
                'id' => $d->id,
                'name' => $d->name,
                'driver' => $d->driver,
                'is_active' => $d->is_active,
                'is_default' => $d->is_default,
                'is_healthy' => $d->is_healthy,
                'total_bytes' => $d->total_bytes,
                'total_human' => $this->humanBytes($d->total_bytes),
                'backup_count' => $d->backup_count,
                'last_backup_at' => $d->last_backup_at?->format('d.m.Y H:i'),
            ]);

        return Inertia::render('Admin/Backup/Dashboard', [
            'stats' => [
                'total_backups' => $totalBackups,
                'total_size' => $totalSize,
                'total_size_human' => $this->humanBytes($totalSize),
                'last_24h' => $last24h,
                'failed_7d' => $failed,
            ],
            'recent_backups' => $recentBackups,
            'schedules' => $schedules,
            'destinations' => $destinations,
        ]);
    }

    // ─── Schedules ──────────────────────────────────────────────────────

    public function schedules(Request $request): Response
    {
        $schedules = BackupSchedule::with('destination')
            ->whereNull('deleted_at')
            ->latest()
            ->get();

        $destinations = BackupDestination::whereNull('deleted_at')
            ->where('is_active', true)
            ->get(['id', 'name', 'driver']);

        return Inertia::render('Admin/Backup/Schedules', [
            'schedules' => $schedules->map(fn ($s) => [
                'id' => $s->id,
                'name' => $s->name,
                'is_active' => $s->is_active,
                'cron' => $s->cron,
                'human_cron' => $s->humanCron(),
                'scope' => $s->scope,
                'include_accounts' => $s->include_accounts,
                'exclude_paths' => $s->exclude_paths,
                'destination' => $s->destination ? ['id' => $s->destination->id, 'name' => $s->destination->name, 'driver' => $s->destination->driver] : null,
                'compression' => $s->compression,
                'encrypt' => $s->encrypt,
                'keep_daily' => $s->keep_daily,
                'keep_weekly' => $s->keep_weekly,
                'keep_monthly' => $s->keep_monthly,
                'keep_yearly' => $s->keep_yearly,
                'last_run_at' => $s->last_run_at?->format('d.m.Y H:i'),
                'last_run_status' => $s->last_run_status,
                'next_run_at' => $s->next_run_at?->format('d.m.Y H:i'),
            ]),
            'destinations' => $destinations,
        ]);
    }

    public function storeSchedule(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:100'],
            'cron' => ['required', 'string', 'max:50'],
            'scope' => ['required', 'in:full,home_only,db_only,config_only'],
            'destination_id' => ['required', 'integer', 'exists:backup_destinations,id'],
            'compression' => ['required', 'in:none,gzip,zstd,xz'],
            'encrypt' => ['boolean'],
            'keep_daily' => ['integer', 'min:0', 'max:90'],
            'keep_weekly' => ['integer', 'min:0', 'max:52'],
            'keep_monthly' => ['integer', 'min:0', 'max:36'],
            'keep_yearly' => ['integer', 'min:0', 'max:10'],
            'is_active' => ['boolean'],
            'exclude_paths' => ['array'],
        ]);

        BackupSchedule::create($validated);

        return back()->with('success', "Yedek profili '{$validated['name']}' oluşturuldu.");
    }

    public function updateSchedule(Request $request, BackupSchedule $schedule): RedirectResponse
    {
        $validated = $request->validate([
            'name' => ['sometimes', 'string', 'max:100'],
            'cron' => ['sometimes', 'string', 'max:50'],
            'scope' => ['sometimes', 'in:full,home_only,db_only,config_only'],
            'destination_id' => ['sometimes', 'integer', 'exists:backup_destinations,id'],
            'compression' => ['sometimes', 'in:none,gzip,zstd,xz'],
            'encrypt' => ['boolean'],
            'keep_daily' => ['integer', 'min:0', 'max:90'],
            'keep_weekly' => ['integer', 'min:0', 'max:52'],
            'keep_monthly' => ['integer', 'min:0', 'max:36'],
            'keep_yearly' => ['integer', 'min:0', 'max:10'],
            'is_active' => ['boolean'],
        ]);

        $schedule->update($validated);

        return back()->with('success', 'Yedek profili güncellendi.');
    }

    public function destroySchedule(BackupSchedule $schedule): RedirectResponse
    {
        $schedule->delete();

        return back()->with('success', 'Yedek profili silindi.');
    }

    public function runScheduleNow(Request $request, BackupSchedule $schedule): RedirectResponse
    {
        // Schedule'i "running" durumuna işaretle, queued job çalışana kadar bu görünür.
        $schedule->update([
            'last_run_status' => 'pending',
            'last_run_error' => null,
        ]);

        // Gerçek RunBackupScheduleJob — kuyruğa atılır, worker BackupService::create()
        // ile her aktif hesap için sysapi `backup-run` çağırır, schedule.last_run_*
        // alanlarını GERÇEK metric'lerle (duration, size, status) günceller.
        RunBackupScheduleJob::dispatch($schedule->id, 'manual')
            ->onQueue('backups');

        // Audit
        try {
            if (\Illuminate\Support\Facades\Schema::hasTable('audit_log')) {
                DB::table('audit_log')->insert([
                    'user_id' => $request->user()?->id,
                    'action' => 'backup.schedule.manual_run',
                    'target_type' => 'backup_schedule',
                    'target_id' => $schedule->id,
                    'ip_address' => $request->ip(),
                    'outcome' => 'queued',
                    'metadata' => json_encode([
                        'schedule_name' => $schedule->name,
                        'scope' => $schedule->scope,
                    ], JSON_UNESCAPED_UNICODE),
                    'created_at' => now(),
                ]);
            }
        } catch (\Throwable) {
            // soft-fail
        }

        return back()->with('success', "'{$schedule->name}' profili manuel başlatıldı — backup queue'a alındı. Süresine göre 1-30 dk sürebilir.");
    }

    // ─── Destinations ───────────────────────────────────────────────────

    public function destinations(Request $request): Response
    {
        $destinations = BackupDestination::whereNull('deleted_at')->latest()->get();

        return Inertia::render('Admin/Backup/Destinations', [
            'destinations' => $destinations->map(fn ($d) => $this->destinationResource($d)),
        ]);
    }

    public function storeDestination(Request $request): RedirectResponse
    {
        $driver = $request->input('driver', 'local');

        $base = $request->validate([
            'name' => ['required', 'string', 'max:100'],
            'driver' => ['required', Rule::in(['local', 's3', 'restic', 'sftp', 'backblaze_b2', 'wasabi', 'ftp'])],
            'is_active' => ['boolean'],
            'is_default' => ['boolean'],
        ]);

        // Per-driver field validation
        $driverFields = match ($driver) {
            'local' => $request->validate([
                'local_path' => ['required', 'string', 'max:191', 'regex:#^(/var/backups/onox|/mnt/backup)#'],
            ]),

            's3', 'wasabi' => $request->validate([
                'endpoint' => ['nullable', 'url', 'max:191'],
                'region' => ['required', 'string', 'max:50'],
                'bucket' => ['required', 'string', 'max:100'],
                'access_key' => ['required', 'string', 'max:191'],
                'secret_key' => ['required', 'string', 'max:500'],
                'path_prefix' => ['nullable', 'string', 'max:191'],
            ]),

            'backblaze_b2' => $request->validate([
                'bucket' => ['required', 'string', 'max:100'],
                // B2 uses access_key for application_key_id, secret_key for application_key
                'access_key' => ['required', 'string', 'max:191'],
                'secret_key' => ['required', 'string', 'max:500'],
                'region' => ['nullable', 'string', 'max:50'],
                'endpoint' => ['nullable', 'url', 'max:191'],
                'path_prefix' => ['nullable', 'string', 'max:191'],
            ]),

            'sftp' => $request->validate([
                'sftp_host' => ['required', 'string', 'max:191'],
                'sftp_port' => ['nullable', 'integer', 'min:1', 'max:65535'],
                'sftp_user' => ['required', 'string', 'max:100'],
                'sftp_private_key' => ['nullable', 'string'],
                'sftp_password' => ['nullable', 'string', 'max:500'],
                'sftp_remote_path' => ['required', 'string', 'max:191'],
            ]),

            'ftp' => $request->validate([
                'ftp_host' => ['required', 'string', 'max:191'],
                'ftp_port' => ['nullable', 'integer', 'min:1', 'max:65535'],
                'ftp_user' => ['required', 'string', 'max:100'],
                'ftp_password' => ['required', 'string', 'max:500'],
                'ftp_remote_path' => ['required', 'string', 'max:191'],
                'ftp_passive' => ['boolean'],
            ]),

            'restic' => $request->validate([
                'restic_repo' => ['required', 'string', 'max:191'],
                'restic_password' => ['required', 'string', 'max:191'],
            ]),

            default => [],
        };

        // Set sane port defaults before save
        if ($driver === 'sftp' && empty($driverFields['sftp_port'])) {
            $driverFields['sftp_port'] = 22;
        }
        if ($driver === 'ftp' && empty($driverFields['ftp_port'])) {
            $driverFields['ftp_port'] = 21;
        }
        if ($driver === 'backblaze_b2' && empty($driverFields['region'])) {
            $driverFields['region'] = 'us-west-002';
        }

        // is_default uniqueness
        if (! empty($base['is_default'])) {
            BackupDestination::query()->update(['is_default' => false]);
        }

        $dest = BackupDestination::create(array_merge($base, $driverFields));

        return back()->with('success', "Yedek hedefi '{$dest->name}' oluşturuldu.");
    }

    /**
     * Test a destination's connection and return JSON (called via fetch from Vue).
     * Falls back to redirect+flash for non-XHR callers.
     */
    public function testDestination(Request $request, BackupDestination $destination): JsonResponse|RedirectResponse
    {
        $force = $request->boolean('force', false);
        $result = $this->runDestinationTest($request, $destination, $force);

        if ($request->wantsJson() || $request->ajax()) {
            return response()->json($result);
        }

        $msg = $result['success']
            ? "Bağlantı başarılı: {$destination->name} — {$result['latency_ms']}ms"
            : "Bağlantı başarısız: {$destination->name} — ".($result['error_message'] ?? 'Bilinmeyen hata');

        return back()->with($result['success'] ? 'success' : 'error', $msg);
    }

    /**
     * JSON-only test endpoint used by inline AJAX testing in the UI.
     */
    public function testDestinationJson(Request $request, BackupDestination $destination): JsonResponse
    {
        $force = $request->boolean('force', false);
        $result = $this->runDestinationTest($request, $destination, $force);

        return response()->json($result);
    }

    /**
     * Shared logic: run test, persist health flags, audit log, enrich response.
     *
     * @return array<string, mixed>
     */
    private function runDestinationTest(Request $request, BackupDestination $destination, bool $force): array
    {
        $manager = app(BackupDestinationManager::class);

        // Detect cache-hit before remember() runs.
        $cacheKey = "backup_dest_test:{$destination->id}";
        $wasCached = ! $force && \Illuminate\Support\Facades\Cache::has($cacheKey);

        $result = $force
            ? $manager->forceTest($destination)
            : $manager->testConnection($destination);

        $result['cached'] = $wasCached;
        $result['destination_id'] = $destination->id;
        $result['destination_name'] = $destination->name;

        // Only persist health on fresh (non-cached) tests, to avoid spamming updates
        if (! $wasCached) {
            $destination->update([
                'last_health_check_at' => now(),
                'is_healthy' => (bool) ($result['success'] ?? $result['ok'] ?? false),
                'last_error' => ($result['success'] ?? false) ? null : ($result['error_message'] ?? 'Test başarısız'),
                'last_test_result' => $result,
            ]);

            // Audit log — soft-fail
            try {
                if (\Illuminate\Support\Facades\Schema::hasTable('audit_log')) {
                    DB::table('audit_log')->insert([
                        'user_id' => $request->user()?->id,
                        'action' => 'backup.destination_tested',
                        'target_type' => 'backup_destination',
                        'target_id' => $destination->id,
                        'ip_address' => $request->ip(),
                        'outcome' => ($result['success'] ?? false) ? 'success' : 'failure',
                        'metadata' => json_encode([
                            'destination_name' => $destination->name,
                            'driver' => $destination->driver,
                            'forced' => $force,
                            'latency_ms' => $result['latency_ms'] ?? 0,
                            'upload_mbps' => $result['throughput']['upload_mbps'] ?? null,
                            'download_mbps' => $result['throughput']['download_mbps'] ?? null,
                            'total_duration_ms' => $result['total_duration_ms'] ?? 0,
                            'error' => $result['error_message'] ?? null,
                        ], JSON_UNESCAPED_UNICODE),
                        'created_at' => now(),
                    ]);
                }
            } catch (\Throwable) {
                // soft-fail
            }
        }

        return $result;
    }

    public function destroyDestination(BackupDestination $destination): RedirectResponse
    {
        if ($destination->is_default) {
            return back()->with('error', 'Varsayılan hedef silinemez. Önce başka bir hedefi varsayılan yap.');
        }

        $destination->delete();

        return back()->with('success', 'Yedek hedefi silindi.');
    }

    // ─── History + Restore ──────────────────────────────────────────────

    public function history(Request $request): Response
    {
        $query = Backup::with(['account:id,username,email']);

        if ($s = $request->string('status')->value()) {
            $query->where('status', $s);
        }
        if ($search = $request->string('search')->value()) {
            $query->whereHas('account', fn ($q) => $q->where('username', 'like', "%{$search}%")->orWhere('email', 'like', "%{$search}%"));
        }

        $backups = $query->latest()->paginate(30)->withQueryString()
            ->through(fn ($b) => [
                'id' => $b->id,
                'account_username' => $b->account?->username,
                'account_email' => $b->account?->email,
                'type' => $b->type,
                'size_bytes' => $b->size_bytes,
                'size_human' => $this->humanBytes($b->size_bytes),
                'status' => $b->status,
                'file_path' => $b->file_path,
                'created_at' => $b->created_at->format('d.m.Y H:i'),
                'expires_at' => $b->expires_at?->format('d.m.Y'),
            ]);

        return Inertia::render('Admin/Backup/History', [
            'backups' => $backups,
            'filters' => [
                'status' => $request->string('status')->value(),
                'search' => $request->string('search')->value(),
            ],
        ]);
    }

    public function restoreNow(Request $request, Backup $backup, BackupService $service): RedirectResponse
    {
        // Onay zorunlu — kazara restore koruması (cross-account replay önleme)
        $confirmId = (int) $request->input('confirm_backup_id', 0);
        if ($confirmId !== $backup->id) {
            return back()->with('error',
                "Restore onayı eksik. confirm_backup_id={$backup->id} olmalı."
            );
        }

        try {
            // BackupService::restore incremental ise chain'i otomatik toplar (Phase 2).
            $service->restore($backup);

            $msg = $backup->isIncremental()
                ? "Incremental yedek #{$backup->id} chain (#{$backup->chain_position} pozisyon) restore başlatıldı."
                : "Yedek #{$backup->id} restore başlatıldı.";

            return back()->with('success', $msg);
        } catch (BackupException $e) {
            return back()->with('error', "Restore başarısız: {$e->getMessage()}");
        }
    }

    /**
     * @return array<string, mixed>
     */
    private function destinationResource(BackupDestination $d): array
    {
        return [
            'id' => $d->id,
            'name' => $d->name,
            'driver' => $d->driver,
            'is_active' => $d->is_active,
            'is_default' => $d->is_default,
            'is_healthy' => $d->is_healthy,
            // S3/B2/Wasabi
            'bucket' => $d->bucket,
            'region' => $d->region,
            'endpoint' => $d->endpoint,
            'access_key_masked' => $d->maskedSecret(),
            'path_prefix' => $d->path_prefix,
            // Local
            'local_path' => $d->local_path,
            // SFTP
            'sftp_host' => $d->sftp_host,
            'sftp_port' => $d->sftp_port,
            'sftp_user' => $d->sftp_user,
            'sftp_remote_path' => $d->sftp_remote_path,
            'sftp_has_password' => (bool) $d->getRawOriginal('sftp_password'),
            'sftp_has_private_key' => (bool) $d->getRawOriginal('sftp_private_key'),
            // FTP
            'ftp_host' => $d->ftp_host,
            'ftp_port' => $d->ftp_port,
            'ftp_user' => $d->ftp_user,
            'ftp_remote_path' => $d->ftp_remote_path,
            'ftp_passive' => $d->ftp_passive,
            // Restic
            'restic_repo' => $d->restic_repo,
            // Stats
            'total_bytes' => $d->total_bytes,
            'total_human' => $this->humanBytes($d->total_bytes),
            'backup_count' => $d->backup_count,
            'last_backup_at' => $d->last_backup_at?->format('d.m.Y H:i'),
            'last_health_check_at' => $d->last_health_check_at?->format('d.m.Y H:i'),
            'last_error' => $d->last_error,
            'last_test_result' => $d->last_test_result,
        ];
    }

    private function humanBytes(int $bytes): string
    {
        if ($bytes < 1024) {
            return $bytes.' B';
        }
        if ($bytes < 1024 ** 2) {
            return number_format($bytes / 1024, 1).' KB';
        }
        if ($bytes < 1024 ** 3) {
            return number_format($bytes / 1024 ** 2, 1).' MB';
        }

        return number_format($bytes / 1024 ** 3, 2).' GB';
    }
}
