<?php

namespace App\Services;

use App\Models\Release;
use App\Models\Settings;
use Blacklight\ColorCLI;
use Blacklight\processing\tv\LocalDB;
use Blacklight\processing\tv\TMDB;
use Blacklight\processing\tv\TraktTv;
use Blacklight\processing\tv\TVDB;
use Blacklight\processing\tv\TVMaze;

class TvProcessor
{
    // Processing modes
    public const MODE_PIPELINE = 'pipeline';  // Sequential processing (efficient, reduces API calls)

    public const MODE_PARALLEL = 'parallel';  // Parallel processing (faster, more API calls)

    private bool $echooutput;

    private ColorCLI $colorCli;

    /**
     * @var array<int, array{name: string, factory: callable, status: int}>
     */
    private array $providers;

    private array $stats = [
        'mode' => self::MODE_PIPELINE,
        'totalDuration' => 0.0,
        'providers' => [],
    ];

    public function __construct(bool $echooutput)
    {
        $this->echooutput = $echooutput;
        $this->colorCli = new ColorCLI;
        $this->providers = $this->buildProviderPipeline();
    }

    /**
     * Process all TV related releases across supported providers.
     *
     * @param  string  $groupID  Group ID to process
     * @param  string  $guidChar  GUID character to process
     * @param  int|string|null  $processTV  0/1/2 or '' to read from settings
     * @param  string  $mode  Processing mode: 'pipeline' (sequential) or 'parallel' (simultaneous)
     */
    public function process(string $groupID = '', string $guidChar = '', int|string|null $processTV = '', string $mode = self::MODE_PIPELINE): void
    {
        $processTV = (int) (is_numeric($processTV) ? $processTV : Settings::settingValue('lookuptv'));
        if ($processTV <= 0) {
            return;
        }

        if ($mode === self::MODE_PARALLEL) {
            $this->processParallel($groupID, $guidChar, $processTV);
        } else {
            $this->processPipeline($groupID, $guidChar, $processTV);
        }
    }

    /**
     * Retrieve statistics from the most recent run.
     */
    public function getStats(): array
    {
        return $this->stats;
    }

    /**
     * Process releases through providers in parallel (all providers process all releases).
     * This is faster but uses more API calls. Compatible with Forking class.
     */
    private function processParallel(string $groupID, string $guidChar, int $processTV): void
    {
        $this->resetStats(self::MODE_PARALLEL);
        $this->displayModeHeader(self::MODE_PARALLEL, $guidChar);
        [$totalTime, $processedAny] = $this->runProviders(self::MODE_PARALLEL, $groupID, $guidChar, $processTV);

        if ($processedAny) {
            $this->displaySummaryParallel($totalTime);
        }
    }

    /**
     * Process releases through providers in pipeline (sequential, each processes failures from previous).
     * This is more efficient and reduces API calls significantly.
     */
    private function processPipeline(string $groupID, string $guidChar, int $processTV): void
    {
        $this->resetStats(self::MODE_PIPELINE);
        $this->displayModeHeader(self::MODE_PIPELINE, $guidChar);
        [, $processedAny] = $this->runProviders(self::MODE_PIPELINE, $groupID, $guidChar, $processTV);

        if ($processedAny) {
            $this->displaySummary();
        }
    }

    private function resetStats(string $mode): void
    {
        $this->stats = [
            'mode' => $mode,
            'totalDuration' => 0.0,
            'providers' => [],
        ];

        foreach ($this->providers as $provider) {
            $this->stats['providers'][$provider['name']] = [
                'status' => 'pending',
                'duration' => 0.0,
            ];
        }
    }

