<?php

namespace App\Models;

/**
 * Points balance + tier is a maintained running total (see the
 * CreateLoyaltyTables migration doc comment for why) — every change
 * goes through creditPoints(), which is the one place that writes both
 * the ledger row and the updated total together, so they can never
 * drift apart the way two independent writers could let them.
 */
class LoyaltyAccountModel extends TenantModel
{
    protected $table            = 'loyalty_accounts';
    protected $primaryKey       = 'id';
    protected $useAutoIncrement = true;
    protected $returnType       = 'object';
    protected $useTimestamps    = true;

    protected $allowedFields = [
        'guest_id',
        'points_balance',
        'tier',
        'enrolled_at',
    ];

    public function forGuest(int $guestId): ?object
    {
        return $this->where('guest_id', $guestId)->first();
    }

    /** Batches a list-page lookup (e.g. Guest CRM) into one query, keyed by guest_id, instead of one query per row. */
    public function forGuests(array $guestIds): array
    {
        if (empty($guestIds)) {
            return [];
        }

        $rows = $this->whereIn('guest_id', $guestIds)->findAll();
        $byGuest = [];

        foreach ($rows as $row) {
            $byGuest[$row->guest_id] = $row;
        }

        return $byGuest;
    }

    /** Creates the account on first touch — every guest is implicitly enrolled the first time they earn or redeem. */
    public function ensureAccount(int $guestId): object
    {
        $account = $this->forGuest($guestId);

        if ($account !== null) {
            return $account;
        }

        $now = date('Y-m-d H:i:s');
        $this->insert(['guest_id' => $guestId, 'points_balance' => 0, 'tier' => 'bronze', 'enrolled_at' => $now]);

        return $this->forGuest($guestId);
    }

    /**
     * The one path that ever changes a balance — records the ledger
     * entry and updates the cached balance/tier together. $points is
     * signed (negative for a redemption).
     */
    public function creditPoints(int $guestId, int $points, string $type, ?string $referenceType = null, ?int $referenceId = null, ?string $notes = null, ?int $createdBy = null): void
    {
        $account = $this->ensureAccount($guestId);
        $newBalance = $account->points_balance + $points;

        (new LoyaltyTransactionModel())->insert([
            'guest_id'       => $guestId,
            'type'           => $type,
            'points'         => $points,
            'reference_type' => $referenceType,
            'reference_id'   => $referenceId,
            'notes'          => $notes,
            'created_by'     => $createdBy,
        ]);

        $this->update($account->id, [
            'points_balance' => $newBalance,
            'tier'           => $this->tierForPoints($newBalance),
        ]);
    }

    /** Thresholds are manager-configurable (Settings), not hardcoded — see SettingsController::FIELDS. */
    public function tierForPoints(int $points): string
    {
        $settings = service('settings');
        $gold     = (int) ($settings->get('loyalty.tier_gold_threshold') ?: 2000);
        $silver   = (int) ($settings->get('loyalty.tier_silver_threshold') ?: 500);

        if ($points >= $gold) {
            return 'gold';
        }

        if ($points >= $silver) {
            return 'silver';
        }

        return 'bronze';
    }
}
