<?php

namespace App\Http\Controllers;

use App\Models\Ad;
use App\Models\Genre;
use App\Models\Movie;
use App\Models\Page;
use App\Models\Setting;
use App\Models\Show;
use App\Services\EmbedProviderService;
use App\Services\TmdbService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;

class FrontController extends Controller
{
    private function shared(): array
    {
        return [
            'settings' => Setting::pluck('value', 'key'),
            'footerPages' => Page::where('is_active', true)->orderBy('title')->get(),
            'genres' => Genre::where('is_active', true)->orderBy('name')->get(),
            'ads' => Ad::where('is_active', true)->get()->groupBy('slot'),
        ];
    }

    private function catalogueFilters(Request $request, bool $allowContentType = false): array
    {
        $rules = [
            'sort' => ['nullable', 'in:trending,latest_imported,latest_released,most_viewed'],
            'from_date' => ['nullable', 'date'],
            'to_date' => ['nullable', 'date', 'after_or_equal:from_date'],
        ];

        if ($allowContentType) {
            $rules['content_type'] = ['nullable', 'in:all,movie,show'];
        }

        $validated = $request->validate($rules);

        return [
            'sort' => $validated['sort'] ?? 'trending',
            'fromDate' => $validated['from_date'] ?? null,
            'toDate' => $validated['to_date'] ?? null,
            'contentType' => $validated['content_type'] ?? 'all',
        ];
    }

    private function applyListSorting($query, string $sort, string $dateColumn, array $trendingRanks = [])
    {
        if ($sort === 'trending' && $trendingRanks !== []) {
            $cases = [];
            $bindings = [];

            foreach ($trendingRanks as $tmdbId => $rank) {
                $cases[] = 'WHEN ? THEN ?';
                $bindings[] = (int) $tmdbId;
                $bindings[] = (int) $rank;
            }

            return $query
                ->orderByRaw('CASE tmdb_id ' . implode(' ', $cases) . ' ELSE 999999 END ASC', $bindings)
                ->orderByDesc($dateColumn)
                ->orderByDesc('id');
        }

        return match ($sort) {
            'latest_released' => $query->orderByDesc($dateColumn)->orderByDesc('id'),
            'most_viewed' => $query->orderByDesc('views_count')->orderByDesc('id'),
            default => $query->orderByDesc('created_at')->orderByDesc('id'),
        };
    }

    private function trendingRanks(TmdbService $tmdb, string $type): array
    {
        try {
            return $tmdb->trendingRanks($type);
        } catch (\Throwable $exception) {
            report($exception);
            return ['movie' => [], 'show' => []];
        }
    }

    public function home(Request $request, TmdbService $tmdb): View|JsonResponse
    {
        $filter = $this->catalogueFilters($request, true);
        $sort = $filter['sort'];
        $fromDate = $filter['fromDate'];
        $toDate = $filter['toDate'];
        $contentType = $filter['contentType'];
        $trending = $sort === 'trending' ? $this->trendingRanks($tmdb, 'all') : ['movie' => [], 'show' => []];

        $movies = DB::table('movies')
            ->where('is_active', true)
            ->when($fromDate, fn ($query) => $query->whereDate('release_date', '>=', $fromDate))
            ->when($toDate, fn ($query) => $query->whereDate('release_date', '<=', $toDate))
            ->select([
                'id', 'tmdb_id', 'title', 'slug', 'poster_path', 'vote_average',
                'is_featured', 'created_at', 'views_count',
                DB::raw("'movie' as content_type"),
                DB::raw('release_date as content_date'),
            ]);

        $shows = DB::table('shows')
            ->where('is_active', true)
            ->when($fromDate, fn ($query) => $query->whereDate('first_air_date', '>=', $fromDate))
            ->when($toDate, fn ($query) => $query->whereDate('first_air_date', '<=', $toDate))
            ->select([
                'id', 'tmdb_id', 'title', 'slug', 'poster_path', 'vote_average',
                'is_featured', 'created_at', 'views_count',
                DB::raw("'show' as content_type"),
                DB::raw('first_air_date as content_date'),
            ]);

        $feed = match ($contentType) {
            'movie' => $movies,
            'show' => $shows,
            default => $movies->unionAll($shows),
        };

        $contentsQuery = DB::query()->fromSub($feed, 'catalogue_contents');

        if ($sort === 'trending' && ($trending['movie'] !== [] || $trending['show'] !== [])) {
            $cases = [];
            $bindings = [];
            foreach (['movie', 'show'] as $type) {
                foreach ($trending[$type] as $tmdbId => $rank) {
                    $cases[] = 'WHEN content_type = ? AND tmdb_id = ? THEN ?';
                    $bindings[] = $type;
                    $bindings[] = (int) $tmdbId;
                    $bindings[] = (int) $rank;
                }
            }

            $contentsQuery
                ->orderByRaw('CASE ' . implode(' ', $cases) . ' ELSE 999999 END ASC', $bindings)
                ->orderByDesc('content_date')
                ->orderByDesc('id');
        } else {
            match ($sort) {
                'latest_released', 'trending' => $contentsQuery->orderByDesc('content_date')->orderByDesc('id'),
                'most_viewed' => $contentsQuery->orderByDesc('views_count')->orderByDesc('id'),
                default => $contentsQuery->orderByDesc('created_at')->orderByDesc('id'),
            };
        }

        $contents = $contentsQuery->paginate(36)->withQueryString();

        if ($request->ajax() || $request->wantsJson()) {
            return response()->json([
                'html' => view('front.partials.home-cards', compact('contents'))->render(),
                'next_page_url' => $contents->nextPageUrl(),
                'has_more_pages' => $contents->hasMorePages(),
            ]);
        }

        return view('front.home', array_merge($this->shared(), compact(
            'contents', 'fromDate', 'toDate', 'contentType', 'sort'
        )));
    }

