<?php

declare(strict_types=1);

namespace App\Models;

use App\Facades\Search;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

/**
 * App\Models\Video.
 *
 * @property int $id Show ID to be used in other tables as reference
 * @property bool $type 0 = TV, 1 = Film, 2 = Anime
 * @property string $title Name of the video.
 * @property string $countries_id Two character country code (FK to countries table).
 * @property string $started Date (UTC) of production's first airing.
 * @property int $anidb ID number for anidb site
 * @property string $imdb ID number for IMDB site (without the 'tt' prefix).
 * @property int $tmdb ID number for TMDB site.
 * @property int $trakt ID number for TraktTV site.
 * @property int $tvdb ID number for TVDB site
 * @property int $tvmaze ID number for TVMaze site.
 * @property int $tvrage ID number for TVRage site.
 * @property bool $source Which site did we use for info?
 * @property-read Collection|VideoAlias[] $alias
 * @property-read Collection|TvEpisode[] $episode
 * @property-read Collection|Release[] $release
 *
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereAnidb($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereCountriesId($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereId($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereImdb($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereSource($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereStarted($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereTitle($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereTmdb($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereTrakt($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereTvdb($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereTvmaze($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereTvrage($value)
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video whereType($value)
 *
 * @mixin \Eloquent
 *
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video newModelQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video newQuery()
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Video query()
 */
class Video extends Model
{
    protected $dateFormat = false;

    /**
     * @var array<string>
     */
    protected $guarded = [];

    /**
     * @var bool
     */
    public $timestamps = false;

    /**
     * @return HasMany<VideoAlias, $this>
     */
    public function alias(): HasMany
    {
        return $this->hasMany(VideoAlias::class, 'videos_id');
    }

    /**
     * @return HasMany<Release, $this>
     */
    public function release(): HasMany
    {
        return $this->hasMany(Release::class, 'videos_id');
    }

    /**
     * @return HasMany<TvEpisode, $this>
     */
    public function episode(): HasMany
    {
        return $this->hasMany(TvEpisode::class, 'videos_id');
    }

    /**
     * @return HasOne<TvInfo, $this>
     */
    public function tvInfo(): HasOne
    {
        return $this->hasOne(TvInfo::class, 'videos_id');
    }

    /**
     * Get info from tables for the provided ID.
     *
     *
     * @return Model|null|static
     */
    public static function getByVideoID(mixed $id)
    {
        return self::query()
            ->select(['videos.*', 'tv_info.summary', 'tv_info.publisher', 'tv_info.image'])
            ->where('videos.id', $id)
            ->join('tv_info', 'videos.id', '=', 'tv_info.videos_id')
            ->first();
    }

    /**
     * Retrieves a range of all shows for the show-edit admin list.
     */
    public static function getRange(string $showname = ''): LengthAwarePaginator // @phpstan-ignore missingType.generics
    {
        $sql = self::query()
            ->select(['videos.*', 'tv_info.summary', 'tv_info.publisher', 'tv_info.image'])
            ->join('tv_info', 'videos.id', '=', 'tv_info.videos_id');

        if ($showname !== '') {
            $sql->where('videos.title', 'like', '%'.$showname.'%');
        }

        return $sql->paginate(config('nntmux.items_per_page'));
    }

    /**
     * Returns a count of all shows -- usually used by pager.
     */
    public static function getCount(string $showname = ''): int
    {
        $res = self::query()->join('tv_info', 'videos.id', '=', 'tv_info.videos_id');

        if ($showname !== '') {
            $res->where('videos.title', 'like', '%'.$showname.'%');
        }

        return $res->count('videos.id');
    }

    /**
     * Retrieves and returns a list of shows with eligible releases.
     *
     * @return array<string, mixed>
     */
    public static function getSeriesList(mixed $uid, string $letter = '', string $showname = ''): array
    {
        $cacheKey = 'video_series_list:'.md5(serialize([(string) $uid, $letter, $showname]));

        return Cache::remember($cacheKey, now()->addMinutes((int) config('nntmux.cache_expiry_medium', 60)), function () use ($uid, $letter, $showname): array {
            return self::getSeriesListUncached($uid, $letter, $showname);
        });
    }

    /**
     * @internal Used by {@see getSeriesList} with cache wrapper.
     */
    private static function getSeriesListUncached(mixed $uid, string $letter = '', string $showname = ''): array
    {
        $params = [
            'uid' => $uid,
            'tv_root' => Category::TV_ROOT,
            'tv_other' => Category::TV_OTHER,
            'now' => now()->toDateTimeString(),
        ];

        $letterCondition = '';
        if ($letter !== '') {
            if ($letter === '0-9' || $letter === '[0-9]') {
                $letterCondition = "AND videos.title REGEXP '^[0-9]'";
            } else {
                $letterCondition = 'AND videos.title LIKE :letter';
                $params['letter'] = $letter.'%';
            }
        }

        $shownameCondition = '';
        if ($showname !== '') {
            if (Search::isAvailable()) {
                $hits = Search::searchTvShows($showname, 800);
                $vids = array_values(array_filter(array_map(static fn ($id): int => (int) $id, $hits['id'] ?? [])));
                if ($vids === []) {
                    return [];
                }
                $shownameCondition = 'AND videos.id IN ('.implode(',', $vids).')';
            } else {
                $shownameCondition = 'AND videos.title LIKE :showname';
                $params['showname'] = '%'.$showname.'%';
            }
        }

        $sql = "
            SELECT
                videos.*,
                tve.firstaired AS prevdate,
                tve.title AS previnfo,
                tvi.publisher,
                us.id AS userseriesid
            FROM videos
            INNER JOIN tv_info AS tvi ON videos.id = tvi.videos_id
            INNER JOIN (
                SELECT videos_id, MAX(firstaired) AS max_firstaired
                FROM tv_episodes
                WHERE firstaired < :now
                GROUP BY videos_id
            ) AS latest_ep ON videos.id = latest_ep.videos_id
            INNER JOIN tv_episodes AS tve
                ON videos.id = tve.videos_id
                AND tve.firstaired = latest_ep.max_firstaired
            LEFT JOIN user_series AS us
                ON videos.id = us.videos_id
                AND us.users_id = :uid
            WHERE EXISTS (
                SELECT 1
                FROM releases AS r
                WHERE r.videos_id = videos.id
                AND r.categories_id BETWEEN :tv_root AND :tv_other
            )
            {$letterCondition}
            {$shownameCondition}
            GROUP BY videos.id
            ORDER BY videos.title ASC
        ";

        $results = DB::select($sql, $params);

        // Convert stdClass objects to arrays for backward compatibility
        return array_map(fn ($row) => (array) $row, $results);
    }
}
