<?php

namespace App\Console\Commands;

use App\Models\User;
use App\Models\Video;
use App\Services\NasSyncService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;

class NasFreeLocalStorage extends Command
{
    protected $signature = 'nas:free-local
                            {--dry-run : Preview what would be deleted without deleting}
                            {--force  : Actually delete local files confirmed on NAS}
                            {--cache-ttl=24 : Hours after which NAS stream-cache files are evicted (0 = all)}';

    protected $description = 'Delete local files (videos, thumbnails, avatars, banners) already stored on the NAS';

    public function handle(NasSyncService $nas): int
    {
        if (! $nas->isEnabled()) {
            $this->error('NAS sync is not enabled. Enable it in Admin → Settings first.');
            return 1;
        }

        $dryRun = $this->option('dry-run');
        $force  = $this->option('force');

        if (! $dryRun && ! $force) {
            $this->warn('Pass --dry-run to preview, or --force to delete.');
            return 1;
        }

        $mode = $dryRun ? 'DRY RUN — nothing will be deleted' : 'FORCE — deleting confirmed NAS files';
        $this->info($mode);
        $this->newLine();

        $totalBytes = 0;
        $toDelete   = [];

        // ── Videos ───────────────────────────────────────────────────────────
        $this->info('Scanning video files…');

        $videos = Video::all()->filter(fn (Video $v) => file_exists(storage_path('app/' . $v->path)));

        if ($videos->isNotEmpty()) {
            $bar = $this->output->createProgressBar($videos->count());
            $bar->start();

            foreach ($videos as $video) {
                $localPath = storage_path('app/' . $video->path);
                $dir  = $nas->resolveVideoDir($video);
                $meta = null;
                try {
                    $raw  = $nas->getContent("{$dir}/meta.json");
                    $meta = $raw ? json_decode($raw, true) : null;
                } catch (\Throwable) {}

                if (is_array($meta) && ($meta['id'] ?? null) === $video->id) {
                    $bytes       = filesize($localPath);
                    $totalBytes += $bytes;
                    $toDelete[]  = ['label' => "video #{$video->id}", 'path' => $localPath, 'bytes' => $bytes];
                }
                $bar->advance();
            }

            $bar->finish();
            $this->newLine();
        } else {
            $this->line('  No local video files found.');
        }

        // ── Thumbnails & slides (both legacy flat dir and new NAS-mirrored dirs) ──
        $this->newLine();
        $this->info('Scanning thumbnail and slide files…');

        // Helper: check if a video's NAS dir is confirmed, using meta.json
        $confirmNas = function (Video $v) use ($nas): bool {
            try {
                $raw = $nas->getContent($nas->resolveVideoDir($v) . '/meta.json');
                $meta = $raw ? json_decode($raw, true) : null;
                return is_array($meta) && ($meta['id'] ?? null) === $v->id;
            } catch (\Throwable) { return false; }
        };

        // Legacy flat thumbnails dir
        $thumbDir = storage_path('app/public/thumbnails');
        if (is_dir($thumbDir)) {
            foreach (glob($thumbDir . '/*') as $file) {
                if (! is_file($file)) continue;
                $filename = basename($file);
                $video    = Video::where('thumbnail', $filename)->first();
                if ($video) {
                    if ($confirmNas($video)) {
                        $bytes = filesize($file); $totalBytes += $bytes;
                        $toDelete[] = ['label' => "thumb:{$filename}", 'path' => $file, 'bytes' => $bytes];
                    }
                } else {
                    $slide = \App\Models\VideoSlide::where('filename', $filename)->with('video')->first();
                    if ($slide && $slide->video && $confirmNas($slide->video)) {
                        $bytes = filesize($file); $totalBytes += $bytes;
                        $toDelete[] = ['label' => "slide:{$filename}", 'path' => $file, 'bytes' => $bytes];
                    }
                }
            }
        }

        // New NAS-mirrored dirs: storage/app/users/{username}/videos/{slug}/thumb.*
        //                                                                    slides/{id}.*
        $usersBase = storage_path('app/users');
        if (is_dir($usersBase)) {
            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($usersBase, \FilesystemIterator::SKIP_DOTS)
            );
            foreach ($iterator as $file) {
                if (! $file->isFile()) continue;
                $absPath  = $file->getPathname();
                $relPath  = ltrim(str_replace(storage_path('app'), '', $absPath), '/');

                // Match thumbnail or slide by relative path stored in DB
                $video = Video::where('thumbnail', $relPath)->first();
                if ($video) {
                    if ($confirmNas($video)) {
                        $bytes = $file->getSize(); $totalBytes += $bytes;
                        $toDelete[] = ['label' => 'thumb:' . basename($relPath), 'path' => $absPath, 'bytes' => $bytes];
                    }
                    continue;
                }
                $slide = \App\Models\VideoSlide::where('filename', $relPath)->with('video')->first();
                if ($slide && $slide->video && $confirmNas($slide->video)) {
                    $bytes = $file->getSize(); $totalBytes += $bytes;
                    $toDelete[] = ['label' => 'slide:' . basename($relPath), 'path' => $absPath, 'bytes' => $bytes];
                }
            }
        }

