<?php

namespace App\Jobs;

use App\Models\Setting;
use App\Models\Video;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;

class GenerateHlsJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $video;

    public function __construct(Video $video)
    {
        $this->video = $video;
        $this->onQueue('video-processing');
    }

    public function handle()
    {
        $video = $this->video->fresh();

        if ($video->status !== 'ready') {
            Log::warning('GenerateHlsJob: Video not ready', ['id' => $video->id]);
            return;
        }

        $sourcePath    = storage_path('app/' . $video->path);
        $nasDownloaded = null; // track a NAS-fetched local copy so we can clean it up

        if (! file_exists($sourcePath)) {
            // NAS-primary mode: file lives on NAS, download a temporary local copy
            $nas = app(\App\Services\NasSyncService::class);
            if ($nas->isEnabled()) {
                $localCopy = $nas->ensureLocalCopy($video);
                if ($localCopy) {
                    $sourcePath    = $localCopy;
                    $nasDownloaded = $localCopy;
                }
            }
            if (! file_exists($sourcePath)) {
                Log::error('GenerateHlsJob: Source file missing', ['path' => $sourcePath]);
                return;
            }
        }

        // HLS rendition lives in the song's own cache/ subfolder (regenerable, local-only —
        // never pushed to NAS). Fall back to the shared public/hls only for legacy rows
        // whose path is not in the users/ layout.
        $hlsDir  = str_starts_with((string) $video->path, 'users/')
            ? dirname($video->path) . '/cache/hls'
            : 'public/hls/' . $video->id;
        $hlsPath = storage_path('app/' . $hlsDir);

        if (is_dir($hlsPath)) {
            Storage::deleteDirectory($hlsDir);
        }
        Storage::makeDirectory($hlsDir);

        $variants = [
            ['height' => 480,  'name' => '480p',  'bitrate' => '1000k'],
            ['height' => 720,  'name' => '720p',  'bitrate' => '2500k'],
            ['height' => 1080, 'name' => '1080p', 'bitrate' => '5000k'],
        ];

        foreach ($variants as $v) {
            @mkdir($hlsPath . '/' . $v['name'], 0755, true);
        }

        try {
            $ffmpegBin  = \App\Models\Setting::ffmpegBinary();
            // Verify the GPU is actually reachable and able to encode before sending
            // the file to it; otherwise fall back to CPU so the job never hangs.
            $gpuEnabled = Setting::gpuUsable();
            $encoder    = Setting::gpuEncoder();  // h264_nvenc / hevc_nvenc / libx264
            $preset     = Setting::gpuPreset();   // p1–p7 for NVENC, fast/medium/slow for x264
            $device     = Setting::gpuDevice();   // GPU index (0, 1, …)
            $hwaccel    = Setting::gpuHwaccel();  // cuda / none

            $cmd = [escapeshellcmd($ffmpegBin)];

            // Hardware-accelerated decode when GPU is in use
            if ($gpuEnabled && $hwaccel !== 'none') {
                $cmd[] = "-hwaccel {$hwaccel}";
                $cmd[] = "-hwaccel_device {$device}";
            }

            $cmd[] = '-i ' . escapeshellarg($sourcePath);

            // One video+audio stream per variant
            $n = count($variants);
            for ($i = 0; $i < $n; $i++) {
                $cmd[] = '-map 0:v:0';
                $cmd[] = '-map 0:a:0?';
            }

            // Video codec
            $cmd[] = "-c:v {$encoder}";
            if ($gpuEnabled && str_contains($encoder, 'nvenc')) {
                $cmd[] = "-preset {$preset}";
                $cmd[] = '-rc vbr';
                $cmd[] = '-cq 23';
                $cmd[] = "-gpu {$device}";
            } else {
                $cmd[] = "-preset {$preset}";
                $cmd[] = '-crf 23';
            }
            $cmd[] = '-pix_fmt yuv420p';

            // Audio codec
            $cmd[] = '-c:a aac';
            $cmd[] = '-b:a 128k';
            $cmd[] = '-ar 48000';

            // Per-variant scale + bitrate
            for ($i = 0; $i < $n; $i++) {
                $cmd[] = "-filter:v:{$i} scale=-2:{$variants[$i]['height']}";
                $cmd[] = "-b:v:{$i} {$variants[$i]['bitrate']}";
            }

            // HLS muxer options
            $cmd[] = '-g 48';
            $cmd[] = '-sc_threshold 0';
            $cmd[] = '-f hls';
            $cmd[] = '-hls_time 6';
            $cmd[] = '-hls_list_size 0';
            $cmd[] = '-hls_flags independent_segments';
            $cmd[] = '-hls_segment_filename ' . escapeshellarg($hlsPath . '/%v/%03d.ts');
            $cmd[] = '-master_pl_name playlist.m3u8';

            $vsm = implode(' ', array_map(
                fn ($i, $v) => "v:{$i},a:{$i},name:{$v['name']}",
                array_keys($variants),
                $variants
            ));
            $cmd[] = '-var_stream_map ' . escapeshellarg($vsm);

            $cmd[] = escapeshellarg($hlsPath . '/%v/index.m3u8');

            $fullCmd = implode(' ', $cmd) . ' 2>&1';

            Log::info('GenerateHlsJob: Starting', [
                'video_id' => $video->id,
                'gpu'      => $gpuEnabled,
                'encoder'  => $encoder,
                'device'   => $gpuEnabled ? $device : 'cpu',
            ]);

            exec($fullCmd, $output, $exitCode);

            if ($exitCode !== 0) {
                $tail = implode("\n", array_slice($output, -30));
                throw new \RuntimeException("FFmpeg exited {$exitCode}:\n{$tail}");
            }

            $video->update(['has_hls' => true, 'hls_path' => $hlsDir]);

            Log::info('GenerateHlsJob: HLS generated', [
                'video_id' => $video->id,
                'variants' => array_column($variants, 'name'),
                'encoder'  => $encoder,
            ]);

            $nas = app(\App\Services\NasSyncService::class);
            if ($nas->isEnabled()) {
                if ($nasDownloaded) {
                    // NAS-primary mode: video was fetched from NAS for HLS generation.
                    // The original is already on NAS — just delete the local temp copy.
                    @unlink($nasDownloaded);
                } else {
                    // Local-storage mode: push the (compressed) file to NAS and free local disk.
                    // HLS segments stay local — per-segment SMB latency would hurt playback.
                    $nas->syncVideo($video);
                    $nas->deleteLocalVideo($video);
                    $nas->deleteLocalAssets($video);
                }
            }

        } catch (\Exception $e) {
            Log::error('GenerateHlsJob failed: ' . $e->getMessage(), ['video_id' => $video->id]);
            Storage::deleteDirectory($hlsDir);
        }
    }
}
