<?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\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class ArtworkAwardService
{
    /**
     * Award an artwork with the given medal.
     * Throws ValidationException if the user already awarded this artwork.
     */
    public function award(Artwork $artwork, User $user, string $medal): ArtworkAward
    {
        $this->validateMedal($medal);

        $existing = ArtworkAward::where('artwork_id', $artwork->id)
            ->where('user_id', $user->id)
            ->first();

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

        $award = ArtworkAward::create([
            'artwork_id' => $artwork->id,
            'user_id'    => $user->id,
            'medal'      => $medal,
            'weight'     => ArtworkAward::WEIGHTS[$medal],
        ]);

        $this->recalcStats($artwork->id);
        $this->syncToSearch($artwork);

        return $award;
    }

    /**
     * Change an existing award medal for a user/artwork pair.
     */
    public function changeAward(Artwork $artwork, User $user, string $medal): ArtworkAward
    {
        $this->validateMedal($medal);

        $award = ArtworkAward::where('artwork_id', $artwork->id)
            ->where('user_id', $user->id)
            ->firstOrFail();

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

        $this->recalcStats($artwork->id);
        $this->syncToSearch($artwork);

        return $award->fresh();
    }

    /**
     * Remove an award for a user/artwork pair.
     * Uses model-level delete so the ArtworkAwardObserver fires.
     */
    public function removeAward(Artwork $artwork, User $user): void
    {
        $award = ArtworkAward::where('artwork_id', $artwork->id)
            ->where('user_id', $user->id)
            ->first();

        if ($award) {
            $award->delete(); // fires ArtworkAwardObserver::deleted
        } else {
            // Nothing to remove, but still sync stats to be safe.
            $this->recalcStats($artwork->id);
            $this->syncToSearch($artwork);
        }
    }

    /**
     * Recalculate and persist stats for the given artwork.
     */
    public function recalcStats(int $artworkId): ArtworkAwardStat
    {
        $counts = DB::table('artwork_awards')
            ->where('artwork_id', $artworkId)
            ->selectRaw('
                SUM(medal = \'gold\')   AS gold_count,
                SUM(medal = \'silver\') AS silver_count,
                SUM(medal = \'bronze\') AS bronze_count
            ')
            ->first();

        $gold   = (int) ($counts->gold_count ?? 0);
        $silver = (int) ($counts->silver_count ?? 0);
        $bronze = (int) ($counts->bronze_count ?? 0);
        $score  = ($gold * 3) + ($silver * 2) + ($bronze * 1);

        $stat = ArtworkAwardStat::updateOrCreate(
            ['artwork_id' => $artworkId],
            [
                'gold_count'   => $gold,
                'silver_count' => $silver,
                'bronze_count' => $bronze,
                'score_total'  => $score,
                'updated_at'   => now(),
            ]
        );

        return $stat;
    }

    /**
     * Queue a non-blocking reindex for the artwork after award stats change.
     */
    public function syncToSearch(Artwork $artwork): void
    {
        IndexArtworkJob::dispatch($artwork->id);
    }

    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.',
            ]);
        }
    }
}
