<?php

namespace App\Http\Controllers;

use App\Models\AuditLog;
use App\Models\Setting;
use App\Models\User;
use App\Models\Video;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class SuperAdminController extends Controller
{
    public function __construct()
    {
        $this->middleware('super_admin')->except(['exitImpersonation']);
    }

    // Manually verify a user's email
    public function verifyUser(User $user)
    {
        if ($user->email_verified_at) {
            return back()->with('error', "{$user->name} is already verified.");
        }

        $user->email_verified_at = now();
        $user->save();

        return back()->with('success', "{$user->name}'s account has been verified.");
    }

    // Start impersonating a user
    public function impersonate(User $user)
    {
        if ($user->isSuperAdmin()) {
            return back()->with('error', 'You cannot impersonate another super admin.');
        }

        $admin = \Auth::user();
        AuditLog::record('admin.impersonate', [
            'user_id'       => $admin->id,
            'user_name'     => $admin->name,
            'subject_type'  => 'User',
            'subject_id'    => (string) $user->id,
            'subject_label' => $user->name,
        ]);

        session(['impersonator_id' => \Auth::id()]);
        \Auth::loginUsingId($user->id);

        return redirect()->route('home')
            ->with('success', "You are now impersonating {$user->name}. Use the banner to exit.");
    }

    // Exit impersonation and return to original admin account
    public function exitImpersonation()
    {
        $impersonatorId = session('impersonator_id');

        if (! $impersonatorId) {
            return redirect()->route('home');
        }

        $impersonatedUser = \Auth::user();
        session()->forget('impersonator_id');
        \Auth::loginUsingId($impersonatorId);

        AuditLog::record('admin.impersonate.exit', [
            'subject_type'  => 'User',
            'subject_id'    => (string) $impersonatedUser->id,
            'subject_label' => $impersonatedUser->name,
        ]);

        return redirect()->route('admin.users')
            ->with('success', 'Impersonation ended. You are back as yourself.');
    }

    // Dashboard - Overview statistics
    public function dashboard()
    {
        $now  = now();
        $w0   = $now->copy()->subDays(7);   // start of this week window
        $w1   = $now->copy()->subDays(14);  // start of last week window

        // ── Core totals ────────────────────────────────────────────
        $totalUsers    = User::count();
        $totalVideos   = Video::count();
        $totalViews    = \DB::table('video_views')->count();
        $totalLikes    = \DB::table('video_likes')->count();
        $totalComments = \DB::table('comments')->count();

        // ── Week-over-week growth ──────────────────────────────────
        $usersThisWeek    = User::where('created_at', '>=', $w0)->count();
        $usersLastWeek    = User::whereBetween('created_at', [$w1, $w0])->count();
        $videosThisWeek   = Video::where('created_at', '>=', $w0)->count();
        $videosLastWeek   = Video::whereBetween('created_at', [$w1, $w0])->count();
        $viewsThisWeek    = \DB::table('video_views')->where('watched_at', '>=', $w0)->count();
        $viewsLastWeek    = \DB::table('video_views')->whereBetween('watched_at', [$w1, $w0])->count();
        $likesThisWeek    = \DB::table('video_likes')->where('created_at', '>=', $w0)->count();
        $likesLastWeek    = \DB::table('video_likes')->whereBetween('created_at', [$w1, $w0])->count();
        $commentsThisWeek = \DB::table('comments')->where('created_at', '>=', $w0)->count();

        $growthUsers  = $this->growthPct($usersLastWeek,  $usersThisWeek);
        $growthVideos = $this->growthPct($videosLastWeek, $videosThisWeek);
        $growthViews  = $this->growthPct($viewsLastWeek,  $viewsThisWeek);
        $growthLikes  = $this->growthPct($likesLastWeek,  $likesThisWeek);

        $stats = compact(
            'totalUsers','totalVideos','totalViews','totalLikes','totalComments',
            'usersThisWeek','videosThisWeek','viewsThisWeek','likesThisWeek','commentsThisWeek',
            'growthUsers','growthVideos','growthViews','growthLikes'
        );

        // ── 30-day daily activity (for charts) ────────────────────
        $days30 = collect(range(29, 0))->map(fn($d) => $now->copy()->subDays($d)->format('Y-m-d'));

        $rawUsers  = User::selectRaw('DATE(created_at) as d, COUNT(*) as n')
            ->where('created_at', '>=', $now->copy()->subDays(30))
            ->groupBy('d')->pluck('n', 'd');
        $rawVideos = Video::selectRaw('DATE(created_at) as d, COUNT(*) as n')
            ->where('created_at', '>=', $now->copy()->subDays(30))
            ->groupBy('d')->pluck('n', 'd');
        $rawViews  = \DB::table('video_views')
            ->selectRaw('DATE(watched_at) as d, COUNT(*) as n')
            ->where('watched_at', '>=', $now->copy()->subDays(30))
            ->groupBy('d')->pluck('n', 'd');

        $chartLabels      = $days30->map(fn($d) => date('M j', strtotime($d)))->values()->toJson();
        $chartDatesRaw    = $days30->values()->toJson();
        $chartUsersData   = $days30->map(fn($d) => $rawUsers->get($d, 0))->values()->toJson();
        $chartVideosData  = $days30->map(fn($d) => $rawVideos->get($d, 0))->values()->toJson();
        $chartViewsData   = $days30->map(fn($d) => $rawViews->get($d, 0))->values()->toJson();

        // ── Video status & visibility ──────────────────────────────
        $videosByStatus     = Video::selectRaw('status, count(*) as n')->groupBy('status')->pluck('n', 'status');
        $videosByVisibility = Video::selectRaw('visibility, count(*) as n')->groupBy('visibility')->pluck('n', 'visibility');
        $videosByType       = Video::selectRaw('type, count(*) as n')->groupBy('type')->pluck('n', 'type');

        // ── Alerts ────────────────────────────────────────────────
        $failedCount     = $videosByStatus->get('failed', 0);
        $processingCount = $videosByStatus->get('processing', 0);
        $pendingCount    = $videosByStatus->get('pending', 0);

        // ── Top content ───────────────────────────────────────────
        $topVideos = \DB::table('videos')
            ->join('users', 'videos.user_id', '=', 'users.id')
            ->select(
                'videos.id', 'videos.title', 'videos.thumbnail', 'videos.type',
                'users.name as username',
                \DB::raw('(SELECT COUNT(*) FROM video_views WHERE video_views.video_id = videos.id) as view_count'),
                \DB::raw('(SELECT COUNT(*) FROM video_likes WHERE video_likes.video_id = videos.id) as like_count')
            )
            ->where('videos.status', 'ready')
            ->orderByDesc('view_count')
            ->take(5)->get();

        $topUploaders = \DB::table('users')
            ->select('users.id','users.name','users.email','users.avatar',
                \DB::raw('COUNT(videos.id) as video_count'),
                \DB::raw('SUM((SELECT COUNT(*) FROM video_views WHERE video_views.video_id = videos.id)) as total_views')
            )
            ->leftJoin('videos', 'users.id', '=', 'videos.user_id')
            ->groupBy('users.id','users.name','users.email','users.avatar')
            ->orderByDesc('video_count')
            ->take(5)->get();

        // ── Engagement ────────────────────────────────────────────
        $readyVideos      = $videosByStatus->get('ready', 0);
        $avgViewsPerVideo = $readyVideos > 0 ? round($totalViews / $readyVideos, 1) : 0;
        $likeToViewRatio  = $totalViews > 0 ? round(($totalLikes / $totalViews) * 100, 1) : 0;
        $avgVideosPerUser = $totalUsers > 0 ? round($totalVideos / $totalUsers, 1) : 0;

        // ── Viewers by country ────────────────────────────────────
        $viewsByCountry = \DB::table('video_views')
            ->whereNotNull('country')
            ->selectRaw('country, country_name, COUNT(*) as total')
            ->groupBy('country', 'country_name')
            ->orderByDesc('total')
            ->take(20)
            ->get();

        // ── Recent ────────────────────────────────────────────────
        $recentUsers  = User::latest()->take(5)->get();
        $recentVideos = Video::with('user')->latest()->take(5)->get();

        // ── Storage ───────────────────────────────────────────────
        $disk = Storage::disk('public');

        $sizeVideos     = $this->dirSize($disk, 'videos');
        $sizeThumbnails = $this->dirSize($disk, 'thumbnails');
        $sizeAvatars    = $this->dirSize($disk, 'avatars');
        $sizeImages     = $this->dirSize($disk, 'images');
        $totalPublicSize = $sizeVideos + $sizeThumbnails + $sizeAvatars + $sizeImages;
        $videosUsagePercent = $totalPublicSize > 0 ? round(($sizeVideos / $totalPublicSize) * 100, 1) : 0;

        // Convert to MB
        $toMb = fn($b) => round($b / 1024 / 1024, 1);
        $storage = [
            'videos'     => $toMb($sizeVideos),
            'thumbnails' => $toMb($sizeThumbnails),
            'avatars'    => $toMb($sizeAvatars),
            'images'     => $toMb($sizeImages),
            'total'      => $toMb($totalPublicSize),
        ];
        $videosDirSize      = $storage['videos'];
        $totalPublicSizeMb  = $storage['total'];

        return view('admin.dashboard', compact(
            'stats',
            'chartLabels','chartDatesRaw','chartUsersData','chartVideosData','chartViewsData',
            'videosByStatus','videosByVisibility','videosByType',
            'failedCount','processingCount','pendingCount',
            'topVideos','topUploaders',
            'avgViewsPerVideo','likeToViewRatio','avgVideosPerUser',
            'recentUsers','recentVideos',
            'storage','videosDirSize','totalPublicSizeMb','videosUsagePercent',
            'viewsByCountry'
        ));
    }

    private function growthPct(int $prev, int $curr): array
    {
        if ($prev === 0) {
            return ['pct' => $curr > 0 ? 100 : 0, 'dir' => $curr > 0 ? 'up' : 'flat'];
        }
        $pct = round((($curr - $prev) / $prev) * 100, 1);
        return ['pct' => abs($pct), 'dir' => $pct >= 0 ? 'up' : 'down'];
    }

    private function dirSize($disk, string $dir): int
    {
        $size = 0;
        foreach ($disk->allFiles($dir) as $file) {
            try { $size += $disk->size($file); } catch (\Exception $e) {}
        }
        return $size;
    }

    // List all users with search/filter
    public function users(Request $request)
    {
        $query = User::query();

        // Search by name or email
        if ($request->has('search') && $request->search) {
            $search = $request->search;
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', "%{$search}%")
                  ->orWhere('email', 'like', "%{$search}%");
            });
        }

        // Filter by role
        if ($request->has('role') && $request->role) {
            $query->where('role', $request->role);
        }

        // Sort by
        $sort = $request->get('sort', 'latest');
        switch ($sort) {
            case 'oldest':
                $query->oldest();
                break;
            case 'name_asc':
                $query->orderBy('name', 'asc');
                break;
            case 'name_desc':
                $query->orderBy('name', 'desc');
                break;
            default:
                $query->latest();
        }

        $users = $query->paginate(20);
        $users->appends($request->query());

        return view('admin.users', compact('users'));
    }

    // Show edit user form
    public function editUser(User $user)
    {
        return view('admin.edit-user', compact('user'));
    }

    // Update user
    public function updateUser(Request $request, User $user)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|email|max:255|unique:users,email,' . $user->id,
            'role' => 'required|in:user,admin,super_admin',
            'new_password' => 'nullable|min:8|confirmed',
        ]);

        $data = [
            'name' => $request->name,
            'email' => $request->email,
            'role' => $request->role,
        ];

        // Update password if provided
        if ($request->new_password) {
            $data['password'] = Hash::make($request->new_password);
        }

        $user->update($data);

        return redirect()->route('admin.users')->with('success', 'User updated successfully!');
    }

    // Returns true if admin is already within the 30-min verified window
    private function adminIsVerified(): bool
    {
        $admin = auth()->user();
        if (! $admin->two_factor_enabled || ! $admin->two_factor_secret) {
            return true;
        }
        $verifiedAt = session('admin_2fa_verified_at');
        return $verifiedAt && now()->timestamp - $verifiedAt < 1800;
    }

    // Validates OTP and stamps the session on success
    private function verify2fa(Request $request): bool
    {
        $admin = auth()->user();
        if (! $admin->two_factor_enabled || ! $admin->two_factor_secret) {
            return true;
        }
        if ($this->adminIsVerified()) {
            return true;
        }
        $code = $request->input('otp_code', '');
        $google2fa = app('pragmarx.google2fa');
        if ($google2fa->verifyKey(decrypt($admin->two_factor_secret), (string) $code)) {
            session(['admin_2fa_verified_at' => now()->timestamp]);
            return true;
        }
        return false;
    }

    // Delete user
    public function deleteUser(Request $request, User $user)
    {
        // Prevent deleting yourself
        if (auth()->id() === $user->id) {
            return response()->json(['success' => false, 'message' => 'You cannot delete your own account!'], 422);
        }

        if (! $this->verify2fa($request)) {
            return response()->json(['success' => false, 'message' => 'Invalid 2FA code. Please try again.'], 422);
        }

        AuditLog::record('admin.user.deleted', [
            'subject_type'  => 'User',
            'subject_id'    => (string) $user->id,
            'subject_label' => $user->name,
            'details'       => ['email' => $user->email, 'video_count' => $user->videos->count()],
        ]);

        // Delete user's videos and associated files
        foreach ($user->videos as $video) {
            Storage::delete($video->path);
            if ($video->thumbnail) {
                Storage::delete($video->thumbnailStorageKey());
            }
        }
        $user->videos()->delete();

        // Delete user likes and views - use direct query since relationship is named 'viewers'
        \DB::table('video_likes')->where('user_id', $user->id)->delete();
        \DB::table('video_views')->where('user_id', $user->id)->delete();

        $user->delete();

        return response()->json(['success' => true, 'message' => 'User deleted successfully!']);
    }

    // List all videos
    public function videos(Request $request)
    {
        $query = Video::with('user');

        // Search by title or description
        if ($request->has('search') && $request->search) {
            $search = $request->search;
            $query->where(function ($q) use ($search) {
                $q->where('title', 'like', "%{$search}%")
                  ->orWhere('description', 'like', "%{$search}%");
            });
        }

        // Filter by status
        if ($request->has('status') && $request->status) {
            $query->where('status', $request->status);
        }

        // Filter by visibility
        if ($request->has('visibility') && $request->visibility) {
            $query->where('visibility', $request->visibility);
        }

        // Filter by type
        if ($request->has('type') && $request->type) {
            $query->where('type', $request->type);
        }

        // Sort by
        $sort = $request->get('sort', 'latest');
        switch ($sort) {
            case 'oldest':
                $query->oldest();
                break;
            case 'title_asc':
                $query->orderBy('title', 'asc');
                break;
            case 'title_desc':
                $query->orderBy('title', 'desc');
                break;
            case 'views':
                // Can't use withCount for views due to pivot table issue
                $query->latest();
                break;
            case 'likes':
                $query->withCount('likes')->orderBy('likes_count', 'desc');
                break;
            default:
                $query->latest();
        }

        $videos = $query->paginate(20);
        $videos->appends($request->query());

        return view('admin.videos', compact('videos'));
    }

    // Per-video analytics
    public function videoAnalytics(Video $video)
    {
        // ── Total view events (every watch counts) ──────────────────────────
        $totalViews    = \DB::table('video_views')->where('video_id', $video->id)->count();
        $totalLikes    = \DB::table('video_likes')->where('video_id', $video->id)->count();
        $totalComments = \DB::table('comments')->where('video_id', $video->id)->count();

        // ── Views per day – last 30 days (raw events) ───────────────────────
        $rawDaily = \DB::table('video_views')
            ->where('video_id', $video->id)
            ->where('watched_at', '>=', now()->subDays(29)->startOfDay())
            ->selectRaw('DATE(watched_at) as date, COUNT(*) as total')
            ->groupBy('date')
            ->orderBy('date')
            ->get()
            ->keyBy('date');

        $dailyLabels = [];
        $dailyViews  = [];
        for ($i = 29; $i >= 0; $i--) {
            $d = now()->subDays($i);
            $dailyLabels[] = $d->format('M d');
            $dailyViews[]  = $rawDaily[$d->format('Y-m-d')]->total ?? 0;
        }

        // ── Fetch all view records once, join user profile data ─────────────
        // Ordered newest-first so the first occurrence per viewer is their
        // most recent record (country, etc. from their latest watch).
        $allViewRecords = \DB::table('video_views')
            ->leftJoin('users', 'video_views.user_id', '=', 'users.id')
            ->where('video_views.video_id', $video->id)
            ->select(
                'video_views.id',
                'video_views.user_id',
                'video_views.ip_address',
                'video_views.country',
                'video_views.country_name',
                'video_views.watched_at',
                'users.name as viewer_name',
                'users.avatar as viewer_avatar',
                'users.birthday',
                'users.gender'
            )
            ->orderByDesc('video_views.watched_at')
            ->get();

        // ── Deduplicate to one row per unique viewer ─────────────────────────
        // Auth users  → keyed by user_id   (u_123)
        // Guest users → keyed by ip_address (i_1.2.3.4)
        $seenKeys      = [];
        $uniqueViewers = [];
        foreach ($allViewRecords as $row) {
            $key = $row->user_id ? 'u_' . $row->user_id : 'i_' . $row->ip_address;
            if (isset($seenKeys[$key])) continue;
            $seenKeys[$key]  = true;
            $uniqueViewers[] = $row;
        }

        $totalUniqueViewers = count($uniqueViewers);
        $authViewers        = count(array_filter($uniqueViewers, fn($v) => $v->user_id !== null));
        $guestViewers       = $totalUniqueViewers - $authViewers;

        // ── Countries – one count per unique viewer ──────────────────────────
        $countryMap = [];
        foreach ($uniqueViewers as $viewer) {
            if (!$viewer->country) continue;
            if (!isset($countryMap[$viewer->country])) {
                $countryMap[$viewer->country] = ['country' => $viewer->country, 'country_name' => $viewer->country_name, 'total' => 0];
            }
            $countryMap[$viewer->country]['total']++;
        }
        usort($countryMap, fn($a, $b) => $b['total'] - $a['total']);
        $viewsByCountry = collect(array_slice($countryMap, 0, 20))->map(fn($r) => (object) $r);

        // ── Age groups – one count per unique viewer ─────────────────────────
        $ageGroups = ['Under 18' => 0, '18–24' => 0, '25–34' => 0, '35–44' => 0, '45–54' => 0, '55+' => 0, 'Unknown' => 0];
        $now       = now();
        foreach ($uniqueViewers as $viewer) {
            if (!$viewer->birthday) { $ageGroups['Unknown']++; continue; }
            $age = \Carbon\Carbon::parse($viewer->birthday)->diffInYears($now);
            if ($age < 18)     $ageGroups['Under 18']++;
            elseif ($age < 25) $ageGroups['18–24']++;
            elseif ($age < 35) $ageGroups['25–34']++;
            elseif ($age < 45) $ageGroups['35–44']++;
            elseif ($age < 55) $ageGroups['45–54']++;
            else               $ageGroups['55+']++;
        }

        // ── Gender – one count per unique viewer ─────────────────────────────
        $genderCounts = ['Male' => 0, 'Female' => 0, 'Prefer not to say' => 0];
        foreach ($uniqueViewers as $viewer) {
            if ($viewer->gender === 'male')              $genderCounts['Male']++;
            elseif ($viewer->gender === 'female')        $genderCounts['Female']++;
            elseif ($viewer->gender === 'prefer_not_to_say') $genderCounts['Prefer not to say']++;
        }

        // ── Recent 20 individual view events ────────────────────────────────
        $recentViews = $allViewRecords->take(20);

        return view('admin.video-analytics', compact(
            'video', 'totalViews', 'totalUniqueViewers', 'authViewers', 'guestViewers',
            'totalLikes', 'totalComments', 'dailyLabels', 'dailyViews',
            'viewsByCountry', 'ageGroups', 'genderCounts', 'recentViews'
        ));
    }

    // Show edit video form
    public function editVideo(Video $video)
    {
        return view('admin.edit-video', compact('video'));
    }

    // Update video
    public function updateVideo(Request $request, Video $video)
    {
        $request->validate([
            'title'           => 'required|string|max:255',
            'description'     => 'nullable|string',
            'visibility'      => 'required|in:public,unlisted,private',
            'type'            => 'required|in:generic,music,match',
            'status'          => 'required|in:pending,processing,ready,failed',
            'is_shorts'       => 'nullable|boolean',
            'download_access' => 'nullable|in:disabled,everyone,registered,subscribers',
        ]);

        $oldTitle = $video->title;

        $data = $request->only(['title', 'description', 'visibility', 'type', 'status', 'is_shorts', 'download_access']);

        $video->update($data);

        if (($data['title'] ?? $oldTitle) !== $oldTitle) {
            try {
                $nas = app(\App\Services\NasSyncService::class);
                if ($nas->isEnabled()) {
                    $nas->renameVideoDir($video->fresh());
                }
            } catch (\Throwable $e) {
                \Illuminate\Support\Facades\Log::warning('NAS video dir rename failed: ' . $e->getMessage());
            }
        }

        return redirect()->route('admin.videos')->with('success', 'Video updated successfully!');
    }

    // Delete video
    public function deleteVideo(Request $request, Video $video)
    {
        if (! $this->verify2fa($request)) {
            return response()->json(['success' => false, 'message' => 'Invalid 2FA code. Please try again.'], 422);
        }

        $videoTitle = $video->title;

        AuditLog::record('admin.video.deleted', [
            'subject_type'  => 'Video',
            'subject_id'    => (string) $video->id,
            'subject_label' => $videoTitle,
            'details'       => ['owner_id' => $video->user_id, 'type' => $video->type],
        ]);

        // Delete files
        Storage::delete($video->path);
        if ($video->thumbnail) {
            Storage::delete($video->thumbnailStorageKey());
        }

        // Delete likes and views - use direct queries since relationships have timestamp issues
        \DB::table('video_likes')->where('video_id', $video->id)->delete();
        \DB::table('video_views')->where('video_id', $video->id)->delete();

        $video->delete();

        return response()->json(['success' => true, 'message' => 'Video "' . $videoTitle . '" deleted successfully!']);
    }

    /**
     * Manual cleanup orphaned videos (admin instant trigger)
     */
    public function cleanupOrphanedVideos(Request $request)
    {
        $output = [];
        $returnCode = 0;

        Artisan::call('cleanup:orphaned-videos --force', [], $output);

        Log::channel('orphaned-videos')->info('Manual cleanup triggered by super admin');

        $disk = Storage::disk('public');
        $totalPublicSize = 0;
        foreach ($disk->allFiles() as $file) {
            $totalPublicSize += $disk->size($file);
        }
        $videosDirSize = 0;
        foreach ($disk->allFiles('videos') as $file) {
            $videosDirSize += $disk->size($file);
        }

        return response()->json([
            'success' => true,
            'message' => 'Cleanup completed! Check logs for details.',
            'stats' => [
                'return_code' => $returnCode,
                'videos_dir_size_mb' => round($videosDirSize / 1024 / 1024, 1),
                'total_public_size_mb' => round($totalPublicSize / 1024 / 1024, 1),
            ],
        ]);
    }

    public function modalData(Request $request)
    {
        $resource = $request->get('resource', '');
        $limit    = min((int) $request->get('limit', 30), 100);

        switch ($resource) {

            case 'users':
                $query = User::latest();
                if ($request->date)   $query->whereDate('created_at', $request->date);
                if ($request->filter === 'week') $query->where('created_at', '>=', now()->subDays(7));
                $items = $query->take($limit)->get();
                return response()->json(['type' => 'users', 'items' => $items->map(fn($u) => [
                    'avatar' => $u->avatar_url,
                    'name'   => $u->name,
                    'email'  => $u->email,
                    'role'   => $u->role ?? 'user',
                    'joined' => $u->created_at->diffForHumans(),
                    'url'    => route('channel', $u->channel),
                ])]);

            case 'videos':
                $query = Video::with('user')->latest();
                if ($request->status)      $query->where('status',     $request->status);
                if ($request->visibility)  $query->where('visibility', $request->visibility);
                if ($request->type)        $query->where('type',       $request->type);
                if ($request->date)        $query->whereDate('created_at', $request->date);
                if ($request->uploader_id) $query->where('user_id',   $request->uploader_id);
                $items = $query->take($limit)->get();
                return response()->json(['type' => 'videos', 'items' => $items->map(fn($v) => [
                    'thumbnail' => $v->thumbnail ? asset('storage/thumbnails/'.$v->thumbnail) : null,
                    'title'     => $v->title,
                    'owner'     => $v->user->name ?? 'Unknown',
                    'status'    => $v->status,
                    'type'      => $v->type,
                    'views'     => \DB::table('video_views')->where('video_id', $v->id)->count(),
                    'uploaded'  => $v->created_at->diffForHumans(),
                    'url'       => route('videos.show', $v),
                ])]);

            case 'top_videos':
                $items = \DB::table('videos')
                    ->join('users', 'videos.user_id', '=', 'users.id')
                    ->select('videos.id', 'videos.title', 'videos.thumbnail', 'users.name as username',
                        \DB::raw('(SELECT COUNT(*) FROM video_views WHERE video_views.video_id = videos.id) as view_count'),
                        \DB::raw('(SELECT COUNT(*) FROM video_likes WHERE video_likes.video_id = videos.id) as like_count'))
                    ->orderByDesc('view_count')->take($limit)->get();
                return response()->json(['type' => 'top_videos', 'items' => $items->map(fn($v) => [
                    'thumbnail' => $v->thumbnail ? asset('storage/thumbnails/'.$v->thumbnail) : null,
                    'title'     => $v->title,
                    'owner'     => $v->username,
                    'views'     => $v->view_count,
                    'likes'     => $v->like_count,
                    'url'       => route('videos.show', Video::encodeId($v->id)),
                ])]);

            case 'views_day':
                $items = \DB::table('video_views')
                    ->join('videos', 'video_views.video_id', '=', 'videos.id')
                    ->leftJoin('users', 'video_views.user_id', '=', 'users.id')
                    ->select('videos.title', 'videos.id as video_id', 'users.name as viewer_name', 'video_views.country', 'video_views.watched_at')
                    ->when($request->date, fn($q) => $q->whereDate('video_views.watched_at', $request->date))
                    ->orderByDesc('video_views.watched_at')->take($limit)->get();
                return response()->json(['type' => 'views', 'items' => $items->map(fn($v) => [
                    'video'   => $v->title,
                    'viewer'  => $v->viewer_name ?? 'Guest',
                    'country' => $v->country ?? '—',
                    'time'    => \Carbon\Carbon::parse($v->watched_at)->diffForHumans(),
                    'url'     => route('videos.show', Video::encodeId($v->video_id)),
                ])]);

            case 'likes':
                $items = \DB::table('videos')
                    ->join('users', 'videos.user_id', '=', 'users.id')
                    ->select('videos.id', 'videos.title', 'videos.thumbnail', 'users.name as username',
                        \DB::raw('(SELECT COUNT(*) FROM video_likes WHERE video_likes.video_id = videos.id) as like_count'))
                    ->having('like_count', '>', 0)->orderByDesc('like_count')->take($limit)->get();
                return response()->json(['type' => 'top_videos', 'items' => $items->map(fn($v) => [
                    'thumbnail' => $v->thumbnail ? asset('storage/thumbnails/'.$v->thumbnail) : null,
                    'title'     => $v->title,
                    'owner'     => $v->username,
                    'views'     => 0,
                    'likes'     => $v->like_count,
                    'url'       => route('videos.show', Video::encodeId($v->id)),
                ])]);

            case 'comments':
                $items = \DB::table('comments')
                    ->join('users', 'comments.user_id', '=', 'users.id')
                    ->join('videos', 'comments.video_id', '=', 'videos.id')
                    ->select('comments.body', 'users.name as user_name', 'videos.title as video_title', 'videos.id as video_id', 'comments.created_at')
                    ->orderByDesc('comments.created_at')->take($limit)->get();
                return response()->json(['type' => 'comments', 'items' => $items->map(fn($c) => [
                    'user'  => $c->user_name,
                    'body'  => \Str::limit($c->body, 120),
                    'video' => $c->video_title,
                    'time'  => \Carbon\Carbon::parse($c->created_at)->diffForHumans(),
                    'url'   => route('videos.show', Video::encodeId($c->video_id)),
                ])]);

            case 'country_viewers':
                $items = \DB::table('video_views')
                    ->join('videos', 'video_views.video_id', '=', 'videos.id')
                    ->leftJoin('users', 'video_views.user_id', '=', 'users.id')
                    ->select('videos.title', 'videos.id as video_id', 'users.name as viewer_name', 'video_views.watched_at')
                    ->where('video_views.country', $request->country)
                    ->orderByDesc('video_views.watched_at')->take($limit)->get();
                return response()->json(['type' => 'views', 'items' => $items->map(fn($v) => [
                    'video'   => $v->title,
                    'viewer'  => $v->viewer_name ?? 'Guest',
                    'country' => $request->country,
                    'time'    => \Carbon\Carbon::parse($v->watched_at)->diffForHumans(),
                    'url'     => route('videos.show', Video::encodeId($v->video_id)),
                ])]);

            case 'uploaders':
                $items = \DB::table('users')
                    ->select('users.id', 'users.name', 'users.avatar',
                        \DB::raw('COUNT(videos.id) as video_count'),
                        \DB::raw('COALESCE(SUM((SELECT COUNT(*) FROM video_views WHERE video_views.video_id = videos.id)),0) as total_views'))
                    ->leftJoin('videos', 'users.id', '=', 'videos.user_id')
                    ->groupBy('users.id', 'users.name', 'users.avatar')
                    ->orderByDesc('video_count')->take($limit)->get();
                return response()->json(['type' => 'uploaders', 'items' => $items->map(fn($u) => [
                    'avatar' => $u->avatar ? asset('storage/avatars/'.$u->avatar) : 'https://i.pravatar.cc/40?u='.$u->id,
                    'name'   => $u->name,
                    'videos' => $u->video_count,
                    'views'  => $u->total_views,
                    'url'    => route('channel', $u->channel),
                ])]);
        }

        return response()->json(['error' => 'Unknown resource'], 400);
    }

    public function logs(Request $request)
    {
        $logFile = storage_path('logs/laravel.log');
        $lines   = [];
        $filter  = $request->get('filter', '');
        $level   = $request->get('level', '');
        $limit   = (int) $request->get('limit', 200);

        if (file_exists($logFile)) {
            $all = array_reverse(file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));
            foreach ($all as $line) {
                if ($filter && stripos($line, $filter) === false) continue;
                if ($level  && stripos($line, ".$level:") === false) continue;
                $lines[] = $line;
                if (count($lines) >= $limit) break;
            }
        }

        return view('admin.logs', compact('lines', 'filter', 'level', 'limit'));
    }

    public function auditLogs(Request $request)
    {
        $query = AuditLog::query()->latest('created_at');

        if ($request->filled('action')) {
            $query->where('action', $request->action);
        }
        if ($request->filled('user')) {
            $query->where(function ($q) use ($request) {
                $q->where('user_name', 'like', '%'.$request->user.'%')
                  ->orWhereHas('user', fn($u) => $u->where('email', 'like', '%'.$request->user.'%'));
            });
        }
        if ($request->filled('ip')) {
            $query->where('ip_address', 'like', '%'.$request->ip.'%');
        }
        if ($request->filled('subject')) {
            $query->where('subject_label', 'like', '%'.$request->subject.'%');
        }
        if ($request->filled('date_from')) {
            $query->whereDate('created_at', '>=', $request->date_from);
        }
        if ($request->filled('date_to')) {
            $query->whereDate('created_at', '<=', $request->date_to);
        }

        $logs = $query->paginate(50)->withQueryString();

        $actionTypes = AuditLog::query()
            ->select('action')
            ->distinct()
            ->orderBy('action')
            ->pluck('action');

        return view('admin.audit-logs', compact('logs', 'actionTypes'));
    }

    // ── Settings ──────────────────────────────────────────────────────────

    public function settings()
    {
        $settings = [
            'llm_enabled'         => Setting::get('llm_enabled',         'false'),
            'llm_clean_lyrics'    => Setting::get('llm_clean_lyrics',    'true'),
            'llm_decorate_lyrics' => Setting::get('llm_decorate_lyrics', 'false'),
            'llm_providers'       => json_decode((string) Setting::get('llm_providers', '[]'), true) ?: [],
            'llm_active_id'       => (string) Setting::get('llm_active_id', ''),
        ];

        return view('admin.settings', compact('settings'));
    }

    /**
     * Settings save handler — accepts partial submissions from any of the
     * separated admin pages (GPU, NAS, Backup, AI/LLM). Only updates the keys
     * that appear in the request.
     */
    public function updateSettings(Request $request)
    {
        // ── GPU section ──────────────────────────────────────────────────────
        if ($request->has('gpu_enabled')) {
            $request->validate([
                'gpu_enabled'   => 'required|in:true,false',
                'gpu_device'    => 'required|integer|min:0|max:15',
                'gpu_encoder'   => 'required|in:h264_nvenc,hevc_nvenc,libx264',
                'gpu_hwaccel'   => 'required|in:cuda,none',
                'gpu_preset'    => 'required|in:p1,p2,p3,p4,p5,p6,p7,fast,medium,slow',
                'ffmpeg_binary' => 'required|string|max:255',
            ]);

            $binary = trim($request->ffmpeg_binary) ?: '/usr/bin/ffmpeg';
            if (! file_exists($binary) || ! is_executable($binary)) {
                return back()->withErrors(['ffmpeg_binary' => "FFmpeg binary not found or not executable: {$binary}"]);
            }

            Setting::set('gpu_enabled',   $request->gpu_enabled);
            Setting::set('gpu_device',    (string) $request->gpu_device);
            Setting::set('gpu_encoder',   $request->gpu_encoder);
            Setting::set('gpu_hwaccel',   $request->gpu_hwaccel);
            Setting::set('gpu_preset',    $request->gpu_preset);
            Setting::set('ffmpeg_binary', $binary);
            Setting::flushGpuProbe();
        }

        // ── Lyrics pipeline section ──────────────────────────────────────────
        if ($request->has('lyrics_section')) {
            foreach ([
                'lyrics_enabled',                 // master switch
                'lyrics_use_description',         // align to description text
                'lyrics_vad_enabled',             // Silero VAD filter
                'lyrics_vocal_region_gapfill',    // snap gap-filled lines to vocal regions
                'lyrics_demucs_enabled',          // vocal isolation (Demucs)
                'lyrics_llm_decorate',            // post-bake emojis via LLM
            ] as $k) {
                Setting::set($k, $request->input($k, 'false') === 'true' ? 'true' : 'false');
            }
        }

        // ── AI / LLM section ─────────────────────────────────────────────────
        if ($request->has('llm_section')) {
            Setting::set('llm_enabled',         $request->input('llm_enabled', 'false') === 'true' ? 'true' : 'false');
            Setting::set('llm_clean_lyrics',    $request->input('llm_clean_lyrics', 'false') === 'true' ? 'true' : 'false');
            Setting::set('llm_decorate_lyrics', $request->input('llm_decorate_lyrics', 'false') === 'true' ? 'true' : 'false');
            $this->saveLlmProviders($request);
        }

        return back()->with('success', 'Settings saved.');
    }

    /**
     * Probe an LLM provider endpoint: verify the connection and list
     * available models. Used by the AI / LLM settings page.
     *
     * Accepts kind / endpoint / api_key from the form, plus an optional
     * provider id so we can fall back to the saved key when the admin
     * left the password field blank (placeholder ••••••••).
     */
    public function llmProviderTest(Request $request)
    {
        $kind     = (string) $request->input('kind', 'ollama');
        $endpoint = trim((string) $request->input('endpoint', '')) ?: self::defaultEndpoint($kind);
        $endpoint = rtrim($endpoint, '/');
        $apiKey   = (string) $request->input('api_key', '');
        $id       = (string) $request->input('id', '');

        if ($apiKey === '' && $id !== '') {
            $providers = json_decode((string) Setting::get('llm_providers', '[]'), true) ?: [];
            foreach ($providers as $p) {
                if (($p['id'] ?? '') === $id) {
                    $apiKey = (string) ($p['api_key'] ?? '');
                    break;
                }
            }
        }

        if (! in_array($kind, ['ollama', 'anthropic', 'openai'], true)) {
            return response()->json(['ok' => false, 'message' => 'Unknown provider kind.'], 422);
        }

        if ($kind !== 'ollama' && $apiKey === '') {
            return response()->json(['ok' => false, 'message' => 'API key required for ' . $kind . '.'], 422);
        }

        try {
            $models = match ($kind) {
                'ollama'    => $this->fetchOllamaModels($endpoint),
                'anthropic' => $this->fetchAnthropicModels($endpoint, $apiKey),
                'openai'    => $this->fetchOpenAIModels($endpoint, $apiKey),
            };
        } catch (\Throwable $e) {
            return response()->json(['ok' => false, 'message' => $e->getMessage()]);
        }

        sort($models, SORT_NATURAL | SORT_FLAG_CASE);
        return response()->json([
            'ok'     => true,
            'count'  => count($models),
            'models' => $models,
        ]);
    }

    private function fetchOllamaModels(string $endpoint): array
    {
        $resp = \Illuminate\Support\Facades\Http::timeout(10)->acceptJson()->get($endpoint . '/api/tags');
        if (! $resp->successful()) {
            throw new \RuntimeException('Ollama returned HTTP ' . $resp->status());
        }
        $j = $resp->json();
        return array_values(array_filter(array_map(
            fn ($m) => (string) ($m['name'] ?? ''),
            $j['models'] ?? []
        )));
    }

    private function fetchAnthropicModels(string $endpoint, string $apiKey): array
    {
        $resp = \Illuminate\Support\Facades\Http::timeout(15)->withHeaders([
            'x-api-key'         => $apiKey,
            'anthropic-version' => '2023-06-01',
        ])->get($endpoint . '/v1/models');
        if (! $resp->successful()) {
            $body = $resp->json();
            throw new \RuntimeException($body['error']['message'] ?? ('HTTP ' . $resp->status()));
        }
        $j = $resp->json();
        return array_values(array_filter(array_map(
            fn ($m) => (string) ($m['id'] ?? ''),
            $j['data'] ?? []
        )));
    }

    private function fetchOpenAIModels(string $endpoint, string $apiKey): array
    {
        $resp = \Illuminate\Support\Facades\Http::timeout(15)
            ->withToken($apiKey)->acceptJson()
            ->get($endpoint . '/v1/models');
        if (! $resp->successful()) {
            $body = $resp->json();
            throw new \RuntimeException($body['error']['message'] ?? ('HTTP ' . $resp->status()));
        }
        $j = $resp->json();
        return array_values(array_filter(array_map(
            fn ($m) => (string) ($m['id'] ?? ''),
            $j['data'] ?? []
        )));
    }

    public function lyrics()
    {
        $settings = [
            'lyrics_enabled'              => Setting::get('lyrics_enabled',              'true'),
            'lyrics_use_description'      => Setting::get('lyrics_use_description',      'true'),
            'lyrics_vad_enabled'          => Setting::get('lyrics_vad_enabled',          'true'),
            'lyrics_vocal_region_gapfill' => Setting::get('lyrics_vocal_region_gapfill', 'true'),
            'lyrics_demucs_enabled'       => Setting::get('lyrics_demucs_enabled',       'false'),
            'lyrics_llm_decorate'         => Setting::get('lyrics_llm_decorate',         Setting::get('llm_decorate_lyrics', 'false')),
        ];
        return view('admin.lyrics', compact('settings'));
    }

    public function gpu()
    {
        $settings = [
            'gpu_enabled'   => Setting::get('gpu_enabled',   'true'),
            'gpu_device'    => Setting::get('gpu_device',    '0'),
            'gpu_encoder'   => Setting::get('gpu_encoder',   'h264_nvenc'),
            'gpu_hwaccel'   => Setting::get('gpu_hwaccel',   'cuda'),
            'gpu_preset'    => Setting::get('gpu_preset',    'p4'),
            'ffmpeg_binary' => Setting::get('ffmpeg_binary', config('ffmpeg.ffmpeg', '/usr/bin/ffmpeg')),
        ];

        $gpus       = $this->probeGpus();
        $nvencWorks = $this->probeNvenc();

        return view('admin.gpu', compact('settings', 'gpus', 'nvencWorks'));
    }

    public function backup()
    {
        return view('admin.backup');
    }

    public function detectGpu()
    {
        return response()->json(['gpus' => $this->probeGpus()]);
    }

    /**
     * Persist the LLM provider list from the multi-provider form. Each row
     * carries id / name / kind (ollama|anthropic|openai) / endpoint / model /
     * api_key. An empty api_key means "keep the previously stored value" so the
     * admin doesn't have to retype it on every save.
     */
    private function saveLlmProviders(Request $request): void
    {
        $existing = collect(json_decode((string) Setting::get('llm_providers', '[]'), true) ?: [])
            ->keyBy(fn ($p) => $p['id'] ?? '');

        $kinds = ['ollama', 'anthropic', 'openai'];
        $rows  = (array) $request->input('providers', []);
        $out   = [];

        foreach ($rows as $row) {
            $name = trim((string) ($row['name'] ?? ''));
            $kind = (string) ($row['kind'] ?? 'ollama');
            if (! in_array($kind, $kinds, true)) $kind = 'ollama';
            if ($name === '') continue;

            $id        = (string) ($row['id'] ?? '') ?: (string) \Illuminate\Support\Str::uuid();
            $endpoint  = trim((string) ($row['endpoint'] ?? '')) ?: self::defaultEndpoint($kind);
            $model     = trim((string) ($row['model'] ?? ''));
            $apiKeyIn  = (string) ($row['api_key'] ?? '');
            // Blank input → keep the previously-stored key for this id (admin
            // didn't retype it). Non-blank → use the new value verbatim.
            $apiKey = $apiKeyIn !== '' ? $apiKeyIn : (string) ($existing[$id]['api_key'] ?? '');

            $out[] = [
                'id'       => $id,
                'name'     => $name,
                'kind'     => $kind,
                'endpoint' => $endpoint,
                'model'    => $model,
                'api_key'  => $apiKey,
            ];
        }

        Setting::set('llm_providers', json_encode($out, JSON_UNESCAPED_UNICODE));

        $activeId = trim((string) $request->input('llm_active_id', ''));
        $validIds = array_column($out, 'id');
        if ($activeId !== '' && in_array($activeId, $validIds, true)) {
            Setting::set('llm_active_id', $activeId);
        } elseif (count($validIds) === 1) {
            Setting::set('llm_active_id', $validIds[0]);
        } elseif (! in_array((string) Setting::get('llm_active_id', ''), $validIds, true)) {
            Setting::set('llm_active_id', '');
        }
    }

    private static function defaultEndpoint(string $kind): string
    {
        return match ($kind) {
            'anthropic' => 'https://api.anthropic.com',
            'openai'    => 'https://api.openai.com',
            default     => 'http://localhost:11434',
        };
    }

    private function probeGpus(): array
    {
        $gpus = [];
        exec(
            'nvidia-smi --query-gpu=index,name,memory.total,memory.free,utilization.gpu,temperature.gpu,driver_version'
            . ' --format=csv,noheader,nounits 2>/dev/null',
            $lines, $exit
        );
        if ($exit !== 0 || empty($lines)) return $gpus;

        foreach ($lines as $line) {
            $parts = array_map('trim', explode(',', $line));
            if (count($parts) < 7) continue;
            $gpus[] = [
                'index'      => (int)   $parts[0],
                'name'       => $parts[1],
                'mem_total'  => (int)   $parts[2],
                'mem_free'   => (int)   $parts[3],
                'util'       => (int)   $parts[4],
                'temp'       => (int)   $parts[5],
                'driver'     => $parts[6],
            ];
        }
        return $gpus;
    }

    /**
     * Quick smoke-test: encode one frame with h264_nvenc and return true if it succeeds.
     * This catches CUDA compat / driver-version mismatches that nvidia-smi can't detect.
     */
    private function probeNvenc(): bool
    {
        // Single source of truth lives on the Setting model; force the NVENC encoder so the
        // admin indicator always reflects GPU capability regardless of the configured encoder.
        return Setting::probeGpu('h264_nvenc');
    }

    public function nasStorage()
    {
        $nodes = config('nas-file-manager.schema', []);
        $settings = [
            'nas_sync_enabled' => Setting::get('nas_sync_enabled', 'false'),
        ];
        return view('admin.nas-storage', compact('nodes', 'settings'));
    }

    public function nasDelete(Request $request)
    {
        $path = trim($request->input('path', ''));
        $type = $request->input('type', 'dir');

        if ($path === '') {
            return response()->json(['success' => false, 'message' => 'Path is required.'], 422);
        }

        $nas = app(\App\Services\NasSyncService::class);

        if (! $nas->isEnabled()) {
            return response()->json(['success' => false, 'message' => 'NAS not enabled.'], 422);
        }

        try {
            if ($type === 'dir') {
                $nas->deleteNasTree($path);
            } else {
                $nas->deleteFile($path);
            }
            return response()->json(['success' => true]);
        } catch (\Throwable $e) {
            return response()->json(['success' => false, 'message' => $e->getMessage()]);
        }
    }

    public function nasRepair(Request $request)
    {
        $nas = app(\App\Services\NasSyncService::class);

        if (! $nas->isEnabled()) {
            return response()->json(['success' => false, 'message' => 'NAS sync is not enabled.'], 422);
        }

        // ── Collect stuck items ───────────────────────────────────────────────
        $stuckVideos  = $this->collectStuckVideos();
        $stuckAvatars = $this->collectStuckAvatars();
        $stuckBanners = $this->collectStuckBanners();
        $stuckThumbs  = $this->collectStuckLegacyThumbnails();
        $nasOrphans   = $nas->scanNasOrphans();

        $totalStuck = $stuckVideos->count() + $stuckAvatars->count()
                    + $stuckBanners->count() + $stuckThumbs->count()
                    + count($nasOrphans);

        // Scan-only mode ───────────────────────────────────────────────────────
        if ($request->boolean('scan_only')) {
            $details = [];
            foreach ($stuckVideos as $item) {
                $details[] = "[video] #{$item['video']->id} {$item['video']->title}: " . implode(', ', $item['files']);
            }
            foreach ($stuckAvatars as $item) {
                $details[] = "[avatar] {$item['user']->username}: {$item['file']}";
            }
            foreach ($stuckBanners as $item) {
                $details[] = "[banner] {$item['user']->username}: {$item['file']}";
            }
            foreach ($stuckThumbs as $item) {
                $details[] = "[{$item['type']}] {$item['file']} (video #{$item['video_id']})";
            }
            foreach ($nasOrphans as $orphan) {
                $label = $orphan['video_id'] ? "video #{$orphan['video_id']}" : 'no meta.json';
                $details[] = "[nas-orphan] {$orphan['dir']} ({$label} — not in DB)";
            }

            $cacheBytes = $nas->nasCacheSize();
            if ($cacheBytes > 0) {
                $cacheMb = round($cacheBytes / 1048576, 1);
                $details[] = "[stream-cache] {$cacheMb} MB of on-demand video cache (safe to clear)";
                $totalStuck++;
            }

            return response()->json(['stuck' => $totalStuck, 'details' => $details]);
        }

        // Repair mode ─────────────────────────────────────────────────────────
        $repaired = 0;
        $failed   = 0;
        $details  = [];

        foreach ($stuckVideos as $item) {
            $video = $item['video'];
            try {
                $nas->syncVideo($video);
                $nas->deleteLocalAssets($video);
                if ($video->hls_path || $video->type === 'music') $nas->deleteLocalVideo($video);
                $nas->pruneLocalVideoDir($video);
                $repaired++;
                $details[] = "✓ [video] #{$video->id}: {$video->title}";
                \Log::info("nas:repair: fixed video #{$video->id}");
            } catch (\Throwable $e) {
                $failed++;
                $details[] = "✗ [video] #{$video->id}: {$e->getMessage()}";
                \Log::error("nas:repair: failed video #{$video->id}: " . $e->getMessage());
            }
        }

        foreach ($stuckAvatars as $item) {
            try {
                $nas->syncAvatar($item['user'], $item['path']);
                $nas->deleteLocalAvatar($item['user']);
                $repaired++;
                $details[] = "✓ [avatar] {$item['user']->username}";
            } catch (\Throwable $e) {
                $failed++;
                $details[] = "✗ [avatar] {$item['user']->username}: {$e->getMessage()}";
                \Log::error("nas:repair: failed avatar user#{$item['user']->id}: " . $e->getMessage());
            }
        }

        foreach ($stuckBanners as $item) {
            try {
                $nas->syncCover($item['user'], $item['path']);
                $nas->deleteLocalBanner($item['user']);
                $repaired++;
                $details[] = "✓ [banner] {$item['user']->username}";
            } catch (\Throwable $e) {
                $failed++;
                $details[] = "✗ [banner] {$item['user']->username}: {$e->getMessage()}";
                \Log::error("nas:repair: failed banner user#{$item['user']->id}: " . $e->getMessage());
            }
        }

        foreach ($stuckThumbs as $item) {
            try {
                if ($item['type'] === 'thumbnail' && $item['video']) {
                    $nas->syncVideo($item['video']);
                } elseif ($item['type'] === 'slide' && $item['slide'] && $item['video']) {
                    $dir = $nas->resolveVideoDir($item['video']);
                    $ext = pathinfo($item['path'], PATHINFO_EXTENSION) ?: 'jpg';
                    $nas->mkdirp("{$dir}/slides");
                    $nas->putFile($item['path'], "{$dir}/slides/{$item['slide']->position}.{$ext}");
                }
                @unlink($item['path']);
                $repaired++;
                $details[] = "✓ [{$item['type']}] {$item['file']}";
            } catch (\Throwable $e) {
                $failed++;
                $details[] = "✗ [{$item['type']}] {$item['file']}: {$e->getMessage()}";
                \Log::error("nas:repair: failed legacy thumb {$item['file']}: " . $e->getMessage());
            }
        }

        // Delete NAS orphan folders
        foreach ($nasOrphans as $orphan) {
            try {
                $nas->deleteNasTree($orphan['dir']);
                $repaired++;
                $details[] = "✓ [nas-orphan] deleted {$orphan['dir']}";
                \Log::info('nas:repair: deleted NAS orphan', ['dir' => $orphan['dir']]);
            } catch (\Throwable $e) {
                $failed++;
                $details[] = "✗ [nas-orphan] {$orphan['dir']}: {$e->getMessage()}";
                \Log::error('nas:repair: failed to delete NAS orphan', ['dir' => $orphan['dir'], 'error' => $e->getMessage()]);
            }
        }

        // Evict NAS stream cache (24h TTL by default)
        $evicted = $nas->clearNasCache(24);
        if ($evicted > 0) {
            $details[] = "✓ [stream-cache] evicted {$evicted} cached file(s)";
            $repaired += $evicted;
        }

        $this->pruneLocalStorageDirs();

        if ($totalStuck === 0) {
            return response()->json([
                'success' => true,
                'message' => 'Nothing to repair — no stuck local files and no NAS orphans found.',
                'repaired' => 0, 'failed' => 0, 'details' => [],
            ]);
        }

        return response()->json([
            'success' => $failed === 0,
            'message' => $failed === 0
                ? "Repaired {$repaired} item(s) successfully."
                : "Repaired {$repaired}, failed {$failed} — check logs.",
            'repaired' => $repaired,
            'failed'   => $failed,
            'details'  => $details,
        ]);
    }

    private function collectStuckVideos(): \Illuminate\Support\Collection
    {
        return \App\Models\Video::with(['user', 'slides'])->get()
            ->filter(fn ($v) => str_starts_with($v->path, 'users/'))
            ->map(function ($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 collectStuckAvatars(): \Illuminate\Support\Collection
    {
        $results = collect();

        // Legacy flat dir
        $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 = \App\Models\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)), '/');
                $user = \App\Models\User::where('avatar', $relPath)->first();
                if ($user) {
                    $results->push(['user' => $user, 'file' => basename($path), 'path' => $path]);
                }
            }
        }

        return $results;
    }

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

        // Legacy flat dir
        $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 = \App\Models\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 = \App\Models\User::where('banner', $relPath)->first();
                if ($user) {
                    $results->push(['user' => $user, 'file' => basename($path), 'path' => $path]);
                }
            }
        }

        return $results;
    }

    private function collectStuckLegacyThumbnails(): \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;

            $video = \App\Models\Video::where('thumbnail', $filename)->first();
            if ($video) {
                $results[] = ['type' => 'thumbnail', 'file' => $filename, 'path' => $path, 'video_id' => $video->id, 'video' => $video, 'slide' => null];
                continue;
            }
            $slide = \App\Models\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);
    }

    private function pruneLocalStorageDirs(): void
    {
        // NAS-mirrored tree
        $nasRoot = storage_path('app/users');
        if (is_dir($nasRoot)) {
            $iter = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($nasRoot, \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);
                }
            }
        }

        // Flat asset dirs — remove if empty
        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);
            }
        }
    }

    // ── NAS Disable Flow ──────────────────────────────────────────────────

    public function nasDisable(Request $request)
    {
        $mode = $request->input('mode'); // 'migrate' or 'fresh'

        if ($mode === 'migrate') {
            // Reset progress cache, dispatch job
            \Cache::put('nas_disable_progress', json_encode([
                'current' => 0, 'total' => 0,
                'phase' => 'Starting...', 'done' => false, 'error' => null,
            ]), 3600);
            \App\Jobs\NasToLocalMigrationJob::dispatch()
                ->onQueue('video-processing')
                ->onConnection('database');
            return response()->json(['ok' => true]);
        }

        if ($mode === 'fresh') {
            // Truncate all media tables, reset user avatars/banners, disable NAS
            $tables = [
                'videos','video_slides','video_likes','video_views','video_shares',
                'video_downloads','playlist_videos','playlists','comments','comment_likes',
                'posts','post_images','post_reactions','post_videos',
                'coach_reviews','match_rounds','match_points',
                'share_accesses','playlist_share_accesses','notifications',
            ];
            foreach ($tables as $t) {
                \DB::table($t)->delete();
            }
            \DB::table('users')->update(['avatar' => null, 'banner' => null]);
            Setting::set('nas_sync_enabled', 'false');
            app(\App\Services\NasSyncService::class)->flushReachabilityCache();
            AuditLog::record('admin.nas_disabled_fresh');
            return response()->json(['ok' => true]);
        }

        return response()->json(['ok' => false, 'message' => 'Invalid mode'], 422);
    }

    public function nasMigrateProgress()
    {
        $raw = \Cache::get('nas_disable_progress');
        if (! $raw) return response()->json(['done' => false, 'current' => 0, 'total' => 0, 'phase' => 'Not started']);
        return response()->json(json_decode($raw, true));
    }

    public function backupUsersSettings()
    {
        $users = \DB::table('users')->get()->map(function ($u) {
            return (array) $u;
        })->toArray();

        $settings = \DB::table('settings')->get()->map(function ($s) {
            return (array) $s;
        })->toArray();

        $payload = json_encode([
            'version'    => '1.0',
            'exported_at' => now()->toIso8601String(),
            'users'      => $users,
            'settings'   => $settings,
        ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

        return response($payload, 200, [
            'Content-Type'        => 'application/json',
            'Content-Disposition' => 'attachment; filename="takeone-backup-' . now()->format('Ymd-His') . '.json"',
        ]);
    }

    public function restoreUsersSettings(Request $request)
    {
        $request->validate(['backup' => 'required|file|mimes:json|max:10240']);

        $content = file_get_contents($request->file('backup')->getRealPath());
        $data    = json_decode($content, true);

        if (! isset($data['users']) || ! isset($data['settings'])) {
            return back()->with('toast_error', 'Invalid backup file.');
        }

        // Restore settings
        foreach ($data['settings'] as $row) {
            \DB::table('settings')->updateOrInsert(
                ['key' => $row['key']],
                ['key' => $row['key'], 'value' => $row['value']]
            );
        }

        // Restore users (upsert by email)
        $restored = 0;
        foreach ($data['users'] as $row) {
            unset($row['id']); // let DB assign new IDs to avoid PK conflicts
            \DB::table('users')->updateOrInsert(
                ['email' => $row['email']],
                $row
            );
            $restored++;
        }

        AuditLog::record('admin.backup_restored', ['users' => $restored]);
        return back()->with('toast_success', "Backup restored: {$restored} users + settings.");
    }
}
