<?php

namespace App\Services\Achievements;

use App\Events\NotificationCreated;
use App\Models\Achievement;
use App\Models\Notification;
use App\Models\User;
use App\Models\UserAchievement;
use App\Models\UserFlight;
use App\Services\Achievements\Checkers\AchievementCheckerInterface;
use App\Services\Achievements\Checkers\AircraftChecker;
use App\Services\Achievements\Checkers\CountriesAndContinentsChecker;
use App\Services\Achievements\Checkers\AirlinesAndAlliancesChecker;
use App\Services\Achievements\Checkers\FunChallengesChecker;
use App\Services\Achievements\Checkers\GeneralFlyingChecker;
use Illuminate\Support\Collection;

class AchievementService
{
    /** Cached achievement lookups so checkers don't each hit the DB. */
    private Collection $achievementCache;

    /** The user currently being evaluated — set per calculate() call. */
    private User $user;

    private array $checkers = [
        GeneralFlyingChecker::class,
        CountriesAndContinentsChecker::class,
        AircraftChecker::class,
        AirlinesAndAlliancesChecker::class,
        FunChallengesChecker::class,
    ];

    public function __construct()
    {
        $this->achievementCache = collect();
    }

    // ---------------------------------------------------------------
    // Orchestration
    // ---------------------------------------------------------------

    /**
     * @var Collection<int,UserFlight> $flights
     */
    private Collection $flights;

    public function calculate(User $user): void
    {
        $this->user = $user;
        $this->achievementCache = Achievement::all()->keyBy('internal_name');

        // Load once with every relationship any checker could need
        $this->flights = $user->flights()->with([
            'airline.alliance',
            'aircraft',
            'flightClass',
            'departureAirport.region',
            'arrivalAirport.region',
            'departureAirport.region.continent',
            'arrivalAirport.region.continent',
        ])->where('departure_date', '<=', now('UTC'))->get();

        foreach ($this->checkers as $checkerClass) {
            $checker = new $checkerClass($this);
            $checker->check($user);
        }
    }

    /**
     * @return Collection<int,UserFlight>
     */
    public function getFlights(): Collection
    {
        return $this->flights;
    }


    // ---------------------------------------------------------------
    // Award / revoke
    // ---------------------------------------------------------------

    /**
     * Upsert a user_achievement row and fire a notification if this
     * is a newly unlocked achievement.
     */
    public function award(Achievement $achievement, User $user, ?int $progress = null): void
    {
        $existing = UserAchievement::where('user_id', $user->id)
            ->where('achievement_id', $achievement->id)
            ->first();

        $alreadyUnlocked = $existing !== null;

        UserAchievement::updateOrCreate(
            ['user_id' => $user->id, 'achievement_id' => $achievement->id],
            ['progress' => $progress],
        );

        if (! $alreadyUnlocked) {
            $this->createAchievementNotification($user, $achievement);
        }
    }

    /**
     * Update progress on a progressive achievement without marking it
     * as unlocked (i.e. progress exists but threshold not yet met).
     * Creates the row if it doesn't exist yet.
     */
    public function updateProgress(Achievement $achievement, User $user, int $progress): void
    {
        UserAchievement::updateOrCreate(
            ['user_id' => $user->id, 'achievement_id' => $achievement->id],
            ['progress' => $progress],
        );
    }

    /**
     * Remove a user_achievement row and its associated notification
     * if the achievement is no longer valid for this user.
     */
    public function revoke(Achievement $achievement, User $user): void
    {
        UserAchievement::where('user_id', $user->id)
            ->where('achievement_id', $achievement->id)
            ->delete();

        Notification::where('user_id', $user->id)
            ->where('achievement_id', $achievement->id)
            ->delete();
    }

    // ---------------------------------------------------------------
    // Helpers for checkers
    // ---------------------------------------------------------------

    public function resolveAchievement(string $internalName): ?Achievement
    {
        $achievement = $this->achievementCache->get($internalName);
        return $achievement instanceof Achievement ? $achievement : null;
    }

    public function currentUser(): User
    {
        return $this->user;
    }

    // ---------------------------------------------------------------
    // Notifications
    // ---------------------------------------------------------------

    private function createAchievementNotification(User $user, Achievement $achievement): void
    {
        Notification::create([
            'user_id'        => $user->id,
            'title'          => $achievement->name,
            'body'           => $achievement->short_description,
            'is_achievement' => true,
            'achievement_id' => $achievement->id,
            'expires_at'     => null,
        ]);
    }
}
