<?php

use App\Livewire\Admin\ConfirmDeleteDatacenter;
use App\Livewire\Admin\Datacenters;
use App\Livewire\Admin\HostCreate;
use App\Models\Datacenter;
use App\Models\Host;
use App\Models\User;
use Livewire\Livewire;

// admin() helper is defined in HostManagementTest (global Pest function).

it('gates the datacenters page', function () {
    $this->get(route('admin.datacenters'))->assertRedirect(route('admin.login'));
    $this->actingAs(User::factory()->create(['is_admin' => false]))->get(route('admin.datacenters'))->assertForbidden();
    $this->actingAs(admin(), 'operator')->get(route('admin.datacenters'))->assertOk()->assertSee(__('datacenters.title'));
});

it('creates a datacenter', function () {
    Livewire::actingAs(admin(), 'operator')
        ->test(Datacenters::class)
        ->set('code', 'NBG')
        ->set('name', 'Nürnberg')
        ->set('location', 'DE')
        ->call('save')
        ->assertHasNoErrors();

    expect(Datacenter::query()->where('code', 'nbg')->where('name', 'Nürnberg')->exists())->toBeTrue();
});

it('rejects a duplicate datacenter code', function () {
    Datacenter::query()->firstOrCreate(['code' => 'fsn'], ['name' => 'Falkenstein']);

    Livewire::actingAs(admin(), 'operator')
        ->test(Datacenters::class)
        ->set('code', 'fsn')->set('name', 'Falkenstein')
        ->call('save')
        ->assertHasErrors(['code']);
});

it('edits a datacenter name and country via the modal', function () {
    $dc = Datacenter::factory()->create(['code' => 'nbg', 'name' => 'Old', 'location' => 'AT']);

    Livewire::actingAs(admin(), 'operator')
        ->test(\App\Livewire\Admin\EditDatacenter::class, ['uuid' => $dc->uuid])
        ->set('name', 'Nürnberg')
        ->set('location', 'DE')
        ->call('save')
        ->assertHasNoErrors();

    $dc->refresh();
    expect($dc->name)->toBe('Nürnberg')->and($dc->location)->toBe('DE')->and($dc->code)->toBe('nbg');
});

/**
 * Renaming a code, and why it stops being allowed.
 *
 * The code was immutable, which made a typo permanent. It cannot be freely
 * mutable either: hosts.datacenter references it, every past order stores it as
 * plain text, and — the part no cascade reaches — each host's DNS name was
 * minted from it and registered at the provider, while the machine's own
 * hostname was set from it as it was built. Rename a code with hosts in it and
 * you get a datacenter called hel full of machines called fsn-01.
 *
 * Free while nothing references it, which is the case that matters: a typo
 * noticed shortly after creating one.
 */
it('renames a code while nothing depends on it', function () {
    $dc = Datacenter::factory()->create(['code' => 'nbg', 'name' => 'Nürnberg']);

    Livewire::actingAs(admin(), 'operator')
        ->test(\App\Livewire\Admin\EditDatacenter::class, ['uuid' => $dc->uuid])
        ->set('code', 'NBG2')
        ->call('save')
        ->assertHasNoErrors();

    // Lowercased on the way in, like the create form does it.
    expect($dc->refresh()->code)->toBe('nbg2');
});

it('leaves the code alone once a host is placed there, whatever the browser sends back', function () {
    // fsn and hel exist from the migration that created the table.
    $dc = Datacenter::query()->firstOrCreate(['code' => 'fsn'], ['name' => 'Falkenstein']);
    Host::factory()->create(['datacenter' => 'fsn']);

    // set() on a locked field is exactly the forged request this guards
    // against: the input is not rendered, so nothing honest can send it.
    Livewire::actingAs(admin(), 'operator')
        ->test(\App\Livewire\Admin\EditDatacenter::class, ['uuid' => $dc->uuid])
        ->set('code', 'hel')
        ->set('name', 'Falkenstein Süd')
        ->call('save')
        ->assertHasNoErrors();

    expect($dc->refresh()->code)->toBe('fsn')
        ->and($dc->name)->toBe('Falkenstein Süd');   // the rest of the form still saves
});

