<?php

namespace App\Http\Controllers;

use App\Models\Playlist;
use App\Models\Video;
use App\Services\GeoIpService;
use App\Services\NasSyncService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class PlaylistController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth')->except(['index', 'show', 'showByToken', 'publicPlaylists', 'userPlaylists', 'recordShare', 'accessShare', 'ogImage']);
    }

    // List user's playlists
    public function index()
    {
        $user = Auth::user();
        $playlists = $user->playlists()->orderBy('created_at', 'desc')->get();

        return view('playlists.index', compact('playlists'));
    }

    // View a single playlist
    public function show(Request $request, Playlist $playlist)
    {
        if (! $playlist->canView(Auth::user())) {
            abort(404, 'Playlist not found');
        }

        $playlist->loadMissing('user');
        $videos = $playlist->videos()->with('user')->orderBy('position')->get();

        // Count this visit (deduped per device) after the response is sent so
        // the page render isn't blocked by the EXISTS / INSERT / UPDATE round-trip.
        dispatch(function () use ($playlist, $request) {
            $playlist->bumpViewIfNew($request);
        })->afterResponse();

        return view('playlists.show', compact('playlist', 'videos'));
    }

    // View playlist via unguessable share token (unlisted playlists)
    public function showByToken(Request $request, string $token)
    {
        $playlist = Playlist::where('share_token', $token)->firstOrFail();

        if (! $playlist->canViewViaToken(Auth::user())) {
            abort(404, 'Playlist not found');
        }

        $playlist->loadMissing('user');
        $videos = $playlist->videos()->with('user')->orderBy('position')->get();

        dispatch(function () use ($playlist, $request) {
            $playlist->bumpViewIfNew($request);
        })->afterResponse();

        return view('playlists.show', compact('playlist', 'videos'));
    }

    // Generate (or reuse) a per-user share tracking token and return the tracking URL
    public function recordShare(Playlist $playlist)
    {
        $userId = Auth::id();

        if ($userId) {
            $existing = DB::table('playlist_shares')
                ->where('playlist_id', $playlist->id)
                ->where('user_id', $userId)
                ->first();
            if ($existing) {
                return response()->json(['url' => route('playlists.accessShare', $existing->token)]);
            }
        }

        do {
            $token = Str::random(10);
        } while (DB::table('playlist_shares')->where('token', $token)->exists());

        DB::table('playlist_shares')->insert([
            'playlist_id' => $playlist->id,
            'user_id'     => $userId,
            'token'       => $token,
            'created_at'  => now(),
        ]);

        return response()->json(['url' => route('playlists.accessShare', $token)]);
    }

    // Handle a share link click: record the access, then redirect to the playlist
    public function accessShare(Request $request, string $token)
    {
        $share = DB::table('playlist_shares')->where('token', $token)->first();
        if (! $share) {
            return redirect('/');
        }

        $playlist = Playlist::find($share->playlist_id);
        if (! $playlist || ! $playlist->canViewViaToken(Auth::user())) {
            return redirect('/');
        }

        $did  = $request->cookie('_did') ?: (string) Str::uuid();
        $seen = DB::table('playlist_share_accesses')
            ->where('share_id', $share->id)
            ->where('device_id', $did)
            ->exists();

        if (! $seen) {
            $ip  = $request->header('CF-Connecting-IP')
                ?? $request->header('X-Real-IP')
                ?? $request->ip();
            $geo = GeoIpService::lookup($ip);

            DB::table('playlist_share_accesses')->insert([
                'share_id'     => $share->id,
                'device_id'    => $did,
                'ip_address'   => $ip,
                'country'      => $geo['country']      ?? null,
                'country_name' => $geo['country_name'] ?? null,
                'accessed_at'  => now(),
            ]);
        }

        // Serve the playlist's own OG metadata to social-media crawlers so previews
        // show the playlist's picture and name — not the first video's. Humans still
        // get redirected to the first track for one-tap playback.
        $ua = (string) $request->userAgent();
        $isCrawler = (bool) preg_match(
            '/facebookexternalhit|facebookcatalog|Facebot|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|TelegramBot|Pinterest|redditbot|Googlebot|bingbot|DuckDuckBot|YandexBot|Applebot|Embedly|vkShare|W3C_Validator|SkypeUriPreview/i',
            $ua
        );

        if ($isCrawler) {
            $playlist->loadMissing('user');
            $videos = $playlist->videos()->with('user')->orderBy('position')->get();
            return response()
                ->view('playlists.show', compact('playlist', 'videos'))
                ->withCookie(cookie('_did', $did, 60 * 24 * 365 * 5));
        }

        // Human share-link click counts as a playlist view (deduped per device).
        dispatch(function () use ($playlist, $request) {
            $playlist->bumpViewIfNew($request);
        })->afterResponse();

        $firstVideo = $playlist->videos()->orderBy('position')->first();
        $destination = $firstVideo
            ? route('videos.show', $firstVideo) . '?playlist=' . $playlist->share_token
            : route('playlists.showByToken', $playlist->share_token);

        return redirect($destination)
            ->withCookie(cookie('_did', $did, 60 * 24 * 365 * 5));
    }

    // Create new playlist form
    public function create()
    {
        return view('playlists.create');
    }

    // Store new playlist
    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:100',
            'description' => 'nullable|string|max:500',
            'visibility' => 'nullable|in:public,private,unlisted',
            'thumbnail' => 'nullable|image|mimes:jpeg,png,gif,webp|max:20480',
        ]);

        $playlistData = [
            'user_id' => Auth::id(),
            'name' => $request->name,
            'description' => $request->description,
            'visibility' => $request->visibility ?? 'private',
            'is_default' => false,
            'share_token' => Str::random(32),
        ];

        // Create playlist first to get ID for thumbnail naming
        $playlist = Playlist::create($playlistData);

        // Handle thumbnail upload
        if ($request->hasFile('thumbnail')) {
            $file = $request->file('thumbnail');
            $nasPath = self::pushPlaylistThumbnailToNas($file, $playlist);
            $playlist->update(['thumbnail' => $nasPath]);
        }

        // Reload playlist with thumbnail
        $playlist->refresh();

        if ($request->expectsJson() || $request->ajax()) {
            return response()->json([
                'success' => true,
                'playlist' => [
                    'id' => $playlist->id,
                    'name' => $playlist->name,
                    'visibility' => $playlist->visibility,
                    'thumbnail_url' => $playlist->thumbnail_url,
                ],
            ]);
        }

        return redirect()->route('playlists.show', $playlist)->with('success', 'Playlist created!');
    }

    // Edit playlist form
    public function edit(Playlist $playlist)
    {
        // Check ownership
        if (! $playlist->canEdit(Auth::user())) {
            abort(403, 'You do not have permission to edit this playlist.');
        }

        if (request()->expectsJson() || request()->ajax()) {
            return response()->json([
                'success' => true,
                'playlist' => [
                    'id' => $playlist->id,
                    'name' => $playlist->name,
                    'description' => $playlist->description,
                    'visibility' => $playlist->visibility,
                ],
            ]);
        }

        return view('playlists.edit', compact('playlist'));
    }

    // Update playlist
    public function update(Request $request, Playlist $playlist)
    {
        // Check ownership
        if (! $playlist->canEdit(Auth::user())) {
            abort(403, 'You do not have permission to edit this playlist.');
        }

        $request->validate([
            'name' => 'required|string|max:100',
            'description' => 'nullable|string|max:500',
            'visibility' => 'nullable|in:public,private,unlisted',
            'thumbnail' => 'nullable|image|mimes:jpeg,png,gif,webp|max:20480',
        ]);

        $updateData = [
            'name' => $request->name,
            'description' => $request->description,
            'visibility' => $request->visibility ?? 'private',
        ];

        // Thumbnail: an uploaded file always wins over remove_thumbnail. The
        // modal sometimes sends both when a user clears the preview and then
        // picks a new file — without this guard the new file would be silently
        // discarded.
        if ($request->hasFile('thumbnail')) {
            if ($playlist->thumbnail) {
                self::deletePlaylistThumbnailFromNas($playlist->thumbnail);
            }
            $file = $request->file('thumbnail');
            $updateData['thumbnail'] = self::pushPlaylistThumbnailToNas($file, $playlist);
        } elseif ($request->input('remove_thumbnail') == '1' && $playlist->thumbnail) {
            self::deletePlaylistThumbnailFromNas($playlist->thumbnail);
            $updateData['thumbnail'] = null;
        }

        $playlist->update($updateData);

        if ($request->expectsJson() || $request->ajax()) {
            return response()->json([
                'success' => true,
                'message' => 'Playlist updated!',
                'playlist' => [
                    'id' => $playlist->id,
                    'name' => $playlist->name,
                    'visibility' => $playlist->visibility,
                ],
            ]);
        }

        return redirect()->route('playlists.show', $playlist)->with('success', 'Playlist updated!');
    }

    // Delete playlist
    public function destroy(Request $request, Playlist $playlist)
    {
        // Check ownership
        if (! $playlist->canEdit(Auth::user())) {
            abort(403, 'You do not have permission to delete this playlist.');
        }

        // Don't allow deleting default playlists
        if ($playlist->is_default) {
            abort(400, 'Cannot delete default playlist.');
        }

        $playlist->delete();

        if ($request->expectsJson() || $request->ajax()) {
            return response()->json([
                'success' => true,
                'message' => 'Playlist deleted!',
            ]);
        }

        return redirect()->route('playlists.index')->with('success', 'Playlist deleted!');
    }

    // Add video to playlist
    public function addVideo(Request $request, Playlist $playlist)
    {
        // Check ownership
        if (! $playlist->canEdit(Auth::user())) {
            abort(403, 'You do not have permission to edit this playlist.');
        }

        $request->validate([
            'video_id' => 'required|exists:videos,id',
        ]);

        $video = Video::findOrFail($request->video_id);

        // Check if video can be viewed
        if (! $video->canView(Auth::user())) {
            abort(403, 'You cannot add this video to your playlist.');
        }

        $added = $playlist->addVideo($video);

        if ($request->expectsJson() || $request->ajax()) {
            return response()->json([
                'success' => true,
                'message' => $added ? 'Video added to playlist!' : 'Video is already in playlist.',
                'video_count' => $playlist->video_count,
            ]);
        }

        return back()->with('success', $added ? 'Video added to playlist!' : 'Video is already in playlist.');
    }

    /**
     * Body-based mirror of removeVideo: looks up the Video by raw numeric id
     * from the request body. Lets callers (e.g. the add-to-playlist modal) drive
     * the toggle without having to know the encoded route key for Video.
     */
    public function removeVideoByBody(Request $request, Playlist $playlist)
    {
        $request->validate(['video_id' => 'required|exists:videos,id']);
        $video = Video::findOrFail($request->video_id);
        return $this->removeVideo($request, $playlist, $video);
    }

    // Remove video from playlist
    public function removeVideo(Request $request, Playlist $playlist, Video $video)
    {
        // Check ownership
        if (! $playlist->canEdit(Auth::user())) {
            abort(403, 'You do not have permission to edit this playlist.');
        }

        $removed = $playlist->removeVideo($video);

        if ($request->expectsJson() || $request->ajax()) {
            return response()->json([
                'success' => true,
                'message' => 'Video removed from playlist.',
                'video_count' => $playlist->video_count,
            ]);
        }

        return back()->with('success', 'Video removed from playlist.');
    }

    // Reorder videos in playlist
    public function reorder(Request $request, Playlist $playlist)
    {
        // Check ownership
        if (! $playlist->canEdit(Auth::user())) {
            abort(403, 'You do not have permission to edit this playlist.');
        }

        $request->validate([
            'video_ids' => 'required|array',
            'video_ids.*' => 'integer|exists:videos,id',
        ]);

        $playlist->reorderVideos($request->video_ids);

        if ($request->expectsJson() || $request->ajax()) {
            return response()->json([
                'success' => true,
                'message' => 'Playlist reordered!',
            ]);
        }

        return back()->with('success', 'Playlist reordered!');
    }

    // Get user's playlists (for dropdown)
    public function userPlaylists()
    {
        // Handle unauthenticated users
        if (! Auth::check()) {
            return response()->json([
                'success' => true,
                'playlists' => [],
                'authenticated' => false,
            ]);
        }

        $user = Auth::user();
        $playlists = $user->playlists()->orderBy('name')->get();

        // Get video IDs for each playlist
        $playlistsWithVideoIds = $playlists->map(function ($p) {
            return [
                'id' => $p->id,
                'name' => $p->name,
                'description' => $p->description,
                'video_count' => $p->videos()->count(),
                'formatted_duration' => $p->formatted_duration,
                'is_default' => $p->is_default,
                'visibility' => $p->visibility,
                'thumbnail_url' => $p->thumbnail_url,
                'video_ids' => $p->videos()->pluck('videos.id')->toArray(),
            ];
        });

        return response()->json([
            'success' => true,
            'playlists' => $playlistsWithVideoIds,
            'authenticated' => true,
        ]);
    }

    // Quick add to Watch Later
    public function watchLater(Request $request, Video $video)
    {
        $watchLater = Playlist::getWatchLater(Auth::id());
        $added = $watchLater->addVideo($video);

        if ($request->expectsJson() || $request->ajax()) {
            return response()->json([
                'success' => true,
                'message' => $added ? 'Added to Watch Later!' : 'Already in Watch Later.',
            ]);
        }

        return back()->with('success', $added ? 'Added to Watch Later!' : 'Already in Watch Later.');
    }

    // Update watch progress
    public function updateProgress(Request $request, Playlist $playlist, Video $video)
    {
        $request->validate([
            'seconds' => 'required|integer|min:0',
        ]);

        $playlist->updateWatchProgress($video, $request->seconds);

        return response()->json([
            'success' => true,
        ]);
    }

    // Play all videos in playlist (redirect to first video with playlist context)
    public function playAll(Playlist $playlist)
    {
        if (! $playlist->canViewViaToken(Auth::user())) {
            abort(404, 'Playlist not found');
        }

        $firstVideo = $playlist->videos()->orderBy('position')->first();

        if (! $firstVideo) {
            return back()->with('error', 'Playlist is empty.');
        }

        return redirect(route('videos.show', $firstVideo) . '?playlist=' . $playlist->share_token);
    }

    // Shuffle play - redirect to random video
    public function shuffle(Playlist $playlist)
    {
        if (! $playlist->canViewViaToken(Auth::user())) {
            abort(404, 'Playlist not found');
        }

        $randomVideo = $playlist->getRandomVideo();

        if (! $randomVideo) {
            return back()->with('error', 'Playlist is empty.');
        }

        return redirect(route('videos.show', $randomVideo) . '?playlist=' . $playlist->share_token);
    }

    // ── NAS thumbnail helpers ─────────────────────────────────────────────────

    private static function nasPlaylistThumbPath(Playlist $playlist, string $ext): string
    {
        $nas      = app(\App\Services\NasSyncService::class);
        $playlist->loadMissing('user');
        $userSlug = $nas->userSlug($playlist->user);
        return "users/{$userSlug}/playlists/{$playlist->id}/thumb.{$ext}";
    }

    private static function pushPlaylistThumbnailToNas(\Illuminate\Http\UploadedFile $file, Playlist $playlist): string
    {
        $nas     = app(\App\Services\NasSyncService::class);
        $ext     = $file->getClientOriginalExtension() ?: 'jpg';
        $nasPath = self::nasPlaylistThumbPath($playlist, $ext);
        $localAbs = storage_path('app/' . $nasPath);

        // When NAS is reachable, push directly and avoid the slow timeout path.
        if ($nas->isEnabled()) {
            $tmpName  = self::generateFilename($ext);
            $file->storeAs('public/thumbnails', $tmpName);
            $tempAbs  = storage_path('app/public/thumbnails/' . $tmpName);
            $nas->mkdirp(dirname($nasPath));
            if ($nas->putFile($tempAbs, $nasPath)) {
                @unlink($tempAbs);
                return $nasPath;
            }
            // NAS push failed mid-flight — fall through to local fallback so the
            // file isn't lost. nas:auto-sync will retry when NAS comes back.
            @mkdir(dirname($localAbs), 0755, true);
            @rename($tempAbs, $localAbs);
            return $nasPath;
        }

        // NAS disabled / unreachable — keep on local disk at the same path
        // MediaController expects, so it serves directly without a NAS round-trip.
        @mkdir(dirname($localAbs), 0755, true);
        $file->move(dirname($localAbs), basename($localAbs));
        return $nasPath;
    }

    private static function deletePlaylistThumbnailFromNas(?string $nasPath): void
    {
        if (! $nasPath || ! str_starts_with($nasPath, 'users/')) return;
        try {
            app(\App\Services\NasSyncService::class)->deleteFile($nasPath);
        } catch (\Throwable) {}
    }

    // Social-preview image: resize the playlist's own thumbnail to a 1200x630 JPEG
    // (small enough for WhatsApp/Telegram/Discord previews). Falls back to a branded card.
    public function ogImage(Playlist $playlist, NasSyncService $nas)
    {
        if (! $playlist->canViewViaToken(Auth::user())) {
            abort(404);
        }

        if ($playlist->thumbnail) {
            $local = storage_path('app/' . $playlist->thumbnail);
            if (! file_exists($local)) {
                @mkdir(dirname($local), 0755, true);
                $nas->ensureLocalAsset($local, $playlist->thumbnail);
            }
            if (file_exists($local)) {
                $ext = strtolower(pathinfo($local, PATHINFO_EXTENSION));
                $src = match ($ext) {
                    'png'  => @imagecreatefrompng($local),
                    'webp' => @imagecreatefromwebp($local),
                    'gif'  => @imagecreatefromgif($local),
                    default => @imagecreatefromjpeg($local),
                };
                if ($src) {
                    $ow = imagesx($src); $oh = imagesy($src);
                    // Always output an exact 1200x630 canvas (cover-crop, no letterbox)
                    // so the served image matches the og:image:width/height we declare —
                    // a mismatch makes WhatsApp/Telegram fall back to the tiny thumbnail.
                    $cw = 1200; $ch = 630;
                    $dst = imagecreatetruecolor($cw, $ch);
                    // Cover: scale so the image fills the whole canvas, center-crop overflow
                    $scale = max($cw / $ow, $ch / $oh);
                    $sw = (int) round($cw / $scale);
                    $sh = (int) round($ch / $scale);
                    $sx = (int) round(($ow - $sw) / 2);
                    $sy = (int) round(($oh - $sh) / 2);
                    imagecopyresampled($dst, $src, 0, 0, $sx, $sy, $cw, $ch, $sw, $sh);
                    imagedestroy($src);
                    ob_start();
                    imagejpeg($dst, null, 82);
                    imagedestroy($dst);
                    $jpeg = ob_get_clean();
                    return response($jpeg, 200, [
                        'Content-Type'  => 'image/jpeg',
                        'Cache-Control' => 'public, max-age=86400',
                    ]);
                }
            }
        }

        // Branded fallback card
        $w = 1200; $h = 630;
        $img = imagecreatetruecolor($w, $h);
        $cBg    = imagecolorallocate($img, 10, 10, 10);
        $cRed   = imagecolorallocate($img, 230, 30, 30);
        $cWhite = imagecolorallocate($img, 240, 240, 240);
        $cGray  = imagecolorallocate($img, 120, 120, 120);
        imagefill($img, 0, 0, $cBg);
        imagefilledrectangle($img, 0, 0, $w, 8, $cRed);
        imagefilledrectangle($img, 0, $h - 8, $w, $h, $cRed);

        $cx = (int)($w / 2); $cy = (int)($h / 2) - 30; $r = 72;
        imagefilledellipse($img, $cx, $cy, $r * 2, $r * 2, $cRed);
        $tri = [$cx - 22, $cy - 30, $cx - 22, $cy + 30, $cx + 34, $cy];
        imagefilledpolygon($img, $tri, $cWhite);

        $fontBold   = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf';
        $fontNormal = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf';
        imagettftext($img, 22, 0, 40, 56, $cRed, $fontBold, strtoupper(config('app.name')));

        $title = $playlist->name ?: 'Playlist';
        $maxChars = 42;
        $lines = [];
        if (mb_strlen($title) > $maxChars) {
            $words = explode(' ', $title); $line = '';
            foreach ($words as $word) {
                if (mb_strlen($line . ' ' . $word) > $maxChars) { $lines[] = trim($line); $line = $word; }
                else { $line .= ($line ? ' ' : '') . $word; }
            }
            if ($line) $lines[] = trim($line);
        } else { $lines = [$title]; }
        $lines = array_slice($lines, 0, 2);
        $titleY = $cy + $r + 60;
        foreach ($lines as $i => $line) {
            $bbox = imagettfbbox(28, 0, $fontBold, $line);
            $tw = $bbox[2] - $bbox[0];
            $tx = (int)(($w - $tw) / 2);
            imagettftext($img, 28, 0, $tx, $titleY + $i * 44, $cWhite, $fontBold, $line);
        }

        $meta = $playlist->video_count . ' videos' . ($playlist->user ? ' • by ' . $playlist->user->name : '');
        $bbox = imagettfbbox(16, 0, $fontNormal, $meta);
        $tw = $bbox[2] - $bbox[0];
        $tx = (int)(($w - $tw) / 2);
        imagettftext($img, 16, 0, $tx, $titleY + count($lines) * 44 + 20, $cGray, $fontNormal, $meta);

        ob_start();
        imagejpeg($img, null, 85);
        imagedestroy($img);
        $jpeg = ob_get_clean();
        return response($jpeg, 200, [
            'Content-Type'  => 'image/jpeg',
            'Cache-Control' => 'public, max-age=86400',
        ]);
    }
}
