<?php

namespace App\Console\Commands;

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

class NasRepairLocalFiles extends Command
{
    protected $signature = 'nas:repair
                            {--dry-run : Preview what would be pushed without making changes}
                            {--force  : Actually upload to NAS and remove local copies}
                            {--cache-ttl=24 : Hours after which NAS stream-cache files are evicted (0 = all)}';

    protected $description = 'Upload stuck local files to NAS, fix DB paths, and delete local copies';

    private NasSyncService $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;
        }

        $this->nas = $nas;

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

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

        $this->info($dryRun ? 'DRY RUN — nothing will be changed' : 'FORCE — uploading to NAS and removing local copies');
        $this->newLine();

        $totalRepaired = 0;
        $totalFailed   = 0;

        // ── NAS-format videos / slides / thumbnails ───────────────────────────
        $stuckVideos = $this->findStuckVideos();
        if ($stuckVideos->isNotEmpty()) {
            $this->info('Video / slide files stuck locally:');
            $rows = [];
            foreach ($stuckVideos as $item) {
                foreach ($item['files'] as $label) {
                    $rows[] = [$item['video']->id, substr($item['video']->title, 0, 40), $label];
                }
            }
            $this->table(['Video ID', 'Title', 'File'], $rows);
            $this->newLine();

            if ($force) {
                foreach ($stuckVideos as $item) {
                    $video = $item['video'];
                    $this->line("  Repairing video #{$video->id}: {$video->title}…");
                    try {
                        $nas->syncVideo($video);
                        $nas->deleteLocalAssets($video);
                        if ($video->hls_path || $video->type === 'music') {
                            $nas->deleteLocalVideo($video);
                        }
                        $nas->pruneLocalVideoDir($video);
                        $totalRepaired++;
                        $this->line('    <info>✓ Done</info>');
                        Log::info("nas:repair: fixed video #{$video->id}");
                    } catch (\Throwable $e) {
                        $totalFailed++;
                        $this->warn("    ✗ Failed: {$e->getMessage()}");
                        Log::error("nas:repair: failed video #{$video->id}: " . $e->getMessage());
                    }
                }
                $this->newLine();
            }
        }

        // ── Avatars ───────────────────────────────────────────────────────────
        $stuckAvatars = $this->findStuckAvatars();
        if ($stuckAvatars->isNotEmpty()) {
            $this->info('Avatar files stuck locally:');
            $this->table(['User ID', 'Username', 'File'], $stuckAvatars->map(fn ($r) => [
                $r['user']->id, $r['user']->username ?? $r['user']->name, $r['file'],
            ])->all());
            $this->newLine();

            if ($force) {
                foreach ($stuckAvatars as $item) {
                    $user = $item['user'];
                    $path = $item['path'];
                    $this->line("  Repairing avatar for {$user->username}…");
                    try {
                        $nas->syncAvatar($user, $path);
                        $nas->deleteLocalAvatar($user);
                        $totalRepaired++;
                        $this->line('    <info>✓ Done</info>');
                    } catch (\Throwable $e) {
                        $totalFailed++;
                        $this->warn("    ✗ Failed: {$e->getMessage()}");
                        Log::error("nas:repair: failed avatar user#{$user->id}: " . $e->getMessage());
                    }
                }
                $this->newLine();
            }
        }

        // ── Banners ───────────────────────────────────────────────────────────
        $stuckBanners = $this->findStuckBanners();
        if ($stuckBanners->isNotEmpty()) {
            $this->info('Banner files stuck locally:');
            $this->table(['User ID', 'Username', 'File'], $stuckBanners->map(fn ($r) => [
                $r['user']->id, $r['user']->username ?? $r['user']->name, $r['file'],
            ])->all());
            $this->newLine();

            if ($force) {
                foreach ($stuckBanners as $item) {
                    $user = $item['user'];
                    $path = $item['path'];
                    $this->line("  Repairing banner for {$user->username}…");
                    try {
                        $nas->syncCover($user, $path);
                        $nas->deleteLocalBanner($user);
                        $totalRepaired++;
                        $this->line('    <info>✓ Done</info>');
                    } catch (\Throwable $e) {
                        $totalFailed++;
                        $this->warn("    ✗ Failed: {$e->getMessage()}");
                        Log::error("nas:repair: failed banner user#{$user->id}: " . $e->getMessage());
                    }
                }
                $this->newLine();
            }
        }

        // ── Legacy flat thumbnails (public/thumbnails/) ───────────────────────
        $stuckThumbs = $this->findStuckLegacyThumbnails();
        if ($stuckThumbs->isNotEmpty()) {
            $this->info('Legacy thumbnail/slide files stuck locally:');
            $this->table(['Type', 'File', 'Video ID'], $stuckThumbs->map(fn ($r) => [
                $r['type'], $r['file'], $r['video_id'],
            ])->all());
            $this->newLine();

            if ($force) {
                foreach ($stuckThumbs as $item) {
                    $this->line("  Repairing {$item['type']}: {$item['file']}…");
                    try {
                        if ($item['type'] === 'thumbnail' && $item['video']) {
                            $nas->syncVideo($item['video']);
                        } elseif ($item['type'] === 'slide' && $item['slide'] && $item['video']) {
                            // Upload slide directly to NAS
                            $video    = $item['video'];
                            $slide    = $item['slide'];
                            $dir      = $nas->resolveVideoDir($video);
                            $ext      = pathinfo($item['path'], PATHINFO_EXTENSION) ?: 'jpg';
                            $nas->mkdirp("{$dir}/slides");
                            $nas->putFile($item['path'], "{$dir}/slides/{$slide->position}.{$ext}");
                        }
                        @unlink($item['path']);
                        $totalRepaired++;
                        $this->line('    <info>✓ Done</info>');
                    } catch (\Throwable $e) {
                        $totalFailed++;
                        $this->warn("    ✗ Failed: {$e->getMessage()}");
                        Log::error("nas:repair: failed legacy thumb {$item['file']}: " . $e->getMessage());
                    }
                }
                $this->newLine();
            }
        }

        $anyStuck = $stuckVideos->isNotEmpty() || $stuckAvatars->isNotEmpty()
                 || $stuckBanners->isNotEmpty() || $stuckThumbs->isNotEmpty();

        // ── NAS orphaned video folders ─────────────────────────────────────────
        $this->newLine();
        $this->info('Scanning NAS for orphaned video folders (no matching DB record)…');
        $nasOrphans = $nas->scanNasOrphans();

        if (! empty($nasOrphans)) {
            $this->table(
                ['NAS Directory', 'meta.json video_id'],
                array_map(fn ($o) => [$o['dir'], $o['video_id'] ?? '(none)'], $nasOrphans)
            );
            $this->newLine();

            if ($force) {
                $deletedOrphans = 0;
                $failedOrphans  = 0;
                foreach ($nasOrphans as $orphan) {
                    $this->line("  Deleting NAS orphan: {$orphan['dir']}…");
                    try {
                        $nas->deleteNasTree($orphan['dir']);
                        $deletedOrphans++;
                        $this->line('    <info>✓ Deleted</info>');
                        Log::info('nas:repair: deleted NAS orphan', ['dir' => $orphan['dir']]);
                    } catch (\Throwable $e) {
                        $failedOrphans++;
                        $this->warn("    ✗ Failed: {$e->getMessage()}");
                        Log::error('nas:repair: failed to delete NAS orphan', ['dir' => $orphan['dir'], 'error' => $e->getMessage()]);
                    }
                }
                $totalRepaired += $deletedOrphans;
                $totalFailed   += $failedOrphans;
                $this->newLine();
            }
        } else {
            $this->line('  No orphaned NAS folders found.');
        }

        $anyIssues = $anyStuck || ! empty($nasOrphans);

        // ── NAS stream cache ──────────────────────────────────────────────────
        $cacheBytes = $nas->nasCacheSize();
        if ($cacheBytes > 0) {
            $ttl = (int) $this->option('cache-ttl');
            $ttlLabel = $ttl === 0 ? 'all files' : "files older than {$ttl}h";
            $this->info(sprintf(
                'NAS stream cache: <comment>%s</comment> occupying <comment>%s</comment> — will be evicted on --force (%s).',
                count(glob(storage_path('app/nas_cache/videos/*')) ?: []) . ' file(s)',
                $this->humanBytes($cacheBytes),
                $ttlLabel
            ));
            $this->newLine();
        }

        if (! $anyIssues && $cacheBytes === 0) {
            $this->info('Nothing to repair — no stuck local files, no NAS orphans, and no stream cache found.');
        } elseif (! $anyIssues) {
            $this->info('No issues found (stream cache will be evicted on --force).');
        }

        if ($dryRun && ($anyIssues || $cacheBytes > 0)) {
            $this->warn('Run with --force to repair.');
            return 0;
        }

        if ($force) {
            $this->pruneAllLocalDirs();

            $ttl     = (int) $this->option('cache-ttl');
            $evicted = $nas->clearNasCache($ttl);
            if ($evicted > 0) {
                $this->line("Evicted <comment>{$evicted}</comment> NAS stream-cache file(s).");
            }
        }

        if ($totalRepaired > 0 || $force) {
            $this->newLine();
            if ($totalFailed === 0) {
                $this->info("Repaired {$totalRepaired} item(s). All local directories cleaned up.");
            } else {
                $this->warn("Repaired: {$totalRepaired}    Failed: {$totalFailed} — check logs for details.");
            }
        }

        return $totalFailed > 0 ? 1 : 0;
    }

    // ── Scanners ──────────────────────────────────────────────────────────────

    private function findStuckVideos(): \Illuminate\Support\Collection
    {
        return Video::with(['user', 'slides'])->get()
            ->filter(fn (Video $v) => str_starts_with($v->path, 'users/'))
            ->map(function (Video $video) {
                $files = [];
                if (file_exists(storage_path('app/' . $video->path)))
                    $files[] = basename($video->path) . ' (video)';
                if ($video->thumbnail && str_contains($video->thumbnail, '/') &&
                    file_exists(storage_path('app/' . $video->thumbnail)))
                    $files[] = basename($video->thumbnail) . ' (thumbnail)';
                foreach ($video->slides as $slide) {
                    if (file_exists($slide->localPath()))
                        $files[] = basename($slide->filename) . " (slide #{$slide->position})";
                }
                return $files ? ['video' => $video, 'files' => $files] : null;
            })
            ->filter();
    }

    private function findStuckAvatars(): \Illuminate\Support\Collection
    {
        $results = collect();

        // Legacy flat dir: public/avatars/
        $dir = storage_path('app/public/avatars');
        if (is_dir($dir)) {
            $flat = collect(scandir($dir) ?: [])
                ->filter(fn ($f) => $f !== '.' && $f !== '..' && is_file("{$dir}/{$f}"))
                ->map(function ($filename) use ($dir) {
                    $user = User::where('avatar', $filename)->first();
                    return $user ? ['user' => $user, 'file' => $filename, 'path' => "{$dir}/{$filename}"] : null;
                })
                ->filter();
            $results = $results->merge($flat);
        }

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

        return $results;
    }

    private function findStuckBanners(): \Illuminate\Support\Collection
    {
        $results = collect();

        // Legacy flat dir: public/banners/
        $dir = storage_path('app/public/banners');
        if (is_dir($dir)) {
            $flat = collect(scandir($dir) ?: [])
                ->filter(fn ($f) => $f !== '.' && $f !== '..' && is_file("{$dir}/{$f}"))
                ->map(function ($filename) use ($dir) {
                    $user = User::where('banner', $filename)->first();
                    return $user ? ['user' => $user, 'file' => $filename, 'path' => "{$dir}/{$filename}"] : null;
                })
                ->filter();
            $results = $results->merge($flat);
        }

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

        return $results;
    }

    private function findStuckLegacyThumbnails(): \Illuminate\Support\Collection
    {
        $dir = storage_path('app/public/thumbnails');
        if (! is_dir($dir)) return collect();

        $results = [];
        foreach (scandir($dir) ?: [] as $filename) {
            if ($filename === '.' || $filename === '..') continue;
            $path = "{$dir}/{$filename}";
            if (! is_file($path)) continue;

            // Check if it's a video thumbnail
            $video = Video::where('thumbnail', $filename)->first();
            if ($video) {
                $results[] = ['type' => 'thumbnail', 'file' => $filename, 'path' => $path, 'video_id' => $video->id, 'video' => $video, 'slide' => null];
                continue;
            }

            // Check if it's an old-format slide
            $slide = VideoSlide::where('filename', $filename)->with('video')->first();
            if ($slide && $slide->video) {
                $results[] = ['type' => 'slide', 'file' => $filename, 'path' => $path, 'video_id' => $slide->video_id, 'video' => $slide->video, 'slide' => $slide];
            }
        }

        return collect($results);
    }

    // ── Directory pruning ─────────────────────────────────────────────────────

    private function pruneAllLocalDirs(): void
    {
        // NAS-mirrored dirs
        $this->pruneEmptyDirTree(storage_path('app/users'));

        // Flat asset dirs — remove if empty
        foreach (['public/avatars', 'public/banners', 'public/thumbnails'] as $rel) {
            $path = storage_path("app/{$rel}");
            if (is_dir($path) && $this->isDirEmpty($path)) {
                @rmdir($path);
            }
        }
    }

    /**
     * Bottom-up prune: remove dirs that contain only meta.json or nothing.
     */
    private function pruneEmptyDirTree(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);
            }
        }
    }

    private function isDirEmpty(string $dir): bool
    {
        return empty(array_diff(scandir($dir) ?: [], ['.', '..']));
    }

    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';
    }
}
