<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Collection;
use App\Models\CollectionProgramAssignment;
use App\Models\User;
use App\Services\CollectionCanonicalService;
use App\Services\CollectionExperimentService;
use App\Services\CollectionHealthService;
use App\Services\CollectionHistoryService;
use App\Services\CollectionObservabilityService;
use App\Services\CollectionPartnerProgramService;
use App\Services\CollectionWorkflowService;
use Illuminate\Support\Collection as SupportCollection;

class CollectionProgrammingService
{
    public function __construct(
        private readonly CollectionHealthService $health,
        private readonly CollectionRankingService $ranking,
        private readonly CollectionMergeService $merge,
        private readonly CollectionCanonicalService $canonical,
        private readonly CollectionWorkflowService $workflow,
        private readonly CollectionExperimentService $experiments,
        private readonly CollectionPartnerProgramService $partnerPrograms,
        private readonly CollectionObservabilityService $observability,
    ) {
    }

    public function diagnostics(Collection $collection): array
    {
        return $this->observability->diagnostics($collection->fresh());
    }

    public function syncHooks(Collection $collection, array $attributes, ?User $actor = null): array
    {
        $workflowAttributes = array_intersect_key($attributes, array_flip([
            'placement_eligibility',
        ]));

        $experimentAttributes = array_intersect_key($attributes, array_flip([
            'experiment_key',
            'experiment_treatment',
            'placement_variant',
            'ranking_mode_variant',
            'collection_pool_version',
            'test_label',
        ]));

        $partnerAttributes = array_intersect_key($attributes, array_flip([
            'partner_key',
            'trust_tier',
            'promotion_tier',
            'sponsorship_state',
            'ownership_domain',
            'commercial_review_state',
            'legal_review_state',
        ]));

        $updated = $collection->fresh();

        if ($workflowAttributes !== []) {
            $updated = $this->workflow->update($updated->loadMissing('user'), $workflowAttributes, $actor);
        }

        if ($experimentAttributes !== []) {
            $updated = $this->experiments->sync($updated->loadMissing('user'), $experimentAttributes, $actor);
        }

        if ($partnerAttributes !== []) {
            $updated = $this->partnerPrograms->sync($updated->loadMissing('user'), $partnerAttributes, $actor);
        }

        $updated = $updated->fresh();

        return [
            'collection' => $updated,
            'diagnostics' => $this->observability->diagnostics($updated),
        ];
    }

    public function mergeQueue(bool $ownerView = true, int $pendingLimit = 8, int $recentLimit = 8): array
    {
        return $this->merge->queueOverview($ownerView, $pendingLimit, $recentLimit);
    }

    public function canonicalizePair(Collection $source, Collection $target, ?User $actor = null): array
    {
        $updatedSource = $this->canonical->designate($source->loadMissing('user'), $target->loadMissing('user'), $actor);

        return [
            'source' => $updatedSource,
            'target' => $target->fresh(),
            'mergeQueue' => $this->mergeQueue(true),
        ];
    }

    public function mergePair(Collection $source, Collection $target, ?User $actor = null): array
    {
        $result = $this->merge->mergeInto($source->loadMissing('user'), $target->loadMissing('user'), $actor);

        return [
            'source' => $result['source'],
            'target' => $result['target'],
            'attached_artwork_ids' => $result['attached_artwork_ids'],
            'mergeQueue' => $this->mergeQueue(true),
        ];
    }

    public function rejectPair(Collection $source, Collection $target, ?User $actor = null): array
    {
        $updatedSource = $this->merge->rejectCandidate($source->loadMissing('user'), $target->loadMissing('user'), $actor);

        return [
            'source' => $updatedSource,
            'target' => $target->fresh(),
            'mergeQueue' => $this->mergeQueue(true),
        ];
    }

    public function assignments(): SupportCollection
    {
        return CollectionProgramAssignment::query()
            ->with(['collection.user:id,username,name', 'creator:id,username,name'])
            ->orderBy('program_key')
            ->orderByDesc('priority')
            ->orderBy('id')
            ->get();
    }

