<?php

declare(strict_types=1);

namespace App\Services;

use App\Jobs\IndexArtworkJob;
use App\Models\Artwork;
use App\Models\ArtworkAward;
use App\Models\ArtworkAwardStat;
use App\Models\ArtworkMedal;
use App\Models\ArtworkMedalStat;
use App\Models\User;
use Illuminate\Support\Carbon;
use Illuminate\Validation\ValidationException;

final class ArtworkMedalService
{
    public function __construct(private readonly HomepageService $homepage)
    {
    }

    public function upsert(Artwork $artwork, User $user, string $medal): ArtworkMedal
    {
        $existing = ArtworkMedal::query()
            ->where('artwork_id', $artwork->id)
            ->where('user_id', $user->id)
            ->first();

        return $existing
            ? $this->changeMedal($artwork, $user, $medal)
            : $this->award($artwork, $user, $medal);
    }

    public function award(Artwork $artwork, User $user, string $medal): ArtworkMedal
    {
        $this->validateMedal($medal);

        $exists = ArtworkMedal::query()
            ->where('artwork_id', $artwork->id)
            ->where('user_id', $user->id)
            ->exists();

        if ($exists) {
            throw ValidationException::withMessages([
                'medal' => 'You have already awarded this artwork. Use change to update.',
            ]);
        }

        return ArtworkMedal::query()->create([
            'artwork_id' => $artwork->id,
            'user_id' => $user->id,
            'medal_type' => $medal,
            'weight' => ArtworkAward::weightFor($medal),
        ]);
    }

    public function changeMedal(Artwork $artwork, User $user, string $medal): ArtworkMedal
    {
        $this->validateMedal($medal);

        $award = ArtworkMedal::query()
            ->where('artwork_id', $artwork->id)
            ->where('user_id', $user->id)
            ->first();

        if (! $award) {
            throw ValidationException::withMessages([
                'medal' => 'No existing medal found for this artwork.',
            ]);
        }

        $award->update([
            'medal_type' => $medal,
            'weight' => ArtworkAward::weightFor($medal),
        ]);

        return $award->fresh();
    }

    public function removeMedal(Artwork $artwork, User $user): void
    {
        $award = ArtworkMedal::query()
            ->where('artwork_id', $artwork->id)
            ->where('user_id', $user->id)
            ->first();

        if ($award) {
            $award->delete();
        }
    }

    public function recalculateStats(int $artworkId): ArtworkMedalStat
    {
        $rows = ArtworkMedal::query()
            ->where('artwork_id', $artworkId)
            ->get(['medal_type', 'weight', 'updated_at']);

        $cutoff7d = now()->subDays(7);
        $cutoff30d = now()->subDays(30);

        $goldCount = 0;
        $silverCount = 0;
        $bronzeCount = 0;
        $scoreTotal = 0;
        $score7d = 0;
        $score30d = 0;
        $lastMedaledAt = null;

        foreach ($rows as $row) {
            $medal = (string) $row->medal;
            $weight = (int) ($row->weight ?? ArtworkAward::weightFor($medal));
            $updatedAt = $row->updated_at instanceof Carbon ? $row->updated_at : Carbon::parse($row->updated_at);

            if ($medal === 'gold') {
                $goldCount++;
            } elseif ($medal === 'silver') {
                $silverCount++;
            } elseif ($medal === 'bronze') {
                $bronzeCount++;
            }

            $scoreTotal += $weight;

            if ($updatedAt->greaterThanOrEqualTo($cutoff7d)) {
                $score7d += $weight;
            }

            if ($updatedAt->greaterThanOrEqualTo($cutoff30d)) {
                $score30d += $weight;
            }

            if ($lastMedaledAt === null || $updatedAt->greaterThan($lastMedaledAt)) {
                $lastMedaledAt = $updatedAt;
            }
        }

        $stat = ArtworkAwardStat::query()->updateOrCreate(
            ['artwork_id' => $artworkId],
            [
                'gold_count' => $goldCount,
                'silver_count' => $silverCount,
                'bronze_count' => $bronzeCount,
                'score_total' => $scoreTotal,
                'score_7d' => $score7d,
                'score_30d' => $score30d,
                'last_medaled_at' => $lastMedaledAt,
            ]
        );

        return ArtworkMedalStat::query()->findOrFail($stat->artwork_id);
    }

    public function refreshArtworkMedalState(int $artworkId): ArtworkMedalStat
    {
        $stat = $this->recalculateStats($artworkId);
        $this->syncArtworkToSearch($artworkId);
        $this->homepage->clearFeaturedAndMedalCaches();

        return $stat;
    }

    public function syncArtworkToSearch(int $artworkId): void
    {
        IndexArtworkJob::dispatch($artworkId);
    }

    private function validateMedal(string $medal): void
    {
        if (! in_array($medal, ArtworkAward::MEDALS, true)) {
            throw ValidationException::withMessages([
                'medal' => 'Invalid medal. Must be gold, silver, or bronze.',
            ]);
        }
    }
}