<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use RuntimeException;

/**
 * One booked module, at the price it was booked at.
 *
 * Frozen for the same reason the subscription is: the customer agreed to a
 * figure, and raising it afterwards would rewrite that agreement. Cancelling
 * sets `cancelled_at` rather than deleting the row — what someone was paying,
 * and until when, is evidence.
 */
class SubscriptionAddon extends Model
{
    use HasFactory;
    use HasUuids;

    /** What the customer agreed to. */
    public const FROZEN = ['subscription_id', 'addon_key', 'price_cents', 'currency', 'quantity', 'booked_at', 'pack_gb', 'pack_disk_gb'];

    protected $guarded = [];

    protected static function booted(): void
    {
        static::updating(function (self $addon) {
            $frozen = array_intersect(array_keys($addon->getDirty()), self::FROZEN);

            if ($frozen !== []) {
                throw new RuntimeException(
                    'A booked module is frozen at its booked price; tried to change: '.implode(', ', $frozen).
                    '. Cancel it and book it again at today\'s price instead.'
                );
            }
        });
    }

    protected function casts(): array
    {
        return [
            'price_cents' => 'integer',
            'quantity' => 'integer',
            'pack_gb' => 'integer',
            'pack_disk_gb' => 'integer',
            'booked_at' => 'datetime',
            'cancelled_at' => 'datetime',
            // The end this booking has an appointment with. A cancellation is
            // monthly and lands at the end of the term already paid for, so the
            // module keeps running until this date passes — see
            // App\Actions\BookAddon::cancelAtPeriodEnd().
            'cancels_at' => 'datetime',
            'granted_at' => 'datetime',
            'granted_until' => 'datetime',
            'catalogue_price_cents' => 'integer',
        ];
    }

    public function uniqueIds(): array
    {
        return ['uuid'];
    }

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

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

    /** The operator who gave this module away, when it was a grant. */
    public function grantedBy(): BelongsTo
    {
        return $this->belongsTo(Operator::class, 'granted_by');
    }

    public function isGranted(): bool
    {
        return $this->granted_at !== null;
    }

    public function scopeActive(Builder $query): Builder
    {
        return $query->whereNull('cancelled_at');
    }

    /** Net per month for this booking, packs included. */
    public function monthlyCents(): int
    {
        return $this->price_cents * $this->quantity;
    }

    public function isActive(): bool
    {
        return $this->cancelled_at === null;
    }

    /** Cancelled, still running, and billed for no further term. */
    public function endsAtPeriodEnd(): bool
    {
        return $this->cancelled_at === null && $this->cancels_at !== null;
    }
}
