<?php

declare(strict_types=1);

namespace App\Services;

use App\Jobs\RefreshCollectionHealthJob;
use App\Jobs\RefreshCollectionQualityJob;
use App\Jobs\RefreshCollectionRecommendationJob;
use App\Jobs\ScanCollectionDuplicateCandidatesJob;
use App\Models\Collection;
use App\Models\User;
use Illuminate\Support\Collection as SupportCollection;

class CollectionBackgroundJobService
{
    public function dispatchQualityRefresh(Collection $collection, ?User $actor = null): array
    {
        RefreshCollectionQualityJob::dispatch((int) $collection->id, $actor?->id)->afterCommit();

        return [
            'status' => 'queued',
            'job' => 'quality_refresh',
            'scope' => 'single',
            'count' => 1,
            'collection_ids' => [(int) $collection->id],
            'message' => 'Quality refresh queued.',
        ];
    }

    public function dispatchHealthRefresh(?Collection $collection = null, ?User $actor = null): array
    {
        $targets = $collection ? collect([$collection]) : $this->healthTargets();

        $targets->each(fn (Collection $item) => RefreshCollectionHealthJob::dispatch((int) $item->id, $actor?->id, 'programming-eligibility')->afterCommit());

        return $this->queuedPayload('health_refresh', $targets, 'Health and eligibility refresh queued.');
    }

    public function dispatchRecommendationRefresh(?Collection $collection = null, ?User $actor = null, string $context = 'default'): array
    {
        $targets = $collection ? collect([$collection]) : $this->recommendationTargets();

        $targets->each(fn (Collection $item) => RefreshCollectionRecommendationJob::dispatch((int) $item->id, $actor?->id, $context)->afterCommit());

        return $this->queuedPayload('recommendation_refresh', $targets, 'Recommendation refresh queued.');
    }

    public function dispatchDuplicateScan(?Collection $collection = null, ?User $actor = null): array
    {
        $targets = $collection ? collect([$collection]) : $this->duplicateTargets();

        $targets->each(fn (Collection $item) => ScanCollectionDuplicateCandidatesJob::dispatch((int) $item->id, $actor?->id)->afterCommit());

        return $this->queuedPayload('duplicate_scan', $targets, 'Duplicate scan queued.');
    }

    public function dispatchScheduledMaintenance(bool $health = true, bool $recommendations = true, bool $duplicates = true): array
    {
        $summary = [];

        if ($health) {
            $summary['health'] = $this->dispatchHealthRefresh();
        }

        if ($recommendations) {
            $summary['recommendations'] = $this->dispatchRecommendationRefresh();
        }

        if ($duplicates) {
            $summary['duplicates'] = $this->dispatchDuplicateScan();
        }

        return $summary;
    }

    /** @return SupportCollection<int, Collection> */
    private function healthTargets(): SupportCollection
    {
        $cutoff = now()->subHours(max(1, (int) config('collections.v5.queue.health_stale_after_hours', 24)));

        return Collection::query()
            ->where(function ($query) use ($cutoff): void {
                $query->whereNull('last_health_check_at')
                    ->orWhere('last_health_check_at', '<=', $cutoff)
                    ->orWhereColumn('updated_at', '>', 'last_health_check_at');
            })
            ->orderBy('last_health_check_at')
            ->orderByDesc('updated_at')
            ->limit(max(1, (int) config('collections.v5.queue.health_batch_size', 40)))
            ->get(['id']);
    }

    /** @return SupportCollection<int, Collection> */
    private function recommendationTargets(): SupportCollection
    {
        $cutoff = now()->subHours(max(1, (int) config('collections.v5.queue.recommendation_stale_after_hours', 12)));

        return Collection::query()
            ->where('placement_eligibility', true)
            ->where(function ($query) use ($cutoff): void {
                $query->whereNull('last_recommendation_refresh_at')
                    ->orWhere('last_recommendation_refresh_at', '<=', $cutoff)
                    ->orWhereColumn('updated_at', '>', 'last_recommendation_refresh_at');
            })
            ->orderBy('last_recommendation_refresh_at')
            ->orderByDesc('ranking_score')
            ->limit(max(1, (int) config('collections.v5.queue.recommendation_batch_size', 40)))
            ->get(['id']);
    }

    /** @return SupportCollection<int, Collection> */
    private function duplicateTargets(): SupportCollection
    {
        $cutoff = now()->subHours(max(1, (int) config('collections.v5.queue.duplicate_stale_after_hours', 24)));

        return Collection::query()
            ->whereNull('canonical_collection_id')
            ->where(function ($query) use ($cutoff): void {
                $query->where('updated_at', '>=', $cutoff)
                    ->orWhereDoesntHave('mergeActionsAsSource', function ($mergeQuery) use ($cutoff): void {
                        $mergeQuery->where('action_type', 'suggested')
                            ->where('updated_at', '>=', $cutoff);
                    });
            })
            ->orderByDesc('updated_at')
            ->limit(max(1, (int) config('collections.v5.queue.duplicate_batch_size', 30)))
            ->get(['id']);
    }

    /**
     * @param  SupportCollection<int, Collection>  $targets
     */
    private function queuedPayload(string $job, SupportCollection $targets, string $message): array
    {
        $ids = $targets->pluck('id')->map(static fn ($id): int => (int) $id)->values()->all();

        return [
            'status' => 'queued',
            'job' => $job,
            'scope' => count($ids) === 1 ? 'single' : 'batch',
            'count' => count($ids),
            'collection_ids' => $ids,
            'items' => [],
            'message' => $ids === [] ? 'No collections needed this refresh.' : $message,
        ];
    }
}