<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;
use RuntimeException;

/**
 * A plan line — the thing that has a name.
 *
 * "Team" stays Team through every price change and every resize. Orders,
 * instances and contracts have referred to plans by `key` since day one, so the
 * key is the one part of a family that must never move.
 */
class PlanFamily extends Model
{
    use HasFactory;
    use HasUuids;

    protected $guarded = [];

    protected static function booted(): void
    {
        static::updating(function (self $family) {
            // Orders, instances and every subscription snapshot store this
            // string. Renaming it does not rename those — it orphans them, and
            // a checkout in flight for this family stops matching its own
            // version. Display names are what the `name` column is for.
            if ($family->isDirty('key')) {
                throw new RuntimeException(
                    'A plan family key is permanent; existing orders and contracts refer to it by name. '.
                    'Change the display name instead.'
                );
            }
        });

        static::deleting(function (self $family) {
            if ($family->versions()->whereNotNull('published_at')->exists()) {
                throw new RuntimeException(
                    'A plan family with published versions cannot be deleted; customers are contracted to them. '.
                    'Switch sales off instead — it disappears from the shop and stays on record.'
                );
            }

            // Only drafts left. The foreign key restricts, so they have to go
            // explicitly — and nothing was ever promised on a draft.
            $family->versions->each->delete();
        });
    }

    protected function casts(): array
    {
        return [
            'tier' => 'integer',
            'sales_enabled' => 'boolean',
            'is_recommended' => 'boolean',
            'internal' => 'boolean',
        ];
    }

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

    public function versions(): HasMany
    {
        return $this->hasMany(PlanVersion::class);
    }

    /**
     * The version on sale at a given moment, or null.
     *
     * Resolved with sole(): two overlapping windows are a mistake we want to
     * hear about, not one we want silently resolved by whichever row the
     * database happened to return first.
     *
     * @throws \Illuminate\Database\MultipleRecordsFoundException on overlap
     */
    public function versionAt(?Carbon $at = null): ?PlanVersion
    {
        $query = $this->versions()->available($at);

        return $query->count() === 0 ? null : $query->sole();
    }

    /** On sale right now: not killed by the switch, and a version is running. */
    public function isSellableAt(?Carbon $at = null): bool
    {
        return $this->sales_enabled && $this->versionAt($at) !== null;
    }
}