    public function movies(Request $request, TmdbService $tmdb): View|JsonResponse
    {
        $filter = $this->catalogueFilters($request);
        $sort = $filter['sort'];
        $fromDate = $filter['fromDate'];
        $toDate = $filter['toDate'];
        $trending = $sort === 'trending' ? $this->trendingRanks($tmdb, 'movie')['movie'] : [];

        $query = Movie::with('genres')
            ->where('is_active', true)
            ->when($fromDate, fn ($q) => $q->whereDate('release_date', '>=', $fromDate))
            ->when($toDate, fn ($q) => $q->whereDate('release_date', '<=', $toDate));

        $items = $this->applyListSorting($query, $sort, 'release_date', $trending)
            ->paginate(24)->withQueryString();

        if ($request->ajax() || $request->wantsJson()) {
            return response()->json([
                'html' => view('front.partials.list-cards', ['items' => $items, 'type' => 'movie'])->render(),
                'next_page_url' => $items->nextPageUrl(),
                'has_more_pages' => $items->hasMorePages(),
            ]);
        }

        return view('front.list', array_merge($this->shared(), [
            'title' => 'Movies', 'items' => $items, 'type' => 'movie',
            'sort' => $sort, 'fromDate' => $fromDate, 'toDate' => $toDate,
            'filterAction' => route('movies'),
        ]));
    }

    public function shows(Request $request, TmdbService $tmdb): View|JsonResponse
    {
        $filter = $this->catalogueFilters($request);
        $sort = $filter['sort'];
        $fromDate = $filter['fromDate'];
        $toDate = $filter['toDate'];
        $trending = $sort === 'trending' ? $this->trendingRanks($tmdb, 'tv')['show'] : [];

        $query = Show::with('genres')
            ->where('is_active', true)
            ->when($fromDate, fn ($q) => $q->whereDate('first_air_date', '>=', $fromDate))
            ->when($toDate, fn ($q) => $q->whereDate('first_air_date', '<=', $toDate));

        $items = $this->applyListSorting($query, $sort, 'first_air_date', $trending)
            ->paginate(24)->withQueryString();

        if ($request->ajax() || $request->wantsJson()) {
            return response()->json([
                'html' => view('front.partials.list-cards', ['items' => $items, 'type' => 'show'])->render(),
                'next_page_url' => $items->nextPageUrl(),
                'has_more_pages' => $items->hasMorePages(),
            ]);
        }

        return view('front.list', array_merge($this->shared(), [
            'title' => 'TV Shows', 'items' => $items, 'type' => 'show',
            'sort' => $sort, 'fromDate' => $fromDate, 'toDate' => $toDate,
            'filterAction' => route('shows'),
        ]));
    }

