<?php

namespace App\Models;

use Database\Factories\DrawFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

/**
 * @property int $id
 * @property string $name
 * @property string|null $description
 * @property list<array{quantity: int, price: float}> $ticket_price_tiers
 * @property Carbon $starts_at
 * @property Carbon $ends_at
 * @property bool $is_finalized
 * @property int|null $winner_ticket_id
 * @property Carbon|null $created_at
 * @property Carbon|null $updated_at
 * @property-read \Illuminate\Database\Eloquent\Collection<int, DrawTicket> $tickets
 * @property-read int|null $tickets_count
 * @property-read DrawTicket|null $winnerTicket
 *
 * @method static \Database\Factories\DrawFactory factory($count = null, $state = [])
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw newModelQuery()
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw newQuery()
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw query()
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw whereCreatedAt($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw whereDescription($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw whereEndsAt($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw whereId($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw whereIsFinalized($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw whereName($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw whereStartsAt($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw whereTicketPriceTiers($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw whereUpdatedAt($value)
 * @method static \Illuminate\Database\Eloquent\Builder<static>|Draw whereWinnerTicketId($value)
 *
 * @mixin \Eloquent
 */
class Draw extends Model
{
    /** @use HasFactory<DrawFactory> */
    use HasFactory;

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'description',
        'ticket_price_tiers',
        'starts_at',
        'ends_at',
        'is_finalized',
        'winner_ticket_id',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'ticket_price_tiers' => 'array',
            'starts_at' => 'date',
            'ends_at' => 'date',
            'is_finalized' => 'boolean',
        ];
    }

    /**
     * Get the tickets for this draw.
     *
     * @return HasMany<DrawTicket, $this>
     */
    public function tickets(): HasMany
    {
        return $this->hasMany(DrawTicket::class);
    }

    /**
     * Get the winning ticket.
     *
     * @return BelongsTo<DrawTicket, $this>
     */
    public function winnerTicket(): BelongsTo
    {
        return $this->belongsTo(DrawTicket::class, 'winner_ticket_id');
    }

    /**
     * Check if the draw is currently active.
     */
    public function isActive(): bool
    {
        $now = Carbon::now();

        return $now->greaterThanOrEqualTo($this->starts_at)
            && $now->lessThan($this->ends_at)
            && ! $this->is_finalized;
    }

    /**
     * Check if the draw has ended.
     */
    public function hasEnded(): bool
    {
        return Carbon::now()->greaterThanOrEqualTo($this->ends_at);
    }

    /**
     * Check if the draw has started.
     */
    public function hasStarted(): bool
    {
        return Carbon::now()->greaterThanOrEqualTo($this->starts_at);
    }

    /**
     * Get the total number of tickets sold.
     */
    public function totalTicketsSold(): int
    {
        return $this->tickets_count ?? $this->tickets()->count();
    }

    /**
     * Get the total amount collected from ticket sales.
     */
    public function totalAmountCollected(): float
    {
        return $this->tickets_sum_amount_paid ?? $this->tickets()->sum('amount_paid');
    }

    /**
     * Get the prize amount (50% of total collected).
     */
    public function prizeAmount(): float
    {
        return $this->totalAmountCollected() / 2;
    }

    /**
     * Get the duration of the draw in days.
     */
    public function durationInDays(): int
    {
        return (int) $this->starts_at->diffInDays($this->ends_at);
    }

    /**
     * Get the next available ticket number.
     * Format: {draw_id}{00001} - e.g. Draw 1: 100001, Draw 12: 1200001
     */
    public function nextTicketNumber(): int
    {
        $maxTicketNumber = $this->tickets()->max('ticket_number') ?? 0;

        if ($maxTicketNumber == 0) {
            // First ticket for this draw
            return (int) ($this->id.str_pad('1', 5, '0', STR_PAD_LEFT));
        }

        // Extract the last 5 digits and increment
        $ticketNumberStr = (string) $maxTicketNumber;
        $lastFiveDigits = (int) substr($ticketNumberStr, -5);
        $nextSequence = $lastFiveDigits + 1;

        // Format: draw_id + padded 5-digit sequence
        return (int) ($this->id.str_pad((string) $nextSequence, 5, '0', STR_PAD_LEFT));
    }

    /**
     * Select a random winner from the tickets.
     *
     * Finalization is performed inside a transaction with a row lock so that
     * concurrent calls cannot both select a winner. The is_finalized flag is
     * re-checked under the lock, so a request that lost the race returns null.
     */
    public function selectRandomWinner(): ?DrawTicket
    {
        return DB::transaction(function (): ?DrawTicket {
            // Lock the draw row so concurrent finalizations serialize.
            /** @var static|null $locked */
            $locked = static::whereKey($this->id)->lockForUpdate()->first();

            if (! $locked || $locked->is_finalized) {
                return null;
            }

            /** @var DrawTicket|null $winningTicket */
            $winningTicket = $locked->tickets()->with('user')->inRandomOrder()->first();

            if (! $winningTicket) {
                return null;
            }

            $winningTicket->update(['is_winner' => true]);
            $locked->update([
                'winner_ticket_id' => $winningTicket->id,
                'is_finalized' => true,
            ]);

            // Keep the in-memory instance in sync with the persisted state.
            $this->refresh();

            return $winningTicket;
        });
    }

    /**
     * Get tickets grouped by user.
     *
     * @return Collection<array-key, array{user: User, count: int<0, max>, tickets: Collection<int, DrawTicket>}>
     */
    public function ticketsByUser(): Collection
    {
        return $this->tickets()
            ->with('user')
            ->get()
            ->groupBy('user_id')
            ->map(function ($tickets) {
                /** @var Collection<int, DrawTicket> $tickets */

                return [
                    'user' => $tickets->first()->user,
                    'count' => $tickets->count(),
                    'tickets' => $tickets,
                ];
            });
    }
}