it('leaves the code alone once an order carries it, even with no hosts left', function () {
    // Orders are the reference no foreign key protects: a plain string column,
    // and the record of what a customer actually bought.
    $dc = Datacenter::query()->firstOrCreate(['code' => 'fsn'], ['name' => 'Falkenstein']);
    \App\Models\Order::factory()->create(['datacenter' => 'fsn']);

    Livewire::actingAs(admin(), 'operator')
        ->test(\App\Livewire\Admin\EditDatacenter::class, ['uuid' => $dc->uuid])
        ->set('code', 'hel')
        ->call('save')
        ->assertHasNoErrors();

    expect($dc->refresh()->code)->toBe('fsn');
});

it('holds a renamed code to the same rules as a new one', function () {
    // hel comes from the migration that created the table.
    $dc = Datacenter::factory()->create(['code' => 'nbg']);

    $component = Livewire::actingAs(admin(), 'operator')
        ->test(\App\Livewire\Admin\EditDatacenter::class, ['uuid' => $dc->uuid]);

    // Not a DNS label — the code becomes one (fsn-01.node.clupilot.com).
    $component->set('code', 'nbg_1')->call('save')->assertHasErrors(['code']);

    // Already taken.
    $component->set('code', 'hel')->call('save')->assertHasErrors(['code']);

    expect($dc->refresh()->code)->toBe('nbg');
});

it('lets a datacenter keep its own code when only the name is edited', function () {
    // The unique rule has to ignore this row, or saving anything at all would
    // fail on the code the record already holds.
    $dc = Datacenter::factory()->create(['code' => 'nbg', 'name' => 'Old']);

    Livewire::actingAs(admin(), 'operator')
        ->test(\App\Livewire\Admin\EditDatacenter::class, ['uuid' => $dc->uuid])
        ->set('name', 'Nürnberg')
        ->call('save')
        ->assertHasNoErrors();

    expect($dc->refresh()->name)->toBe('Nürnberg')->and($dc->code)->toBe('nbg');
});

/**
 * Which building, not which city.
 *
 * A provider runs several datacenters within one location, on separate power
 * and separate uplinks. "Falkenstein (fsn)" twice over is not a choice anybody
 * can make — and placing a second machine for redundancy is exactly when it
 * matters which hall it lands in.
 */
it('stores the specific datacenter and shows it where hosts are placed', function () {
    Livewire::actingAs(admin(), 'operator')
        ->test(Datacenters::class)
        ->set('code', 'nbg')
        ->set('name', 'Nürnberg')
        ->set('facility', 'nbg-dc-15')
        ->call('save')
        ->assertHasNoErrors();

    expect(Datacenter::query()->where('code', 'nbg')->value('facility'))->toBe('nbg-dc-15');

    Livewire::actingAs(admin(), 'operator')
        ->test(HostCreate::class)
        ->assertSee('nbg-dc-15');
});

it('keeps the specific datacenter optional, and empty rather than blank', function () {
    // '' in the column would read as "there is one and it has no name" to
    // every @if that decides whether to print it.
    $dc = Datacenter::factory()->create(['code' => 'nbg', 'facility' => 'nbg-dc-3']);

    Livewire::actingAs(admin(), 'operator')
        ->test(\App\Livewire\Admin\EditDatacenter::class, ['uuid' => $dc->uuid])
        ->set('facility', '')
        ->call('save')
        ->assertHasNoErrors();

    expect($dc->refresh()->facility)->toBeNull();
});

it('rejects an invalid country code on edit', function () {
    $dc = Datacenter::factory()->create(['code' => 'nbg', 'location' => 'DE']);

    Livewire::actingAs(admin(), 'operator')
        ->test(\App\Livewire\Admin\EditDatacenter::class, ['uuid' => $dc->uuid])
        ->set('location', 'ZZ')->call('save')->assertHasErrors(['location']);
});

it('deletes an unused datacenter', function () {
    $dc = Datacenter::factory()->create(['code' => 'nbg']);

    Livewire::actingAs(admin(), 'operator')->test(ConfirmDeleteDatacenter::class, ['uuid' => $dc->uuid])->call('delete');

    expect(Datacenter::query()->where('uuid', $dc->uuid)->exists())->toBeFalse();
});

