<?php

use App\Livewire\Admin\Instances as AdminInstances;
use App\Livewire\Admin\Overview;
use App\Livewire\Admin\Revenue;
use App\Models\Customer;
use App\Models\Host;
use App\Models\Instance;
use App\Models\ProvisioningRun;
use App\Models\Subscription;
use Illuminate\Support\Facades\DB;
use Livewire\Livewire;

/**
 * The console reports what is in the database, or nothing.
 *
 * Its front page, its instance list and its revenue page were hard-coded
 * fiction: forty-two customers, €7,842 a month, hosts named pve-fsn-1..3, a
 * twelve-month growth curve. It read like a running business and measured
 * nothing. These tests exist so that cannot come back quietly.
 */
it('reports an empty estate as empty, not as a going concern', function () {
    // The strongest statement of the old bug: with nothing in the database the
    // console still claimed 42 customers, 39 instances, 4 hosts and €7,842 a
    // month. The figures are asserted directly rather than searched for in the
    // markup — "42" also occurs inside Livewire's own component ids.
    //
    // The mailer is pinned to a delivering one: phpunit.xml forces
    // MAIL_MAILER=array so a test suite cannot send mail, and the console
    // rightly warns that nothing is being delivered. That warning is about the
    // ENVIRONMENT, not about the estate, and this test is about an empty estate.
    config()->set('mail.default', 'smtp');
    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Overview::class)
        ->assertViewHas('kpis', function (array $kpis) {
            [$customers, $instances, $hosts, $mrr] = $kpis;

            return $customers['value'] === '0'
                && $instances['value'] === '0'
                && $hosts['value'] === '0'
                && str_contains($mrr['value'], '0,00');
        })
        ->assertViewHas('hostLoad', [])
        ->assertViewHas('runs', [])
        // Not notices === [] any more (Task 12): an installation with nothing
        // configured is not the same as an installation with nothing WRONG.
        // Readiness::blocking() reports a real, non-invented gap here (nine
        // unmet checks on a bare database — no Stripe key, no DNS token, no
        // scheduler heartbeat yet, and so on), and hiding that behind an
        // empty notices list would be exactly the kind of fiction this file
        // exists to catch. What is still true of a truly empty estate: no
        // INCIDENT-shaped notice invents itself out of nothing — the one
        // notice present is the readiness gap, and nothing else.
        ->assertViewHas('notices', fn (array $notices) => collect($notices)->pluck('route')->all() === ['admin.readiness'])
        ->assertSee(__('admin.no_open_runs'));
});

it('counts what is actually there', function () {
    $host = Host::factory()->create(['status' => 'active', 'total_gb' => 1000, 'last_seen_at' => now()]);
    $customer = Customer::factory()->create(['status' => 'active']);
    Instance::factory()->create([
        'customer_id' => $customer->id, 'host_id' => $host->id,
        'status' => 'active', 'quota_gb' => 250,
    ]);

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Overview::class)
        ->assertSee('1')
        ->assertSee($host->name);
});

it('reports host capacity the way placement counts it', function () {
    // The figure on the dashboard has to be the figure placement acts on.
    // Placement counts the VM disk allocation — not the smaller Nextcloud
    // quota — against capacity AFTER the host's reserve. A dashboard doing its
    // own arithmetic would call a host comfortable while orders were already
    // being refused on it.
    $host = Host::factory()->create([
        'status' => 'active', 'total_gb' => 1000, 'reserve_pct' => 20, 'last_seen_at' => now(),
    ]);
    $customer = Customer::factory()->create();
    Instance::factory()->create([
        'customer_id' => $customer->id, 'host_id' => $host->id,
        'status' => 'active', 'quota_gb' => 250, 'disk_gb' => 600,
    ]);

    expect($host->committedGb())->toBe(600)
        ->and($host->freeGb())->toBe(800);

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Overview::class)
        // 600 of the 800 GB placement may actually use, not 250 of 1000.
        ->assertSee('600 / 800 GB')
        ->assertDontSee('250 / 1000 GB');
});