    /**
     * Execute providers in the configured order and track timing.
     *
     * @return array{0: float, 1: bool} [total elapsed time, processed flag]
     */
    private function runProviders(string $mode, string $groupID, string $guidChar, int $processTV): array
    {
        $totalTime = 0.0;
        $providerCount = count($this->providers);
        $processedAny = false;

        foreach ($this->providers as $index => $provider) {
            $pendingWork = $this->getPendingWorkForProvider($provider, $groupID, $guidChar, $processTV);
            if ($pendingWork === null) {
                $this->displayProviderSkip($provider['name'], $index + 1, $providerCount);
                $this->stats['providers'][$provider['name']] = [
                    'status' => 'skipped',
                    'duration' => 0.0,
                ];

                continue;
            }

            $this->displayProviderHeader($provider['name'], $index + 1, $providerCount);
            $this->displayProviderPreview(
                $provider['name'],
                $pendingWork['release'],
                $pendingWork['total'],
                $provider['status'] ?? 0
            );

            /** @var object $processor */
            $processor = ($provider['factory'])();
            if (property_exists($processor, 'echooutput')) {
                $processor->echooutput = $this->echooutput;
            }

            $startTime = microtime(true);
            $processor->processSite($groupID, $guidChar, $processTV);
            $elapsedTime = microtime(true) - $startTime;
            $processedAny = true;
            $this->stats['providers'][$provider['name']] = [
                'status' => 'processed',
                'duration' => $elapsedTime,
            ];
            $this->stats['totalDuration'] += $elapsedTime;

            if ($mode === self::MODE_PARALLEL) {
                $totalTime += $elapsedTime;
            }

            $this->displayProviderComplete($provider['name'], $elapsedTime);
        }

        return [$totalTime, $processedAny];
    }

    /**
     * Get the provider pipeline in order of preference.
     */
    private function buildProviderPipeline(): array
    {
        return [
            ['name' => 'Local DB', 'factory' => static fn () => new LocalDB, 'status' => 0],
            ['name' => 'TVDB', 'factory' => static fn () => new TVDB, 'status' => 0],
            ['name' => 'TVMaze', 'factory' => static fn () => new TVMaze, 'status' => -1],
            ['name' => 'TMDB', 'factory' => static fn () => new TMDB, 'status' => -2],
            ['name' => 'Trakt', 'factory' => static fn () => new TraktTv, 'status' => -3],
        ];
    }

    /**
     * Determine whether a provider has pending work and return a preview release.
     *
     * @return array{release: Release, total: int}|null
     */
    private function getPendingWorkForProvider(array $provider, string $groupID, string $guidChar, int $processTV): ?array
    {
        $status = $provider['status'] ?? 0;

        $baseQuery = Release::query()
            ->select([
                'id',
                'guid',
                'leftguid',
                'groups_id',
                'searchname',
                'size',
                'categories_id',
                'videos_id',
                'tv_episodes_id',
                'postdate',
            ])
            ->where(['videos_id' => 0, 'tv_episodes_id' => $status])
            ->where('size', '>', 1048576)
            ->whereBetween('categories_id', [5000, 5999])
            ->where('categories_id', '<>', 5070);

        if ($groupID !== '') {
            $baseQuery->where('groups_id', $groupID);
        }

        if ($guidChar !== '') {
            $baseQuery->where('leftguid', $guidChar);
        }

        if ($processTV === 2) {
            $baseQuery->where('isrenamed', '=', 1);
        }

        $total = (clone $baseQuery)->count();
        if ($total === 0) {
            return null;
        }

        $release = (clone $baseQuery)
            ->orderByDesc('postdate')
            ->first();

        if ($release === null) {
            return null;
        }

        return [
            'release' => $release,
            'total' => $total,
        ];
    }

    private function displayModeHeader(string $mode, string $guidChar = ''): void
    {
        if ($mode === self::MODE_PARALLEL) {
            $this->displayHeaderParallel($guidChar);
        } else {
            $this->displayHeader($guidChar);
        }
    }

    /**
     * Display the processing header.
     */
    private function displayHeader(string $guidChar = ''): void
    {
        if (! $this->echooutput) {
            return;
        }

        echo "\n";
        $this->colorCli->headerOver('▶ TV Processing');
        if ($guidChar !== '') {
            $this->colorCli->primaryOver(' → ');
            $this->colorCli->headerOver('PIPELINE Mode');
            $this->colorCli->primaryOver(' → ');
            $this->colorCli->warningOver('Bucket: ');
            $this->colorCli->header(strtoupper($guidChar));
        } else {
            $this->colorCli->primaryOver(' → ');
            $this->colorCli->header('PIPELINE Mode');
        }
        echo "\n";
    }

