<?php

declare(strict_types=1);

namespace App\Models;

use App\Enums\SubscriptionStatus;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;

/**
 * A user's subscription to a plan.
 *
 * Provider fields are generic on purpose: the payment gateway is pluggable, so
 * nothing here names a specific processor.
 */
class Subscription extends Model
{
    use HasFactory, SoftDeletes;

    /**
     * Status and period boundaries are set only by the billing service in
     * response to verified provider webhooks -- never from a request body.
     *
     * @var list<string>
     */
    protected $fillable = [
        'plan_id',
        'provider',
        'metadata',
    ];

    /** @return array<string, string> */
    protected function casts(): array
    {
        return [
            'status' => SubscriptionStatus::class,
            'current_period_start' => 'datetime',
            'current_period_end' => 'datetime',
            'trial_ends_at' => 'datetime',
            'canceled_at' => 'datetime',
            'ends_at' => 'datetime',
            'metadata' => 'array',
        ];
    }

    /**
     * Keeps `active_marker` in step with the status.
     *
     * The column exists purely to carry a unique index that binds only while a
     * subscription is entitling, guaranteeing one live plan per account. It is
     * maintained here rather than at each call site so no write path can leave
     * the invariant broken -- a retried checkout or a duplicated provider
     * webhook must not be able to give an account two plans.
     */
    protected static function booted(): void
    {
        static::saving(function (self $subscription): void {
            $subscription->active_marker = $subscription->status->grantsEntitlements()
                ? $subscription->user_id
                : null;
        });
    }

    /** @return BelongsTo<User, $this> */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    /** @return BelongsTo<Plan, $this> */
    public function plan(): BelongsTo
    {
        return $this->belongsTo(Plan::class);
    }

    /** @param  Builder<static>  $query */
    public function scopeEntitling(Builder $query): void
    {
        $query->whereIn('status', [
            SubscriptionStatus::Trialing->value,
            SubscriptionStatus::Active->value,
            SubscriptionStatus::PastDue->value,
        ]);
    }

    /**
     * Whether plan entitlements currently apply.
     *
     * A lapsed period revokes entitlement even if the status column has not yet
     * caught up, so a missed webhook cannot silently extend a paid plan.
     */
    public function isEntitling(): bool
    {
        if (! $this->status->grantsEntitlements()) {
            return false;
        }

        return $this->current_period_end === null || $this->current_period_end->isFuture();
    }

    public function onTrial(): bool
    {
        return $this->trial_ends_at !== null && $this->trial_ends_at->isFuture();
    }
}