it('states monthly revenue off the contracts, with a yearly term divided', function () {
    $customer = Customer::factory()->create();

    Subscription::factory()->create([
        'customer_id' => $customer->id,
        'status' => 'active', 'term' => 'monthly', 'price_cents' => 17900, 'currency' => 'EUR',
    ]);
    // A year paid up front is not twelve times the monthly revenue.
    Subscription::factory()->create([
        'customer_id' => $customer->id,
        'status' => 'active', 'term' => 'yearly', 'price_cents' => 120000, 'currency' => 'EUR',
    ]);
    // A cancelled contract bills nothing.
    Subscription::factory()->create([
        'customer_id' => $customer->id,
        'status' => 'cancelled', 'term' => 'monthly', 'price_cents' => 99900, 'currency' => 'EUR',
    ]);

    // 179,00 monthly + 1.200,00 a year ÷ 12 = 179,00 + 100,00 = 279,00
    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Revenue::class)
        ->assertSee('279,00')
        ->assertDontSee('999,00');
});

/**
 * `admin.systems_ok` can no longer be asserted on a bare install (Task 12): a
 * brand new console always starts with at least one readiness gap, which is
 * exactly the point of Readiness::blocking() existing at all — a badge
 * claiming nothing is wrong would itself be the invented-data problem this
 * file is about. What this test is actually for still holds: an INCIDENT
 * notice (a failed run, a silent host…) appears only once one is real, on
 * top of whatever readiness gaps a fresh install already carries.
 */
it('raises an incident notice only when something is actually wrong', function () {
    // The suite deliberately delivers no mail, and the console now says so on
    // the overview. Pinned so this test's "clean" estate is clean of the MAIL
    // notice too — the readiness gaps above it stay, by design.
    config()->set('mail.default', 'smtp');

    $clean = Livewire::actingAs(operator('Owner'), 'operator')->test(Overview::class);
    $clean->assertDontSee(__('admin.notice.failed_runs', ['n' => 1]));

    ProvisioningRun::factory()->create(['status' => ProvisioningRun::STATUS_FAILED]);

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(Overview::class)
        ->assertSee(__('admin.notice.failed_runs', ['n' => 1]));
});

it('gets the same committed storage whether or not the sum was preloaded', function () {
    // Two code paths now answer one question. If they ever disagree, the
    // dashboard and placement disagree — which is the bug this replaced.
    $host = Host::factory()->create(['total_gb' => 1000, 'reserve_pct' => 10]);
    $customer = Customer::factory()->create();

    Instance::factory()->create(['customer_id' => $customer->id, 'host_id' => $host->id, 'status' => 'active', 'disk_gb' => 120]);
    Instance::factory()->create(['customer_id' => $customer->id, 'host_id' => $host->id, 'status' => 'failed', 'vmid' => 900, 'disk_gb' => 80]);
    // Failed before a VM existed — it holds nothing.
    Instance::factory()->create(['customer_id' => $customer->id, 'host_id' => $host->id, 'status' => 'failed', 'vmid' => null, 'disk_gb' => 500]);

    $onDemand = Host::query()->find($host->id)->committedGb();
    $preloaded = Host::query()
        ->withSum(['instances as committed_disk_gb' => fn ($q) => $q->occupyingHost()], 'disk_gb')
        ->find($host->id)
        ->committedGb();

    expect($onDemand)->toBe(200)->and($preloaded)->toBe($onDemand);

    // A host with nothing on it arrives preloaded-but-null (SUM over no rows).
    // It must read as zero without going back to the database — that is the
    // case the preload exists for.
    $bare = Host::factory()->create(['total_gb' => 500]);
    $preloadedBare = Host::query()
        ->withSum(['instances as committed_disk_gb' => fn ($q) => $q->occupyingHost()], 'disk_gb')
        ->find($bare->id);

    DB::enableQueryLog();
    expect($preloadedBare->committedGb())->toBe(0);
    expect(DB::getQueryLog())->toBeEmpty();
    DB::disableQueryLog();
});

it('lists real instances and claims no version it does not record', function () {
    $host = Host::factory()->create(['name' => 'pve-test-9']);
    $customer = Customer::factory()->create(['name' => 'Beispielkunde GmbH']);
    Instance::factory()->create([
        'customer_id' => $customer->id, 'host_id' => $host->id,
        'subdomain' => 'beispielkunde', 'vmid' => 4711, 'quota_gb' => 500, 'status' => 'active',
    ]);

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(AdminInstances::class)
        ->assertSee('beispielkunde')
        ->assertSee('Beispielkunde GmbH')
        ->assertSee('pve-test-9')
        ->assertSee('4711')
        // The Nextcloud version is not recorded anywhere, so it is not shown.
        ->assertDontSee('NC 31');
});