    /**
     * Resolve by slug while keeping old numeric links working.
     */
    public function movie(Request $request, string $movie, EmbedProviderService $embeds): View|RedirectResponse
    {
        $query = Movie::with('genres')->where('slug', $movie);

        if (ctype_digit($movie)) {
            $query->orWhere('id', (int) $movie);
        }

        $movieModel = $query->firstOrFail();
        abort_unless($movieModel->is_active, 404);

        $movieModel->increment('views_count');

        if ($movie !== $movieModel->slug) {
            return redirect()->route('movie', $movieModel, 301);
        }

        $genreIds = $movieModel->genres->pluck('id');

        $relatedMovies = Movie::with('genres')
            ->where('is_active', true)
            ->whereKeyNot($movieModel->getKey())
            ->when($genreIds->isNotEmpty(), function ($query) use ($genreIds) {
                $query->whereHas('genres', fn ($genreQuery) => $genreQuery->whereIn('genres.id', $genreIds));
            })
            ->latest()
            ->take(12)
            ->get();

        if ($relatedMovies->count() < 12) {
            $fallback = Movie::with('genres')
                ->where('is_active', true)
                ->whereKeyNot($movieModel->getKey())
                ->whereNotIn('id', $relatedMovies->pluck('id'))
                ->latest()
                ->take(12 - $relatedMovies->count())
                ->get();

            $relatedMovies = $relatedMovies->concat($fallback);
        }

        $providers = $embeds->providers();
        $server = $request->string('server')->toString();
        if (!array_key_exists($server, $providers)) {
            $server = $embeds->defaultKey($providers);
        }
        $embedUrl = $server && isset($providers[$server])
            ? $embeds->movieUrl($providers[$server], $movieModel)
            : null;

        return view('front.movie', array_merge($this->shared(), [
            'movie' => $movieModel,
            'relatedMovies' => $relatedMovies,
            'embedProviders' => $providers,
            'selectedServer' => $server,
            'embedUrl' => $embedUrl,
        ]));
    }

    public function show(Request $request, Show $show, EmbedProviderService $embeds): View
    {
        abort_unless($show->is_active, 404);
        $show->increment('views_count');
        $show->load(['genres', 'seasons.episodes.season']);

        $relatedShows = Show::with('genres')
            ->where('is_active', true)
            ->where($show->getQualifiedKeyName(), '!=', $show->getKey())
            ->when($show->genres->isNotEmpty(), function ($query) use ($show) {
                $query->whereHas('genres', fn ($genreQuery) => $genreQuery->whereIn('genres.id', $show->genres->pluck('id')));
            })
            ->latest()
            ->take(8)
            ->get();

        $activeEpisodes = $show->seasons
            ->where('is_active', true)
            ->flatMap(fn ($season) => $season->episodes->where('is_active', true))
            ->values();

        $requestedSeason = $request->integer('season');
        $requestedEpisode = $request->integer('episode');

        $selectedEpisode = $activeEpisodes->first(function ($episode) use ($requestedSeason, $requestedEpisode) {
            return $requestedSeason > 0
                && $requestedEpisode > 0
                && (int) $episode->season->season_number === $requestedSeason
                && (int) $episode->episode_number === $requestedEpisode;
        }) ?: $activeEpisodes->first();

        $providers = $embeds->providers();
        $server = $request->string('server')->toString();
        if (!array_key_exists($server, $providers)) {
            $server = $embeds->defaultKey($providers);
        }

        $embedUrl = ($selectedEpisode && $server && isset($providers[$server]))
            ? $embeds->episodeUrl($providers[$server], $show, $selectedEpisode)
            : null;

        return view('front.show', array_merge($this->shared(), compact('show', 'relatedShows'), [
            'embedProviders' => $providers,
            'selectedServer' => $server,
            'selectedEpisode' => $selectedEpisode,
            'embedUrl' => $embedUrl,
        ]));
    }

    public function genre(Request $request, Genre $genre, TmdbService $tmdb): View|JsonResponse
    {
        $filter = $this->catalogueFilters($request);
        $sort = $filter['sort'];
        $fromDate = $filter['fromDate'];
        $toDate = $filter['toDate'];
        $trending = $sort === 'trending'
            ? $this->trendingRanks($tmdb, 'movie')['movie']
            : [];

        $query = $genre->movies()
            ->with('genres')
            ->where('is_active', true)
            ->when($fromDate, fn ($q) => $q->whereDate('release_date', '>=', $fromDate))
            ->when($toDate, fn ($q) => $q->whereDate('release_date', '<=', $toDate));

        $items = $this->applyListSorting($query, $sort, 'release_date', $trending)
            ->paginate(24)
            ->withQueryString();

        if ($request->ajax() || $request->wantsJson()) {
            return response()->json([
                'html' => view('front.partials.list-cards', [
                    'items' => $items,
                    'type' => 'movie',
                ])->render(),
                'next_page_url' => $items->nextPageUrl(),
                'has_more_pages' => $items->hasMorePages(),
            ]);
        }

        return view('front.list', array_merge($this->shared(), [
            'title' => $genre->name,
            'items' => $items,
            'type' => 'movie',
            'sort' => $sort,
            'fromDate' => $fromDate,
            'toDate' => $toDate,
            'filterAction' => route('genre', $genre),
        ]));
    }

    public function page(Page $page): View
    {
        abort_unless($page->is_active, 404);

        return view('front.page', array_merge($this->shared(), compact('page')));
    }
}
