<?php

use App\Services\Provisioning\ServerMarket;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

/**
 * What a machine costs today, answered on the page that asks the question.
 *
 * Read-only by construction. This orders nothing: buying a server is a
 * contract, and a bug in a capacity calculation must not be able to sign one.
 */
beforeEach(fn () => Cache::forget('server-market'));

/** One offer in the feed's own shape. */
function offer(array $overrides = []): array
{
    return array_merge([
        'cpu' => 'Ryzen 5 3600',
        'ram_size' => 64,
        'is_ecc' => true,
        'hdd_hr' => ['2x SSD M.2 NVMe 1 TB'],
        'serverDiskData' => ['nvme' => [1024, 1024], 'sata' => [], 'hdd' => []],
        'datacenter' => 'FSN1-DC1',
        'price' => 42.0,
    ], $overrides);
}

it('counts a mirrored pair once, not twice', function () {
    // A machine's usable space is not the sum of its disks on a platform whose
    // whole promise is "your data survives a disk dying".
    Http::fake(['*' => Http::response(['server' => [offer()]])]);

    expect(app(ServerMarket::class)->offers(500)[0]['flash_gb'])->toBe(1024);
});

it('leaves out machines that could not take the largest parked order', function () {
    // 1.25× the largest single package: the mirror plus the host's own
    // footprint. An offer that only just fits is not an offer.
    Http::fake(['*' => Http::response(['server' => [offer()]])]);

    expect(app(ServerMarket::class)->offers(900))->toBe([]);
});

it('leaves out spinning disks and non-ECC memory', function () {
    // Both are product decisions, not preferences: one VM per customer puts
    // every database on the same disks, and ECC is part of what the nightly
    // backup promise rests on.
    Http::fake(['*' => Http::response(['server' => [
        offer(['is_ecc' => false]),
        offer(['serverDiskData' => ['nvme' => [], 'sata' => [], 'hdd' => [4096, 4096]]]),
        offer(['ram_size' => 16]),
    ]])]);

    expect(app(ServerMarket::class)->offers(100))->toBe([]);
});

it('lists the cheapest first', function () {
    Http::fake(['*' => Http::response(['server' => [
        offer(['price' => 80.0]),
        offer(['price' => 30.0]),
        offer(['price' => 55.0]),
    ]])]);

    expect(collect(app(ServerMarket::class)->offers(100))->pluck('price')->all())
        ->toBe([30.0, 55.0, 80.0]);
});

it('reads the disk description the feed actually sends', function () {
    // The feed sends a LIST of disk descriptions, not a string. Casting that to
    // string printed the word "Array" into the console and raised a warning
    // that took the whole page down with it.
    Http::fake(['*' => Http::response(['server' => [offer(['hdd_hr' => ['2x SSD M.2 NVMe 1 TB']])]])]);

    expect(app(ServerMarket::class)->offers(100)[0]['disks'])->toBe('2x SSD M.2 NVMe 1 TB');
});

it('says nothing rather than failing when the list cannot be reached', function () {
    // An outside list being down is not a reason for the capacity page to fail.
    // It shows the queue either way; the prices are the convenience.
    Http::fake(['*' => fn () => throw new RuntimeException('connection refused')]);

    expect(app(ServerMarket::class)->offers(100))->toBe([]);
});

it('asks the provider once per quarter hour, not once per page view', function () {
    Http::fake(['*' => Http::response(['server' => [offer()]])]);

    app(ServerMarket::class)->offers(100);
    app(ServerMarket::class)->offers(100);
    app(ServerMarket::class)->offers(100);

    Http::assertSentCount(1);
});
