<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Artwork;
use App\Models\ArtworkFeature;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection as SupportCollection;
use Illuminate\Support\Str;

class FeaturedArtworkAdminService
{
    public function __construct(private readonly ArtworkService $artworks)
    {
    }

    /**
     * @return array<string, mixed>
     */
    public function pageProps(): array
    {
        $now = Carbon::now();
        $features = ArtworkFeature::query()
            ->with([
                'artwork' => fn ($query) => $query->withTrashed()->with([
                    'user:id,username,name',
                    'user.profile:user_id,avatar_hash',
                    'group:id,name,slug,avatar_path',
                    'awardStat:artwork_id,gold_count,silver_count,bronze_count,score_total,score_7d,score_30d,last_medaled_at,updated_at',
                ]),
            ])
            ->orderByDesc('priority')
            ->orderByDesc('featured_at')
            ->orderByDesc('id')
            ->get();

        $duplicateCounts = $features->countBy(fn (ArtworkFeature $feature): int => (int) $feature->artwork_id);
        $entries = $features
            ->map(fn (ArtworkFeature $feature): array => $this->mapFeature($feature, $duplicateCounts, $now))
            ->sort(function (array $left, array $right): int {
                $comparisons = [
                    (int) $right['eligibility']['is_eligible'] <=> (int) $left['eligibility']['is_eligible'],
                    (int) $right['is_force_hero'] <=> (int) $left['is_force_hero'],
                    (int) $right['is_active'] <=> (int) $left['is_active'],
                    (int) $left['is_expired'] <=> (int) $right['is_expired'],
                    (int) $right['priority'] <=> (int) $left['priority'],
                    (int) $right['medals']['score_30d'] <=> (int) $left['medals']['score_30d'],
                    $this->timestamp($right['featured_at']) <=> $this->timestamp($left['featured_at']),
                    $this->timestamp($right['artwork']['published_at']) <=> $this->timestamp($left['artwork']['published_at']),
                    (int) $right['id'] <=> (int) $left['id'],
                ];

                foreach ($comparisons as $comparison) {
                    if ($comparison !== 0) {
                        return $comparison;
                    }
                }

                return 0;
            })
            ->values();

        $eligibleEntries = $this->sortForHeroSelection(
            $entries->filter(fn (array $entry): bool => (bool) $entry['eligibility']['is_eligible'])->values()
        );

        $sharedWinnerArtworkId = $this->artworks->getFeaturedArtworkWinner()?->id;
        $winner = $sharedWinnerArtworkId
            ? $entries->first(fn (array $entry): bool => (int) $entry['artwork_id'] === (int) $sharedWinnerArtworkId)
            : null;

        if (! is_array($winner) && $eligibleEntries->isNotEmpty()) {
            $winner = $eligibleEntries->first();
        }

        $winnerReason = is_array($winner) ? $this->buildWinnerReason($winner, $eligibleEntries) : null;
        $winnerId = is_array($winner) ? (int) $winner['id'] : null;

        $entries = $entries
            ->map(function (array $entry) use ($winnerId, $winnerReason): array {
                $isWinner = $winnerId !== null && (int) $entry['id'] === $winnerId;

                if ($isWinner) {
                    array_unshift($entry['status_badges'], [
                        'label' => 'Winner',
                        'tone' => 'amber',
                    ]);
                }

                $entry['is_winner'] = $isWinner;
                $entry['winner_reason'] = $isWinner ? $winnerReason : null;

                return $entry;
            })
            ->values();

        $winner = is_array($winner)
            ? array_merge($winner, ['selection_reason' => $winnerReason])
            : null;

        return [
            'entries' => $entries->all(),
            'winner' => $winner,
            'stats' => [
                'total' => $entries->count(),
                'active' => $entries->where('is_active', true)->count(),
                'inactive' => $entries->where('is_active', false)->count(),
                'expired' => $entries->where('is_expired', true)->count(),
                'eligible' => $entries->filter(fn (array $entry): bool => (bool) $entry['eligibility']['is_eligible'])->count(),
                'ineligible' => $entries->filter(fn (array $entry): bool => ! $entry['eligibility']['is_eligible'])->count(),
            ],
        ];
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function searchArtworks(string $term, int $limit = 12): array
    {
        $term = trim($term);
        if ($term === '') {
            return [];
        }

        $now = Carbon::now();
        $artworks = Artwork::query()
            ->with([
                'user:id,username,name',
                'user.profile:user_id,avatar_hash',
                'group:id,name,slug,avatar_path',
                'awardStat:artwork_id,gold_count,silver_count,bronze_count,score_total,score_7d,score_30d,last_medaled_at,updated_at',
            ])
            ->where(function ($query) use ($term): void {
                if (ctype_digit($term)) {
                    $query->where('artworks.id', (int) $term);
                }

                $query->orWhere('artworks.title', 'like', '%' . $term . '%')
                    ->orWhere('artworks.slug', 'like', '%' . $term . '%')
                    ->orWhereHas('user', function ($userQuery) use ($term): void {
                        $userQuery->where('username', 'like', '%' . $term . '%')
                            ->orWhere('name', 'like', '%' . $term . '%');
                    })
                    ->orWhereHas('group', function ($groupQuery) use ($term): void {
                        $groupQuery->where('name', 'like', '%' . $term . '%')
                            ->orWhere('slug', 'like', '%' . $term . '%');
                    });
            });

        if (ctype_digit($term)) {
            $artworks->orderByRaw('CASE WHEN artworks.id = ? THEN 0 ELSE 1 END', [(int) $term]);
        }

        $artworks = $artworks
            ->orderByDesc('published_at')
            ->limit($limit)
            ->get();

        $featureCounts = ArtworkFeature::query()
            ->whereNull('deleted_at')
            ->whereIn('artwork_id', $artworks->pluck('id'))
            ->selectRaw('artwork_id, COUNT(*) as aggregate')
            ->groupBy('artwork_id')
            ->pluck('aggregate', 'artwork_id');

        return $artworks
            ->map(fn (Artwork $artwork): array => $this->mapArtworkCandidate($artwork, (int) ($featureCounts[(int) $artwork->id] ?? 0), $now))
            ->values()
            ->all();
    }

    /**
    * @param SupportCollection<int, int> $duplicateCounts
     * @return array<string, mixed>
     */
    private function mapFeature(ArtworkFeature $feature, SupportCollection $duplicateCounts, Carbon $now): array
    {
        $context = $this->mapArtworkContext($feature->artwork, $now);
        $isExpired = $feature->expires_at !== null && $feature->expires_at->lte($now);
        $isEligible = (bool) $feature->is_active && ! $isExpired && (bool) $context['eligibility']['is_eligible'];
        $eligibilityReasons = $context['eligibility']['reasons'];

        if (! $feature->is_active) {
            $eligibilityReasons[] = 'Inactive';
        }

        if ($isExpired) {
            $eligibilityReasons[] = 'Expired';
        }

        $statusBadges = [];

        if ($feature->is_active && ! $isExpired) {
            $statusBadges[] = ['label' => 'Active', 'tone' => 'emerald'];
        }

        if ((bool) $feature->force_hero) {
            $statusBadges[] = ['label' => 'Force Hero', 'tone' => 'amber'];
        }

        if (! $feature->is_active) {
            $statusBadges[] = ['label' => 'Inactive', 'tone' => 'slate'];
        }

        if ($isExpired) {
            $statusBadges[] = ['label' => 'Expired', 'tone' => 'amber'];
        }

        $statusBadges[] = $isEligible
            ? ['label' => 'Eligible', 'tone' => 'sky']
            : ['label' => 'Not eligible', 'tone' => 'rose'];

        if ((bool) $context['flags']['is_private']) {
            $statusBadges[] = ['label' => 'Private', 'tone' => 'slate'];
        }

        if ((bool) $context['flags']['is_unpublished']) {
            $statusBadges[] = ['label' => 'Unpublished', 'tone' => 'slate'];
        }

        if ((bool) $context['flags']['missing_preview']) {
            $statusBadges[] = ['label' => 'Missing preview', 'tone' => 'rose'];
        }

        if ((bool) $context['flags']['is_deleted']) {
            $statusBadges[] = ['label' => 'Deleted', 'tone' => 'slate'];
        }

        if ((int) $duplicateCounts->get((int) $feature->artwork_id, 0) > 1) {
            $statusBadges[] = ['label' => 'Duplicate', 'tone' => 'sky'];
        }

        return [
            'id' => (int) $feature->id,
            'artwork_id' => (int) $feature->artwork_id,
            'priority' => (int) $feature->priority,
            'featured_at' => $feature->featured_at?->toIsoString(),
            'expires_at' => $feature->expires_at?->toIsoString(),
            'created_at' => $feature->created_at?->toIsoString(),
            'updated_at' => $feature->updated_at?->toIsoString(),
            'is_active' => (bool) $feature->is_active,
            'is_force_hero' => (bool) $feature->force_hero,
            'is_expired' => $isExpired,
            'duplicate_count' => (int) $duplicateCounts->get((int) $feature->artwork_id, 0),
            'artwork' => $context['artwork'],
            'medals' => $context['medals'],
            'eligibility' => [
                'is_eligible' => $isEligible,
                'reasons' => array_values(array_unique($eligibilityReasons)),
            ],
            'status_badges' => $statusBadges,
            'is_winner' => false,
            'winner_reason' => null,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function mapArtworkCandidate(Artwork $artwork, int $existingFeatureCount, Carbon $now): array
    {
        $context = $this->mapArtworkContext($artwork, $now);

        return array_merge($context['artwork'], [
            'medals' => $context['medals'],
            'eligibility' => $context['eligibility'],
            'existing_feature_count' => $existingFeatureCount,
            'already_featured' => $existingFeatureCount > 0,
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    private function mapArtworkContext(?Artwork $artwork, Carbon $now): array
    {
        if (! $artwork instanceof Artwork) {
            return [
                'artwork' => [
                    'id' => null,
                    'title' => 'Missing artwork',
                    'slug' => null,
                    'canonical_url' => null,
                    'thumbnail' => ThumbnailPresenter::present(['id' => null, 'name' => 'Missing artwork'], 'sm'),
                    'published_at' => null,
                    'visibility' => null,
                    'is_public' => false,
                    'is_approved' => false,
                    'has_missing_preview' => true,
                    'is_deleted' => true,
                    'owner' => null,
                ],
                'medals' => [
                    'score_30d' => 0,
                ],
                'eligibility' => [
                    'is_eligible' => false,
                    'reasons' => ['Deleted'],
                ],
                'flags' => [
                    'is_private' => false,
                    'is_unpublished' => true,
                    'missing_preview' => true,
                    'is_deleted' => true,
                ],
            ];
        }

        $isDeleted = $artwork->deleted_at !== null;
        $isPublic = ! $isDeleted && (bool) $artwork->is_public;
        $isApproved = ! $isDeleted && (bool) $artwork->is_approved;
        $isPublished = ! $isDeleted && $artwork->published_at !== null && $artwork->published_at->lte($now);
        $hasPreview = ! (bool) $artwork->has_missing_thumbnails;
        $owner = $this->mapOwner($artwork);
        $reasons = [];

        if ($isDeleted) {
            $reasons[] = 'Deleted';
        }

        if (! $isPublic) {
            $reasons[] = 'Private';
        }

        if (! $isApproved) {
            $reasons[] = 'Not approved';
        }

        if (! $isPublished) {
            $reasons[] = 'Unpublished';
        }

        if (! $hasPreview) {
            $reasons[] = 'Missing preview';
        }

        return [
            'artwork' => [
                'id' => (int) $artwork->id,
                'title' => html_entity_decode((string) $artwork->title, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
                'slug' => (string) $artwork->slug,
                'canonical_url' => route('art.show', ['id' => (int) $artwork->id, 'slug' => $this->artworkSlug($artwork)]),
                'thumbnail' => ThumbnailPresenter::present($artwork, 'sm'),
                'published_at' => $artwork->published_at?->toIsoString(),
                'visibility' => (string) ($artwork->visibility ?? ''),
                'is_public' => (bool) $artwork->is_public,
                'is_approved' => (bool) $artwork->is_approved,
                'has_missing_preview' => (bool) $artwork->has_missing_thumbnails,
                'is_deleted' => $isDeleted,
                'owner' => $owner,
            ],
            'medals' => [
                'score_30d' => (int) ($artwork->awardStat?->score_30d ?? 0),
            ],
            'eligibility' => [
                'is_eligible' => $isPublic && $isApproved && $isPublished && $hasPreview,
                'reasons' => $reasons,
            ],
            'flags' => [
                'is_private' => ! $isPublic,
                'is_unpublished' => ! $isPublished,
                'missing_preview' => ! $hasPreview,
                'is_deleted' => $isDeleted,
            ],
        ];
    }

    /**
    * @param SupportCollection<int, array<string, mixed>> $entries
    * @return SupportCollection<int, array<string, mixed>>
     */
    private function sortForHeroSelection(SupportCollection $entries): SupportCollection
    {
        return $entries
            ->sort(function (array $left, array $right): int {
                $comparisons = [
                    (int) $right['is_force_hero'] <=> (int) $left['is_force_hero'],
                    (int) $right['priority'] <=> (int) $left['priority'],
                    (int) $right['medals']['score_30d'] <=> (int) $left['medals']['score_30d'],
                    $this->timestamp($right['featured_at']) <=> $this->timestamp($left['featured_at']),
                    $this->timestamp($right['artwork']['published_at']) <=> $this->timestamp($left['artwork']['published_at']),
                    (int) $right['id'] <=> (int) $left['id'],
                ];

                foreach ($comparisons as $comparison) {
                    if ($comparison !== 0) {
                        return $comparison;
                    }
                }

                return 0;
            })
            ->values();
    }

    /**
     * @param array<string, mixed> $winner
    * @param SupportCollection<int, array<string, mixed>> $eligibleEntries
     */
    private function buildWinnerReason(array $winner, SupportCollection $eligibleEntries): string
    {
        if ((bool) ($winner['is_force_hero'] ?? false)) {
            return 'Forced hero override is enabled for this featured artwork.';
        }

        $runnerUp = $eligibleEntries->skip(1)->first();

        if (! is_array($runnerUp)) {
            return 'Only eligible featured artwork right now.';
        }

        if ((int) $winner['priority'] > (int) $runnerUp['priority']) {
            return 'Highest priority among active, eligible featured artworks.';
        }

        if ((int) $winner['medals']['score_30d'] > (int) $runnerUp['medals']['score_30d']) {
            return 'Tied on priority, won on higher 30-day medal score.';
        }

        if ($this->timestamp($winner['featured_at']) > $this->timestamp($runnerUp['featured_at'])) {
            return 'Tied on priority and medal score, won on newer featured date.';
        }

        if ($this->timestamp($winner['artwork']['published_at']) > $this->timestamp($runnerUp['artwork']['published_at'])) {
            return 'Tied on priority, medal score, and featured date, won on newer published date.';
        }

        return 'Selected by the shared homepage hero ordering.';
    }

    /**
     * @return array<string, mixed>|null
     */
    private function mapOwner(Artwork $artwork): ?array
    {
        if ($artwork->group) {
            return [
                'type' => 'group',
                'display_name' => (string) $artwork->group->name,
                'username' => (string) $artwork->group->slug,
                'profile_url' => $artwork->group->publicUrl(),
            ];
        }

        if (! $artwork->user) {
            return null;
        }

        return [
            'type' => 'user',
            'display_name' => (string) ($artwork->user->name ?: '@' . $artwork->user->username),
            'username' => (string) $artwork->user->username,
            'profile_url' => $artwork->user->username !== '' ? '/@' . $artwork->user->username : null,
        ];
    }

    private function artworkSlug(Artwork $artwork): string
    {
        $slug = trim((string) $artwork->slug);
        if ($slug !== '') {
            return $slug;
        }

        $titleSlug = Str::slug((string) $artwork->title);

        return $titleSlug !== '' ? $titleSlug : (string) $artwork->id;
    }

    private function timestamp(?string $value): int
    {
        if (! is_string($value) || trim($value) === '') {
            return 0;
        }

        return (int) (strtotime($value) ?: 0);
    }
}