<?php

namespace App\Provisioning\Steps\Customer;

use App\Models\Instance;
use App\Models\Order;
use App\Models\ProvisioningRun;
use App\Models\Subscription;
use App\Provisioning\Contracts\ProvisioningStep;
use App\Provisioning\Steps\ManagesRunResources;
use App\Services\Proxmox\ProxmoxClient;
use RuntimeException;

/**
 * Shared helpers for customer-provisioning steps: subject/instance/plan
 * resolution, guest-agent command execution, and idempotency breadcrumbs.
 */
abstract class CustomerStep implements ProvisioningStep
{
    use ManagesRunResources;

    public function label(): string
    {
        return 'provisioning.step.'.$this->key();
    }

    public function maxDuration(): int
    {
        return 300;
    }

    protected function order(ProvisioningRun $run): Order
    {
        /** @var Order $order */
        $order = $run->subject;

        return $order;
    }

    protected function instance(ProvisioningRun $run): ?Instance
    {
        $id = $run->context('instance_id');

        return $id ? Instance::query()->find($id) : null;
    }

    /** The contract this run is carrying out, or null if none was opened. */
    protected function subscription(ProvisioningRun $run): ?Subscription
    {
        return Subscription::query()->where('order_id', $this->order($run)->id)->first();
    }

    /**
     * What the customer actually bought — read from their frozen snapshot, never
     * from config('provisioning.plans').
     *
     * The catalogue says what we sell TODAY. Sizing a machine from it would mean
     * that shrinking a plan resizes the VM of a customer who bought the larger
     * one, on their next run. Empty when no contract exists, so ValidateOrder
     * can fail the run closed instead of quietly falling back to the catalogue.
     *
     * @return array<string, mixed>
     */
    protected function plan(ProvisioningRun $run): array
    {
        $subscription = $this->subscription($run);

        if ($subscription === null) {
            return [];
        }

        return $subscription->only([
            'plan', 'term', 'price_cents', 'currency', 'quota_gb', 'traffic_gb',
            'seats', 'ram_mb', 'cores', 'disk_gb', 'performance', 'template_vmid', 'tier',
        ]);
    }

    /**
     * Run a command inside the guest via qemu-guest-agent. Throws on a non-zero
     * exit code so the runner turns it into a retry.
     */
    protected function guest(ProxmoxClient $pve, ProvisioningRun $run, string $command): string
    {
        $node = (string) $run->context('node');
        $vmid = (int) $run->context('vmid');

        $result = $pve->guestExec($node, $vmid, $command);

        if ((int) ($result['exitcode'] ?? 1) !== 0) {
            throw new RuntimeException("guest exec failed (exit {$result['exitcode']}): {$command}");
        }

        return (string) ($result['out-data'] ?? '');
    }
}