    public function upsertAssignment(array $attributes, ?User $actor = null): CollectionProgramAssignment
    {
        $assignmentId = isset($attributes['id']) ? (int) $attributes['id'] : null;

        $payload = [
            'collection_id' => (int) $attributes['collection_id'],
            'program_key' => (string) $attributes['program_key'],
            'campaign_key' => $attributes['campaign_key'] ?? null,
            'placement_scope' => $attributes['placement_scope'] ?? null,
            'starts_at' => $attributes['starts_at'] ?? null,
            'ends_at' => $attributes['ends_at'] ?? null,
            'priority' => (int) ($attributes['priority'] ?? 0),
            'notes' => $attributes['notes'] ?? null,
            'created_by_user_id' => $actor?->id,
        ];

        if ($assignmentId > 0) {
            $assignment = CollectionProgramAssignment::query()->findOrFail($assignmentId);
            $assignment->fill($payload)->save();
        } else {
            $assignment = CollectionProgramAssignment::query()->create($payload);
        }

        $collection = Collection::query()->findOrFail((int) $payload['collection_id']);
        $collection->forceFill(['program_key' => $payload['program_key']])->save();

        app(CollectionHistoryService::class)->record(
            $collection->fresh(),
            $actor,
            $assignmentId > 0 ? 'program_assignment_updated' : 'program_assignment_created',
            'Collection program assignment updated.',
            null,
            $payload
        );

        return $assignment->fresh(['collection.user', 'creator']);
    }

    public function previewProgram(string $programKey, int $limit = 12): SupportCollection
    {
        return Collection::query()
            ->public()
            ->where('program_key', $programKey)
            ->where('placement_eligibility', true)
            ->with(['user:id,username,name', 'coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at'])
            ->orderByDesc('ranking_score')
            ->orderByDesc('health_score')
            ->limit(max(1, min($limit, 24)))
            ->get();
    }

    public function refreshEligibility(?Collection $collection = null, ?User $actor = null): array
    {
        $items = $collection ? collect([$collection]) : Collection::query()->whereNotNull('program_key')->get();

        $results = $items->map(function (Collection $item) use ($actor): array {
            $fresh = $this->health->refresh($item, $actor, 'programming-eligibility');

            return [
                'collection_id' => (int) $fresh->id,
                'placement_eligibility' => (bool) $fresh->placement_eligibility,
                'health_state' => $fresh->health_state,
                'readiness_state' => $fresh->readiness_state,
            ];
        })->values();

        return [
            'count' => $results->count(),
            'items' => $results->all(),
        ];
    }

    public function refreshRecommendations(?Collection $collection = null): array
    {
        $items = $collection ? collect([$collection]) : Collection::query()->where('placement_eligibility', true)->limit(100)->get();

        $results = $items->map(function (Collection $item): array {
            $fresh = $this->ranking->refresh($item);

            return [
                'collection_id' => (int) $fresh->id,
                'recommendation_tier' => $fresh->recommendation_tier,
                'ranking_bucket' => $fresh->ranking_bucket,
                'search_boost_tier' => $fresh->search_boost_tier,
            ];
        })->values();

        return [
            'count' => $results->count(),
            'items' => $results->all(),
        ];
    }

    public function duplicateScan(?Collection $collection = null): array
    {
        $items = $collection ? collect([$collection]) : Collection::query()->whereNull('canonical_collection_id')->limit(100)->get();

        $results = $items->map(function (Collection $item): array {
            return [
                'collection_id' => (int) $item->id,
                'candidates' => $this->merge->duplicateCandidates($item)->map(fn (Collection $candidate) => [
                    'id' => (int) $candidate->id,
                    'title' => (string) $candidate->title,
                    'slug' => (string) $candidate->slug,
                ])->values()->all(),
            ];
        })->filter(fn (array $row): bool => $row['candidates'] !== [])->values();

        return [
            'count' => $results->count(),
            'items' => $results->all(),
        ];
    }
}