<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use App\Enums\BillingCycle;
use App\Enums\SubscriptionStatus;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

/**
 * A tenant's right to use this product for a period of time.
 *
 * Access decisions everywhere in the app funnel through `accessState()`:
 *   full      — normal operation
 *   read_only — term ended, still inside the grace window
 *   locked    — billing screens only
 */
class Subscription extends Model
{
    protected $fillable = [
        'tenant_id', 'saas_product_id', 'plan_id', 'billing_cycle', 'status',
        'amount', 'currency', 'starts_at', 'ends_at', 'grace_ends_at',
        'cancelled_at', 'cancellation_reason', 'is_trial', 'auto_renew',
        'features_snapshot', 'limits_snapshot', 'previous_subscription_id',
        'admin_notes',
    ];

    protected function casts(): array
    {
        return [
            'billing_cycle' => BillingCycle::class,
            'status' => SubscriptionStatus::class,
            'amount' => 'decimal:2',
            'starts_at' => 'datetime',
            'ends_at' => 'datetime',
            'grace_ends_at' => 'datetime',
            'cancelled_at' => 'datetime',
            'is_trial' => 'boolean',
            'auto_renew' => 'boolean',
            'features_snapshot' => 'array',
            'limits_snapshot' => 'array',
        ];
    }

    public function tenant(): BelongsTo
    {
        return $this->belongsTo(Tenant::class);
    }

    public function plan(): BelongsTo
    {
        return $this->belongsTo(Plan::class);
    }

    public function invoices(): HasMany
    {
        return $this->hasMany(SubscriptionInvoice::class)->latest('id');
    }

    public function payments(): HasMany
    {
        return $this->hasMany(SubscriptionPayment::class)->latest('id');
    }

    public function latestPayment(): ?SubscriptionPayment
    {
        return $this->payments()->first();
    }

    /*
    |--------------------------------------------------------------------------
    | Access state
    |--------------------------------------------------------------------------
    */

    /** @return 'full'|'read_only'|'locked' */
    public function accessState(): string
    {
        if ($this->tenant?->isSuspended()) {
            return 'locked';
        }

        $status = $this->effectiveStatus();

        return match (true) {
            $status->grantsAccess() => 'full',
            $status->isReadOnly() => 'read_only',
            default => 'locked',
        };
    }

    /**
     * The stored status, corrected for the passage of time.
     *
     * A row written as `active` last month is really `grace` or `expired`
     * today; rather than depend on a cron having run, the truth is derived
     * from the dates on every read.
     */
    public function effectiveStatus(): SubscriptionStatus
    {
        $status = $this->status;

        if (in_array($status, [
            SubscriptionStatus::Cancelled,
            SubscriptionStatus::Rejected,
            SubscriptionStatus::Suspended,
        ], true)) {
            return $status;
        }

        if (! $this->ends_at) {
            return $status;
        }

        if ($this->ends_at->isFuture()) {
            return $status;
        }

        $graceEnd = $this->grace_ends_at
            ?? $this->ends_at->copy()->addDays((int) config('thinkrax.grace_days', 3));

        return $graceEnd->isFuture()
            ? SubscriptionStatus::Grace
            : SubscriptionStatus::Expired;
    }

    public function isActive(): bool
    {
        return $this->accessState() === 'full';
    }

    public function isReadOnly(): bool
    {
        return $this->accessState() === 'read_only';
    }

    public function isLocked(): bool
    {
        return $this->accessState() === 'locked';
    }

    public function onTrial(): bool
    {
        return $this->is_trial && $this->effectiveStatus() === SubscriptionStatus::Trial;
    }

    public function awaitingVerification(): bool
    {
        return $this->status === SubscriptionStatus::PendingVerification;
    }

    /** Whole days left before the term ends (0 once it has). */
    public function daysRemaining(): int
    {
        if (! $this->ends_at) {
            return 0;
        }

        return max(0, (int) now()->startOfDay()->diffInDays($this->ends_at->copy()->startOfDay(), false));
    }

    public function daysOfGraceRemaining(): int
    {
        $graceEnd = $this->grace_ends_at
            ?? $this->ends_at?->copy()->addDays((int) config('thinkrax.grace_days', 3));

        if (! $graceEnd) {
            return 0;
        }

        return max(0, (int) now()->startOfDay()->diffInDays($graceEnd->copy()->startOfDay(), false));
    }

    public function isExpiringWithin(int $days): bool
    {
        return $this->ends_at !== null
            && $this->ends_at->isFuture()
            && $this->daysRemaining() <= $days;
    }

    /*
    |--------------------------------------------------------------------------
    | Entitlements
    |--------------------------------------------------------------------------
    |
    | Snapshots are preferred over the live plan so that editing a plan in the
    | admin panel never retroactively downgrades a tenant mid-term.
    |
    */

    /** @return array<int,string> */
    public function features(): array
    {
        return $this->features_snapshot ?? $this->plan?->features ?? [];
    }

    /** @return array<string,int> */
    public function limits(): array
    {
        return $this->limits_snapshot ?? $this->plan?->limits ?? [];
    }
}
