<?php

namespace App\Services\Proxmox;

use App\Models\Host;
use App\Models\PlanVersion;
use Throwable;

/**
 * Whether every published plan's VM template actually EXISTS on a node, not
 * merely that a `template_vmid` is filled in somewhere.
 *
 * Asks the same question as the onboarding step VerifyVmTemplate, but over
 * ALL active hosts instead of one, and BEFORE a purchase instead of after:
 * ProvisioningChecks::vm_template (Task 8) is a field check only — it cannot
 * tell a `template_vmid` that was typed by mistake from one that is really
 * there, and asking Proxmox on every page load does not belong on a
 * readiness page. This is the on-demand button that actually asks.
 */
final class VmTemplateCheck
{
    public function __construct(private ProxmoxClient $pve) {}

    /** @return array<string, mixed> */
    public function run(): array
    {
        $required = PlanVersion::requiredTemplateVmids();

        // A catalogue with no published version requires no template. The
        // same case VerifyVmTemplate explicitly lets through.
        if ($required === []) {
            return ['ok' => true, 'reason' => 'nothing_published'];
        }

        $hosts = Host::query()->where('status', 'active')->whereNotNull('api_token_ref')->get();

        if ($hosts->isEmpty()) {
            return ['ok' => false, 'reason' => 'no_usable_host'];
        }

        $missing = [];
        $unverifiable = [];

        foreach ($required as $vmid) {
            $found = false;
            $aHostFailedToAnswer = false;

            foreach ($hosts as $host) {
                try {
                    // isTemplate, not vmExists — the same correction made in
                    // VerifyVmTemplate, and the same hole: this check ran
                    // BEFORE a purchase, so an abandoned build numbered 9000
                    // reported the catalogue sellable and the failure surfaced
                    // once the money had already moved.
                    if ($this->pve->forHost($host)->isTemplate($host->node ?? 'pve', $vmid)) {
                        $found = true;
                        break;
                    }
                } catch (Throwable) {
                    // A host that is not answering right now is not proof of
                    // a missing template — it might be sitting on exactly
                    // this host. Keep looking at the others; a later "yes"
                    // still wins outright (see $found above).
                    $aHostFailedToAnswer = true;

                    continue;
                }
            }

            if ($found) {
                continue;
            }

            // Two different verdicts, not one: every host answered "no" is a
            // confirmed gap (build the template); some host never answered
            // is merely unverified (ask again once it is back). Telling an
            // operator to build something that may already exist on a host
            // that is simply not responding right now sends them to fix the
            // wrong problem.
            if ($aHostFailedToAnswer) {
                $unverifiable[] = $vmid;
            } else {
                $missing[] = $vmid;
            }
        }

        if ($missing !== []) {
            $result = ['ok' => false, 'reason' => 'template_missing', 'vmids' => $missing];

            if ($unverifiable !== []) {
                // Named separately, never merged into vmids: one is
                // confirmed and actionable, the other merely unanswered.
                $result['unverifiable'] = $unverifiable;
            }

            return $result;
        }

        if ($unverifiable !== []) {
            return ['ok' => false, 'reason' => 'hosts_unreachable', 'vmids' => $unverifiable];
        }

        return ['ok' => true, 'reason' => 'present'];
    }
}
