<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Video extends Model
{
    protected $fillable = [
        'user_id',
        'title',
        'description',
        'filename',
        'path',
        'thumbnail',
        'duration',
        'size',
        'mime_type',
        'orientation',
        'width',
        'height',
        'status',
        'visibility',
        'type',
        'is_shorts',
        'has_hls',
        'hls_path',
        'download_access',
        'download_count',
        'share_count',
        'share_token',
        'slideshow_video_path',
        'language',
    ];

    protected $casts = [
        'duration' => 'integer',
        'size' => 'integer',
        'width' => 'integer',
        'height' => 'integer',
        'is_shorts' => 'boolean',
        'has_hls' => 'boolean',
    ];

    // Relationships
    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function likes()
    {
        return $this->belongsToMany(User::class, 'video_likes')->withTimestamps();
    }

    public function viewers()
    {
        return $this->belongsToMany(User::class, 'video_views')
            ->withPivot('watched_at')
            ->withTimestamps();
    }

    public function slides()
    {
        return $this->hasMany(VideoSlide::class)->orderBy('position');
    }

    public function audioTracks()
    {
        return $this->hasMany(\App\Models\VideoAudioTrack::class)->orderBy('id');
    }

    public function hasSlideshow(): bool
    {
        return $this->slides()->count() > 1;
    }

    /**
     * Resolve the slide list for a given audio track, applying the sharing rule:
     *   1. Slides explicitly owned by this track (audio_track_id = $trackId).
     *   2. Slides owned by the primary (audio_track_id IS NULL = song-wide / legacy).
     *   3. Slides owned by any other track (first track in id order).
     *   4. Empty (caller falls back to cover image).
     *
     * Pass `null` for the primary audio (the one stored on the videos row).
     *
     * @return \Illuminate\Support\Collection<int,\App\Models\VideoSlide>
     */
    public function slidesForTrack(?int $audioTrackId)
    {
        $all = $this->slides; // eager-loaded collection of every slide

        if ($audioTrackId !== null) {
            $own = $all->where('audio_track_id', $audioTrackId)->values();
            if ($own->isNotEmpty()) return $own;
        }

        // Primary / song-wide bucket (audio_track_id IS NULL).
        $primary = $all->whereNull('audio_track_id')->values();
        if ($primary->isNotEmpty()) return $primary;

        // Borrow from the first track (by id) that has any.
        $byTrack = $all->whereNotNull('audio_track_id')->groupBy('audio_track_id');
        if ($byTrack->isNotEmpty()) {
            $firstTrackId = $byTrack->keys()->sort()->first();
            return $byTrack[$firstTrackId]->values();
        }

        return collect();
    }

    // ── Local filesystem path helpers ─────────────────────────────────────────

    /**
     * Absolute path to the video file on local disk.
     * Works for both old flat paths ("public/videos/…") and new NAS-mirrored paths ("users/…").
     */
    public function localVideoPath(): string
    {
        return storage_path('app/' . $this->path);
    }

    /**
     * Absolute path to the thumbnail on local disk.
     * Old format: storage/app/public/thumbnails/{filename}
     * New format: storage/app/{relative-path}  (path contains a slash, e.g. users/…/thumb.jpg)
     */
    public function localThumbnailPath(): ?string
    {
        if (! $this->thumbnail) return null;
        return str_contains($this->thumbnail, '/')
            ? storage_path('app/' . $this->thumbnail)
            : storage_path('app/public/thumbnails/' . $this->thumbnail);
    }

    /**
     * Storage::delete()-compatible key for the thumbnail.
     */
    public function thumbnailStorageKey(): ?string
    {
        if (! $this->thumbnail) return null;
        return str_contains($this->thumbnail, '/')
            ? $this->thumbnail
            : 'public/thumbnails/' . $this->thumbnail;
    }

    // Accessors
    public function getUrlAttribute()
    {
        return asset('storage/videos/'.$this->filename);
    }

    public function getThumbnailUrlAttribute(): ?string
    {
        if ($this->thumbnail) {
            return route('media.thumbnail', $this->thumbnail);
        }

        return null;
    }

    // Check if video is liked by user
    public function isLikedBy($user)
    {
        if (! $user) {
            return false;
        }

        return $this->likes()->where('user_id', $user->id)->exists();
    }

    // Get like count
    public function getLikeCountAttribute()
    {
        return $this->likes()->count();
    }

    // Get view count - use direct query to avoid timestamp issues
    public function getViewCountAttribute()
    {
        return \DB::table('video_views')->where('video_id', $this->id)->count();
    }

    // ── Short opaque URL encoding ─────────────────────────────────────
    // 3-round Feistel cipher over 30-bit space → base62.
    // Consecutive IDs produce completely different output. Pure PHP, no extensions.
    private const URL_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

    private static function feistelRound(int $n, int $round): int
    {
        // Deterministic pseudo-random function per round (no extension needed)
        $keys = [0x2D4F8A1B, 0x7C3E6912, 0xA5B2D083];
        return (int)(($n * $keys[$round % 3] + ($round * 0x9E3779B9)) & 0x7FFF);
    }

    public static function encodeId(int $id): string
    {
        // Split 30-bit id into two 15-bit halves
        $L = ($id >> 15) & 0x7FFF;
        $R = $id & 0x7FFF;
        // 3 Feistel rounds
        for ($i = 0; $i < 3; $i++) {
            $tmp = $L ^ self::feistelRound($R, $i);
            $L   = $R;
            $R   = $tmp;
        }
        $n    = (($L & 0x7FFF) << 15) | ($R & 0x7FFF);
        $base = strlen(self::URL_ALPHABET);
        $out  = '';
        do {
            $out = self::URL_ALPHABET[$n % $base] . $out;
            $n   = intdiv($n, $base);
        } while ($n > 0);
        // Pad to fixed length so all URLs look uniform
        return str_pad($out, 5, '0', STR_PAD_LEFT);
    }

    public static function decodeId(string $encoded): ?int
    {
        $base = strlen(self::URL_ALPHABET);
        $n    = 0;
        for ($i = 0, $len = strlen($encoded); $i < $len; $i++) {
            $pos = strpos(self::URL_ALPHABET, $encoded[$i]);
            if ($pos === false) return null;
            $n = $n * $base + $pos;
        }
        // Reverse Feistel
        $L = ($n >> 15) & 0x7FFF;
        $R = $n & 0x7FFF;
        for ($i = 2; $i >= 0; $i--) {
            $tmp = $R ^ self::feistelRound($L, $i);
            $R   = $L;
            $L   = $tmp;
        }
        return (($L & 0x7FFF) << 15) | ($R & 0x7FFF);
    }

    // Route model binding — use encoded ID in URLs
    public function getRouteKey()
    {
        return self::encodeId($this->id);
    }

    public function getRouteKeyName()
    {
        return 'id';
    }

    public function resolveRouteBinding($value, $field = null)
    {
        $realId = self::decodeId((string) $value);
        if ($realId === null || $realId <= 0) {
            abort(404);
        }
        return $this->where('id', $realId)->firstOrFail();
    }

    // All share URLs use the unguessable token route
    public function getShareUrlAttribute()
    {
        return route('videos.showByToken', $this->share_token);
    }

    // Get formatted duration (e.g., "1:30" or "0:45" for shorts)
    public function getFormattedDurationAttribute()
    {
        if (! $this->duration) {
            return null;
        }

        $minutes = floor($this->duration / 60);
        $seconds = $this->duration % 60;

        return sprintf('%d:%02d', $minutes, $seconds);
    }

    // Get Shorts badge
    public function getShortsBadgeAttribute()
    {
        if ($this->isShorts()) {
            return [
                'icon' => 'bi-collection-play-fill',
                'label' => 'SHORTS',
                'color' => '#ff0000',
            ];
        }

        return null;
    }

    // Visibility helpers
    public function isPublic()
    {
        return $this->visibility === 'public';
    }

    public function isUnlisted()
    {
        return $this->visibility === 'unlisted';
    }

    public function isPrivate()
    {
        return $this->visibility === 'private';
    }

    // Check if user can view this video
    public function canView($user = null)
    {
        // Owner can always view
        if ($user && $this->user_id === $user->id) {
            return true;
        }

        // Public and unlisted videos can be viewed by anyone
        return in_array($this->visibility, ['public', 'unlisted']);
    }

    // Check if video is shareable
    public function isShareable()
    {
        // Only public and unlisted videos are shareable
        return in_array($this->visibility, ['public', 'unlisted']);
    }

    // Scope for public videos (home page, search)
    public function scopePublic($query)
    {
        return $query->where('visibility', 'public');
    }

    // Scope for videos visible to a specific user
    public function scopeVisibleTo($query, $user = null)
    {
        if ($user) {
            return $query->where(function ($q) use ($user) {
                $q->where('visibility', '!=', 'private')
                    ->orWhere('user_id', $user->id);
            });
        }

        return $query->where('visibility', '!=', 'private');
    }

    // Video type helpers
    public function getTypeIconAttribute()
    {
        return match ($this->type) {
            'music' => 'bi-music-note',
            'match' => 'bi-trophy',
            default => 'bi-film',
        };
    }

    public function getTypeSymbolAttribute()
    {
        return match ($this->type) {
            'music' => '🎵',
            'match' => '🏆',
            default => '🎬',
        };
    }

    public function isGeneric()
    {
        return $this->type === 'generic';
    }

    public function isMusic()
    {
        return $this->type === 'music';
    }

    public function isMatch()
    {
        return $this->type === 'match';
    }

    public function isAudioOnly(): bool
    {
        $ext = strtolower(pathinfo($this->filename ?? '', PATHINFO_EXTENSION));
        return in_array($ext, ['mp3', 'm4a', 'aac', 'flac', 'wav']);
    }

    // Shorts helpers
    public function isShorts()
    {
        return $this->is_shorts === true || $this->is_shorts === 1 || $this->is_shorts === '1';
    }

    // Scope for shorts videos
    public function scopeShorts($query)
    {
        return $query->where('is_shorts', true);
    }

    // Scope for non-shorts videos
    public function scopeNotShorts($query)
    {
        return $query->where('is_shorts', false);
    }

    // Check if video qualifies as shorts (auto-detection)
    public function qualifiesAsShorts()
    {
        // Shorts: duration <= 60 seconds AND portrait orientation
        return $this->duration <= 60 && $this->orientation === 'portrait';
    }

    // Comments relationship
    public function comments()
    {
        return $this->hasMany(Comment::class)->latest();
    }

    public function getCommentCountAttribute()
    {
        return $this->comments()->count();
    }

    // Match events relationships
    public function matchRounds()
    {
        return $this->hasMany(MatchRound::class)->orderBy('round_number');
    }

    public function matchPoints()
    {
        return $this->hasMany(MatchPoint::class);
    }

    public function coachReviews()
    {
        return $this->hasMany(CoachReview::class)->orderBy('start_time_seconds');
    }

    // Get recent views count (within hours)
    public function getRecentViews($hours = 48)
    {
        return \DB::table('video_views')
            ->where('video_id', $this->id)
            ->where('watched_at', '>=', now()->subHours($hours))
            ->count();
    }

    // Get views in last 24 hours (for velocity calculation)
    public function getViewsLast24Hours()
    {
        return $this->getRecentViews(24);
    }

    // Calculate trending score (YouTube-style algorithm)
    public function getTrendingScore($hours = 48)
    {
        $recentViews = $this->getRecentViews($hours);

        // Don't include videos older than 10 days
        if ($this->created_at->diffInDays(now()) > 10) {
            return 0;
        }

        // Don't include videos with no recent views
        if ($recentViews < 5) {
            return 0;
        }

        // Calculate view velocity (views per hour in last 48 hours)
        $velocity = $recentViews / $hours;

        // Recency bonus: newer videos get a boost
        $ageHours = $this->created_at->diffInHours(now());
        $recencyBonus = max(0, 1 - ($ageHours / 240)); // Decreases over 10 days

        // Like count bonus
        $likeBonus = $this->like_count * 0.1;

        // Calculate final score
        // Weight: 70% recent views, 15% velocity, 10% recency, 5% likes
        $score = ($recentViews * 0.70) +
                 ($velocity * 100 * 0.15) +
                 ($recencyBonus * 50 * 0.10) +
                 ($likeBonus * 0.05);

        return round($score, 2);
    }

    // Get thumbnail dimensions for Open Graph
    public function getThumbnailWidthAttribute()
    {
        // Default OG recommended size is 1200x630
        return $this->width ?? 1280;
    }

    public function getThumbnailHeightAttribute()
    {
        // Default OG recommended size is 1200x630
        return $this->height ?? 720;
    }

    // Get video stream URL for Open Graph
    public function getStreamUrlAttribute()
    {
        return route('videos.stream', $this);
    }

    // Get secure share URL
    public function getSecureShareUrlAttribute()
    {
        return secure_url(route('videos.show', $this));
    }

    // Get secure thumbnail URL
    public function getSecureThumbnailUrlAttribute()
    {
        if ($this->thumbnail) {
            return secure_asset('storage/thumbnails/'.$this->thumbnail);
        }

        return null;
    }

    // Get full thumbnail URL with dimensions for Open Graph
    public function getOpenGraphImageAttribute()
    {
        $thumbnail = $this->thumbnail_url;

        // Add cache busting for dynamic thumbnails
        if ($this->thumbnail) {
            $thumbnail .= '?v='.$this->updated_at->timestamp;
        }

        return $thumbnail;
    }

    // Get author/uploader name
    public function getAuthorNameAttribute()
    {
        return $this->user ? $this->user->name : config('app.name');
    }

    // Get video duration in ISO 8601 format for Open Graph
    public function getIsoDurationAttribute()
    {
        if (! $this->duration) {
            return null;
        }

        $hours = floor($this->duration / 3600);
        $minutes = floor(($this->duration % 3600) / 60);
        $seconds = $this->duration % 60;

        if ($hours > 0) {
            return sprintf('PT%dH%dM%dS', $hours, $minutes, $seconds);
        } elseif ($minutes > 0) {
            return sprintf('PT%dM%dS', $minutes, $seconds);
        }

        return sprintf('PT%dS', $seconds);
    }

    // Scope for trending videos
    public function scopeTrending($query, $hours = 48, $limit = 50)
    {
        return $query->public()
            ->where('status', 'ready')
            ->where('created_at', '>=', now()->subDays(10))
            ->orderByDesc(\DB::raw('(
                (SELECT COUNT(*) FROM video_views vv WHERE vv.video_id = videos.id AND vv.watched_at >= DATE_SUB(NOW(), INTERVAL '.$hours.' HOUR)) * 0.70 +
                (SELECT COUNT(*) FROM video_views vv WHERE vv.video_id = videos.id AND vv.watched_at >= DATE_SUB(NOW(), INTERVAL '.$hours.' HOUR)) / '.$hours.' * 100 * 0.15 +
                GREATEST(0, TIMESTAMPDIFF(HOUR, videos.created_at, NOW()) / 240) * 50 * 0.10 +
                (SELECT COUNT(*) FROM video_likes vl WHERE vl.video_id = videos.id) * 0.1 * 0.05
            )'))
            ->limit($limit);
    }
}