        $this->line('  Done scanning thumbnails.');

        // ── Avatars ───────────────────────────────────────────────────────────
        $this->newLine();
        $this->info('Scanning avatar files…');

        // Legacy flat dir
        $avatarDir = storage_path('app/public/avatars');
        if (is_dir($avatarDir)) {
            foreach (glob($avatarDir . '/*') as $file) {
                if (! is_file($file)) continue;
                $filename = basename($file);
                $user     = User::where('avatar', $filename)->first();
                if (! $user) continue;

                $dir = "users/{$nas->userSlug($user)}/profile";
                $raw = null;
                try { $raw = $nas->getContent("{$dir}/avatar.webp"); } catch (\Throwable) {}
                if ($raw !== null) {
                    $bytes = filesize($file); $totalBytes += $bytes;
                    $toDelete[] = ['label' => "avatar:{$filename}", 'path' => $file, 'bytes' => $bytes];
                }
            }
        }

        // New structured dir: users/{slug}/profile/avatar.*
        $usersBase = storage_path('app/users');
        if (is_dir($usersBase)) {
            foreach (glob("{$usersBase}/*/profile/avatar.*") ?: [] as $file) {
                if (! is_file($file)) continue;
                $relPath = 'users/' . ltrim(str_replace(storage_path('app/users') . '/', '', str_replace('\\', '/', $file)), '/');
                $user = User::where('avatar', $relPath)->first();
                if (! $user) continue;

                $dir = "users/{$nas->userSlug($user)}/profile";
                $raw = null;
                try { $raw = $nas->getContent("{$dir}/avatar.webp"); } catch (\Throwable) {}
                if ($raw !== null) {
                    $bytes = filesize($file); $totalBytes += $bytes;
                    $toDelete[] = ['label' => 'avatar:' . basename($file), 'path' => $file, 'bytes' => $bytes];
                }
            }
        }

        $this->line('  Done scanning avatars.');

        // ── Banners ───────────────────────────────────────────────────────────
        $this->newLine();
        $this->info('Scanning banner files…');

        // Legacy flat dir
        $bannerDir = storage_path('app/public/banners');
        if (is_dir($bannerDir)) {
            foreach (glob($bannerDir . '/*') as $file) {
                if (! is_file($file)) continue;
                $filename = basename($file);
                $user     = User::where('banner', $filename)->first();
                if (! $user) continue;

                $dir = "users/{$nas->userSlug($user)}/profile";
                $raw = null;
                try { $raw = $nas->getContent("{$dir}/cover.webp"); } catch (\Throwable) {}
                if ($raw !== null) {
                    $bytes = filesize($file); $totalBytes += $bytes;
                    $toDelete[] = ['label' => "banner:{$filename}", 'path' => $file, 'bytes' => $bytes];
                }
            }
        }

        // New structured dir: users/{slug}/profile/cover.*
        if (is_dir($usersBase)) {
            foreach (glob("{$usersBase}/*/profile/cover.*") ?: [] as $file) {
                if (! is_file($file)) continue;
                $relPath = 'users/' . ltrim(str_replace(storage_path('app/users') . '/', '', str_replace('\\', '/', $file)), '/');
                $user = User::where('banner', $relPath)->first();
                if (! $user) continue;

                $dir = "users/{$nas->userSlug($user)}/profile";
                $raw = null;
                try { $raw = $nas->getContent("{$dir}/cover.webp"); } catch (\Throwable) {}
                if ($raw !== null) {
                    $bytes = filesize($file); $totalBytes += $bytes;
                    $toDelete[] = ['label' => 'banner:' . basename($file), 'path' => $file, 'bytes' => $bytes];
                }
            }
        }

        $this->line('  Done scanning banners.');

        // ── Generated/derived renders inside song folders ─────────────────────
        // Everything under a song's cache/ subfolder (download videos + HLS) is a
        // render that regenerates on demand, so it is always safe to delete to free
        // space. Sources (audio, tracks, slides) live outside cache/ and are untouched.
        $this->newLine();
        $this->info('Scanning generated renders (song cache/ folders)…');