it('refuses to delete a datacenter that still has hosts', function () {
    $dc = Datacenter::factory()->create(['code' => 'nbg']);
    Host::factory()->create(['datacenter' => 'nbg']);

    Livewire::actingAs(admin(), 'operator')->test(ConfirmDeleteDatacenter::class, ['uuid' => $dc->uuid])
        ->assertSet('hostCount', 1)
        ->call('delete');

    expect(Datacenter::query()->where('uuid', $dc->uuid)->exists())->toBeTrue();
});

it('suspends and reactivates a customer', function () {
    $customer = \App\Models\Customer::factory()->create(['status' => 'active']);

    Livewire::actingAs(admin(), 'operator')->test(\App\Livewire\Admin\Customers::class)
        ->call('toggleSuspend', $customer->uuid);
    expect($customer->fresh()->status)->toBe('suspended');

    Livewire::actingAs(admin(), 'operator')->test(\App\Livewire\Admin\Customers::class)
        ->call('toggleSuspend', $customer->uuid);
    expect($customer->fresh()->status)->toBe('active');
});

it('does not let the suspend toggle resurrect a closed customer', function () {
    $customer = \App\Models\Customer::factory()->create(['status' => 'closed', 'closed_at' => now()]);

    Livewire::actingAs(admin(), 'operator')->test(\App\Livewire\Admin\Customers::class)
        ->call('toggleSuspend', $customer->uuid);

    $customer->refresh();
    expect($customer->status)->toBe('closed')->and($customer->closed_at)->not->toBeNull();
});

it('toggles a datacenter active flag', function () {
    $dc = Datacenter::factory()->create(['active' => true]);

    Livewire::actingAs(admin(), 'operator')->test(Datacenters::class)->call('toggle', $dc->uuid);

    expect($dc->fresh()->active)->toBeFalse();
});

it('offers only known datacenter codes when adding a host', function () {
    Datacenter::query()->firstOrCreate(['code' => 'fsn'], ['name' => 'Falkenstein']);

    // unknown code rejected
    Livewire::actingAs(admin(), 'operator')
        ->test(HostCreate::class)
        ->set('datacenter', 'ghost')->set('public_ip', '203.0.113.5')->set('root_password', 'supersecret')
        ->call('save')
        ->assertHasErrors(['datacenter']);

    // known code accepted (defaults to the first active datacenter on mount)
    Livewire::actingAs(admin(), 'operator')
        ->test(HostCreate::class)
        ->assertSet('datacenter', 'fsn');
});

it('rejects creating a host in an inactive datacenter', function () {
    Datacenter::query()->firstOrCreate(['code' => 'fsn'], ['name' => 'Falkenstein']);
    Datacenter::factory()->create(['code' => 'old', 'active' => false]);

    Livewire::actingAs(admin(), 'operator')
        ->test(HostCreate::class)
        ->set('datacenter', 'old')->set('public_ip', '203.0.113.6')->set('root_password', 'supersecret')
        ->call('save')
        ->assertHasErrors(['datacenter']);
});

it('can switch a location off, and does not evict what already runs there', function () {
    // The column and the scope existed; the form did not offer them, so a
    // datacenter could be created and never switched off again.
    $dc = App\Models\Datacenter::factory()->create(['active' => true]);
    App\Models\Host::factory()->create(['datacenter' => $dc->code]);

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(App\Livewire\Admin\EditDatacenter::class, ['uuid' => $dc->uuid])
        ->assertSet('active', true)
        ->set('active', false)
        ->call('save')
        ->assertHasNoErrors();

    expect($dc->refresh()->active)->toBeFalse()
        // Switching off stops NEW placements; it does not evict what runs there.
        ->and(App\Models\Host::query()->where('datacenter', $dc->code)->exists())->toBeTrue();
});

it('warns about running hosts only when it actually switches a location off', function () {
    // An already-inactive location announced that it had just been switched off
    // every time its name was edited.
    $dc = App\Models\Datacenter::factory()->create(['active' => false]);
    App\Models\Host::factory()->create(['datacenter' => $dc->code]);

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(App\Livewire\Admin\EditDatacenter::class, ['uuid' => $dc->uuid])
        ->set('name', 'Neuer Name')
        ->call('save')
        ->assertDispatched('notify', message: __('datacenters.updated'));
});