    /**
     * Display the processing header for parallel mode.
     */
    private function displayHeaderParallel(string $guidChar = ''): void
    {
        if (! $this->echooutput) {
            return;
        }

        echo "\n";
        $this->colorCli->headerOver('▶ TV Processing');
        if ($guidChar !== '') {
            $this->colorCli->primaryOver(' → ');
            $this->colorCli->headerOver('PARALLEL Mode');
            $this->colorCli->primaryOver(' → ');
            $this->colorCli->warningOver('Bucket: ');
            $this->colorCli->header(strtoupper($guidChar));
        } else {
            $this->colorCli->primaryOver(' → ');
            $this->colorCli->header('PARALLEL Mode');
        }
        echo "\n";
    }

    /**
     * Display provider processing header.
     */
    private function displayProviderHeader(string $providerName, int $step, int $total): void
    {
        if (! $this->echooutput) {
            return;
        }

        echo "\n";
        $this->colorCli->primaryOver('  [');
        $this->colorCli->warningOver($step);
        $this->colorCli->primaryOver('/');
        $this->colorCli->warningOver($total);
        $this->colorCli->primaryOver('] ');
        $this->colorCli->headerOver('→ ');
        $this->colorCli->header($providerName);
    }

    /**
     * Display details about the next release the provider will work on.
     */
    private function displayProviderPreview(string $providerName, Release $release, int $total, int $status): void
    {
        if (! $this->echooutput) {
            return;
        }

        echo "\n";
        $this->colorCli->primaryOver('       ↳ ');
        $this->colorCli->warningOver($total);
        $this->colorCli->primaryOver(' pending release(s)');
        $this->colorCli->primaryOver(' | Filter: ');
        $this->colorCli->alternate($this->formatStatusLabel($status));

        echo "\n";
        $this->colorCli->primaryOver('         · Next: ');
        $this->colorCli->headerOver($release->searchname ?? 'n/a');
        $this->colorCli->primaryOver(' | ID ');
        $this->colorCli->warningOver((string) $release->id);
        $this->colorCli->primaryOver(' | GUID ');
        $this->colorCli->warningOver((string) $release->guid);
        echo "\n";
    }

    /**
     * Display provider skip message.
     */
    private function displayProviderSkip(string $providerName, int $step, int $total): void
    {
        if (! $this->echooutput) {
            return;
        }

        echo "\n";
        $this->colorCli->primaryOver('  [');
        $this->colorCli->warningOver($step);
        $this->colorCli->primaryOver('/');
        $this->colorCli->warningOver($total);
        $this->colorCli->primaryOver('] ');
        $this->colorCli->alternateOver('→ ');
        $this->colorCli->alternate($providerName.' → No work remaining, skipping');
        echo "\n";
    }

    /**
     * Display provider completion message.
     */
    private function displayProviderComplete(string $providerName, float $elapsedTime): void
    {
        if (! $this->echooutput) {
            return;
        }

        echo "\n";
        $this->colorCli->primaryOver('  ✓ ');
        $this->colorCli->primaryOver($providerName);
        $this->colorCli->primaryOver(' → ');
        $this->colorCli->alternateOver('Completed in ');
        $this->colorCli->warning(sprintf('%.2fs', $elapsedTime));
        echo "\n";
    }

    /**
     * Display final processing summary.
     */
    private function displaySummary(): void
    {
        if (! $this->echooutput) {
            return;
        }

        echo "\n";
        $this->colorCli->primaryOver('✓ Pipeline Complete');
        $this->colorCli->primaryOver(' → ');
        $this->colorCli->primary('Local DB → TVDB → TVMaze → TMDB → Trakt');
        echo "\n";
    }

    /**
     * Display final processing summary for parallel mode.
     */
    private function displaySummaryParallel(float $totalTime): void
    {
        if (! $this->echooutput) {
            return;
        }

        echo "\n";
        $this->colorCli->primaryOver('✓ Parallel Processing Complete');
        $this->colorCli->primaryOver(' → ');
        $this->colorCli->warningOver('Total: ');
        $this->colorCli->warning(sprintf('%.2fs', $totalTime));
        echo "\n";
    }

    private function formatStatusLabel(int $status): string
    {
        return match ($status) {
            0 => 'tv_episodes_id = 0 (Unmatched)',
            -1 => 'tv_episodes_id = -1 (Awaiting TVMaze)',
            -2 => 'tv_episodes_id = -2 (Awaiting TMDB)',
            -3 => 'tv_episodes_id = -3 (Awaiting Trakt)',
            default => 'tv_episodes_id = '.$status,
        };
    }
}