        $usersRoot = storage_path('app/users');
        if (is_dir($usersRoot)) {
            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($usersRoot, \FilesystemIterator::SKIP_DOTS)
            );
            foreach ($iterator as $file) {
                if (! $file->isFile()) continue;
                $path = $file->getPathname();
                if (! str_contains($path, '/cache/')) continue;
                $bytes       = $file->getSize();
                $totalBytes += $bytes;
                $toDelete[]  = ['label' => str_contains($path, '/cache/hls/') ? 'hls' : 'download-video', 'path' => $path, 'bytes' => $bytes];
            }
            $this->line('  Done scanning generated renders.');
        }

        // ── NAS stream cache (nas_cache/videos/) ──────────────────────────────
        // These are on-demand local copies of NAS videos used for HTTP streaming.
        // Always safe to delete — they are re-downloaded from NAS on next play.
        $this->newLine();
        $this->info('Scanning NAS stream cache…');

        $nasCacheDir = storage_path('app/nas_cache/videos');
        $ttl         = (int) $this->option('cache-ttl');
        $cutoff      = time() - ($ttl * 3600);

        if (is_dir($nasCacheDir)) {
            foreach (glob("{$nasCacheDir}/*") as $file) {
                if (! is_file($file)) continue;
                if ($ttl > 0 && filemtime($file) >= $cutoff) continue;
                $bytes       = filesize($file);
                $totalBytes += $bytes;
                $toDelete[]  = ['label' => 'nas-cache', 'path' => $file, 'bytes' => $bytes];
            }
            $this->line('  Done scanning NAS stream cache.');
        }

        // ── Summary ───────────────────────────────────────────────────────────
        $this->newLine();

        if (empty($toDelete)) {
            $this->info('Nothing to delete — all local files are either not yet on NAS or already gone.');
            return 0;
        }

        $this->table(
            ['Type', 'File', 'Size'],
            array_map(fn ($row) => [
                $row['label'],
                basename($row['path']),
                $this->humanBytes($row['bytes']),
            ], $toDelete)
        );

        $this->newLine();
        $this->line(sprintf(
            'Found <comment>%d</comment> file(s) totalling <comment>%s</comment> that can be freed.',
            count($toDelete),
            $this->humanBytes($totalBytes)
        ));

        if ($dryRun) {
            $this->newLine();
            $this->warn('Run with --force to delete these files.');
            return 0;
        }

        // ── Actually delete ───────────────────────────────────────────────────
        $deleted = 0;
        $failed  = 0;

        foreach ($toDelete as $row) {
            if (@unlink($row['path'])) {
                $deleted++;
                Log::info('nas:free-local: deleted ' . $row['path']);
            } else {
                $failed++;
                $this->warn("Could not delete: {$row['path']}");
            }
        }

        // Prune empty directories left behind under storage/app/users/ and flat asset dirs
        $this->pruneEmptyDirs(storage_path('app/users'));
        foreach (['public/avatars', 'public/banners', 'public/thumbnails'] as $rel) {
            $path = storage_path("app/{$rel}");
            if (is_dir($path) && empty(array_diff(scandir($path) ?: [], ['.', '..']))) {
                @rmdir($path);
            }
        }

        $this->newLine();
        $this->info("Deleted {$deleted} file(s), freed {$this->humanBytes($totalBytes)}.");

        if ($failed > 0) {
            $this->warn("{$failed} file(s) could not be deleted — check permissions.");
        }

        return 0;
    }

    /**
     * Bottom-up prune: remove dirs that are empty or contain only meta.json.
     */
    private function pruneEmptyDirs(string $root): void
    {
        if (! is_dir($root)) return;

        $iter = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($root, \FilesystemIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::CHILD_FIRST
        );

        foreach ($iter as $item) {
            if (! $item->isDir()) continue;
            $path     = $item->getPathname();
            $contents = array_diff(scandir($path) ?: [], ['.', '..']);
            $nonMeta  = array_diff($contents, ['meta.json']);
            if (empty($contents)) {
                @rmdir($path);
            } elseif (empty($nonMeta)) {
                @unlink("{$path}/meta.json");
                @rmdir($path);
            }
        }
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private function humanBytes(int $bytes): string
    {
        if ($bytes >= 1_073_741_824) return round($bytes / 1_073_741_824, 2) . ' GB';
        if ($bytes >= 1_048_576)     return round($bytes / 1_048_576, 2)     . ' MB';
        if ($bytes >= 1_024)         return round($bytes / 1_024, 2)         . ' KB';
        return $bytes . ' B';
    }
}
