<?php

namespace App\Console\Commands;

use App\Jobs\PullPackageFromPaddle;
use App\Jobs\SyncPackageToPaddle;
use App\Models\Package;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

class PaddleSyncPackages extends Command
{
    protected $signature = 'paddle:sync-packages
        {--package=* : Limit sync to the given package IDs or slugs}
        {--dry-run : Generate payload snapshots without calling Paddle}
        {--pull : Fetch remote Paddle state instead of pushing local changes}
        {--allow-unmapped : Allow sync when packages are missing Paddle product/price IDs}
        {--queue : Dispatch jobs onto the queue instead of running synchronously}';

    protected $description = 'Synchronise local packages with Paddle products and prices.';

    public function handle(): int
    {
        $packages = $this->resolvePackages();

        if ($packages->isEmpty()) {
            $this->warn('No packages matched the given filters.');

            return self::FAILURE;
        }

        $dryRun = (bool) $this->option('dry-run');
        $pull = (bool) $this->option('pull');
        $queue = (bool) $this->option('queue');
        $allowUnmapped = (bool) $this->option('allow-unmapped');

        if (! $pull && ! $allowUnmapped && ! $this->hasPackageFilter()) {
            if (! $this->guardUnmappedPackages($packages)) {
                return self::FAILURE;
            }
        }

        $packages->each(function (Package $package) use ($dryRun, $pull, $queue) {
            if ($pull) {
                $this->dispatchPullJob($package, $queue);

                return;
            }

            $this->dispatchSyncJob($package, $dryRun, $queue);
        });

        $this->info(sprintf(
            'Queued %d package %s for Paddle %s.',
            $packages->count(),
            Str::plural('entry', $packages->count()),
            $pull ? 'pull' : 'sync'
        ));

        return self::SUCCESS;
    }

    protected function resolvePackages(): Collection
    {
        $keys = collect((array) $this->option('package'))->filter();

        $query = Package::query();

        if ($keys->isNotEmpty()) {
            $ids = $keys
                ->filter(fn ($value) => is_numeric($value))
                ->map(fn ($value) => (int) $value);

            $slugs = $keys
                ->reject(fn ($value) => is_numeric($value))
                ->values();

            $query->where(function ($builder) use ($ids, $slugs) {
                if ($ids->isNotEmpty()) {
                    $builder->orWhereIn('id', $ids);
                }

                if ($slugs->isNotEmpty()) {
                    $builder->orWhereIn('slug', $slugs);
                }
            });
        }

        return $query->orderByDesc('id')->get();
    }

    protected function hasPackageFilter(): bool
    {
        return collect((array) $this->option('package'))->filter()->isNotEmpty();
    }

    protected function guardUnmappedPackages(Collection $packages): bool
    {
        $unmapped = $packages->filter(fn (Package $package) => blank($package->paddle_product_id) || blank($package->paddle_price_id));

        if ($unmapped->isEmpty()) {
            return true;
        }

        $this->error('Unmapped Paddle package IDs detected. Resolve legacy mappings or pass --allow-unmapped.');
        $this->table(
            ['ID', 'Slug', 'Missing'],
            $unmapped->map(function (Package $package): array {
                $missing = [];
                if (blank($package->paddle_product_id)) {
                    $missing[] = 'product_id';
                }
                if (blank($package->paddle_price_id)) {
                    $missing[] = 'price_id';
                }

                return [
                    $package->id,
                    $package->slug,
                    implode(', ', $missing),
                ];
            })->values()->all()
        );

        return false;
    }

    protected function dispatchSyncJob(Package $package, bool $dryRun, bool $queue): void
    {
        $context = [
            'dry_run' => $dryRun,
        ];

        if ($queue) {
            SyncPackageToPaddle::dispatch($package->id, $context);
            $this->line(sprintf('> queued sync for package #%d (%s)', $package->id, $package->slug));

            return;
        }

        SyncPackageToPaddle::dispatchSync($package->id, $context);
        $this->line(sprintf('> synced package #%d (%s)', $package->id, $package->slug));
    }

    protected function dispatchPullJob(Package $package, bool $queue): void
    {
        if ($queue) {
            PullPackageFromPaddle::dispatch($package->id);
            $this->line(sprintf('> queued pull for package #%d (%s)', $package->id, $package->slug));

            return;
        }

        PullPackageFromPaddle::dispatchSync($package->id);
        $this->line(sprintf('> pulled package #%d (%s)', $package->id, $package->slug));
    }
}
