<?php

use App\Support\LocalTime;
use App\Livewire\Admin\Maintenance;
use App\Mail\MaintenanceAnnouncementMail;
use App\Models\Customer;
use App\Models\Host;
use App\Models\Instance;
use App\Models\MaintenanceNotification;
use App\Models\MaintenanceWindow;
use Illuminate\Support\Facades\Mail;
use Livewire\Livewire;

// operator() helper defined in RbacTest.

function affectedCustomerOn(Host $host): Customer
{
    $customer = Customer::factory()->create();
    Instance::factory()->create(['customer_id' => $customer->id, 'host_id' => $host->id, 'status' => 'active']);

    return $customer;
}

it('publishes a window and emails affected customers once (idempotent)', function () {
    Mail::fake();
    $host = Host::factory()->active()->create();
    $customer = affectedCustomerOn($host);

    Livewire::actingAs(operator('Owner'), 'operator')->test(Maintenance::class)
        ->set('title', 'Netz-Wartung')
        ->set('startsAt', LocalTime::toField(now()->addDay()))
        ->set('endsAt', LocalTime::toField(now()->addDay()->addHours(2)))
        ->set('hostIds', [$host->id])
        ->call('publish')->assertHasNoErrors();

    $window = MaintenanceWindow::query()->where('title', 'Netz-Wartung')->first();
    expect($window)->not->toBeNull()->and($window->state)->toBe('scheduled')
        ->and($window->hosts()->count())->toBe(1);
    Mail::assertQueued(MaintenanceAnnouncementMail::class, 1);
    expect(MaintenanceNotification::query()->where('customer_id', $customer->id)->count())->toBe(1);

    // The ledger's unique (window, customer, event) makes re-announcing a no-op.
    expect(fn () => MaintenanceNotification::query()->create([
        'maintenance_window_id' => $window->id, 'customer_id' => $customer->id, 'event' => 'announcement', 'email' => $customer->email,
    ]))->toThrow(Illuminate\Database\UniqueConstraintViolationException::class);

    // Resending while the first mail is still pending must NOT duplicate.
    Livewire::actingAs(operator('Owner'), 'operator')->test(Maintenance::class)->call('resend', $window->uuid);
    Mail::assertQueued(MaintenanceAnnouncementMail::class, 1);
});

it('emails a cancellation to customers who were announced', function () {
    Mail::fake();
    $host = Host::factory()->active()->create();
    $customer = affectedCustomerOn($host);
    $window = MaintenanceWindow::factory()->scheduled()->create();
    $window->hosts()->attach($host->id);
    // Simulate the customer having actually received the announcement.
    \App\Models\MaintenanceNotification::create([
        'maintenance_window_id' => $window->id, 'customer_id' => $customer->id, 'event' => 'announcement', 'email' => $customer->email, 'sent_at' => now(),
    ]);

    Livewire::actingAs(operator('Owner'), 'operator')->test(Maintenance::class)->call('cancel', $window->uuid);

    expect($window->fresh()->state)->toBe('cancelled');
    Mail::assertQueued(\App\Mail\MaintenanceCancelledMail::class, 1);
});

it('scopes the per-instance badge to that instance host', function () {
    $hostA = Host::factory()->active()->create();
    $hostB = Host::factory()->active()->create();
    $customer = Customer::factory()->create();
    $instA = Instance::factory()->create(['customer_id' => $customer->id, 'host_id' => $hostA->id, 'status' => 'active']);
    $instB = Instance::factory()->create(['customer_id' => $customer->id, 'host_id' => $hostB->id, 'status' => 'active']);

    // A window on host A only.
    $window = MaintenanceWindow::factory()->scheduled()->create();
    $window->hosts()->attach($hostA->id);

    expect(MaintenanceWindow::forInstance($instA))->toHaveCount(1)
        ->and(MaintenanceWindow::forInstance($instB))->toHaveCount(0)   // not another instance's window
        ->and(MaintenanceWindow::bannerFor($customer))->toHaveCount(1); // customer-level banner still shows
});

it('requires a host to publish', function () {
    Livewire::actingAs(operator('Owner'), 'operator')->test(Maintenance::class)
        ->set('title', 'X')
        ->set('startsAt', LocalTime::toField(now()->addDay()))
        ->set('endsAt', LocalTime::toField(now()->addDay()->addHours(2)))
        ->set('hostIds', [])
        ->call('publish')->assertHasErrors(['hostIds']);
});

it('forbids non-privileged roles from managing maintenance', function () {
    Livewire::actingAs(operator('Support'), 'operator')->test(Maintenance::class)
        ->set('title', 'X')->set('startsAt', LocalTime::toField(now()->addDay()))
        ->set('endsAt', LocalTime::toField(now()->addDay()->addHours(2)))
        ->call('saveDraft')->assertForbidden();
});

it('shows the banner to an affected customer within the horizon', function () {
    $host = Host::factory()->active()->create();
    $customer = affectedCustomerOn($host);
    $window = MaintenanceWindow::factory()->scheduled()->create(['starts_at' => now()->addDay(), 'ends_at' => now()->addDay()->addHour()]);
    $window->hosts()->attach($host->id);

    $banner = MaintenanceWindow::bannerFor($customer);
    expect($banner)->toHaveCount(1)->and($banner->first()->id)->toBe($window->id);

    // A customer on another host sees nothing.
    $other = affectedCustomerOn(Host::factory()->active()->create());
    expect(MaintenanceWindow::bannerFor($other))->toHaveCount(0);
});

it('fills the end time from a duration chip', function () {
    $owner = operator('Owner');

    // With a start set, the chip just adds the minutes.
    $component = Livewire::actingAs($owner, 'operator')->test(App\Livewire\Admin\Maintenance::class)
        ->set('startsAt', '2026-08-01T22:00')
        ->call('setDuration', 120);

    expect($component->get('endsAt'))->toBe('2026-08-02T00:00')
        ->and($component->instance()->durationMinutes())->toBe(120);

    // Without one, it starts at the next half hour rather than doing nothing.
    $fresh = Livewire::actingAs($owner, 'operator')->test(App\Livewire\Admin\Maintenance::class)
        ->call('setDuration', 60);

    expect($fresh->get('startsAt'))->not->toBe('')
        ->and($fresh->instance()->durationMinutes())->toBe(60);
});

it('reports no duration while the times are unusable', function () {
    $component = Livewire::actingAs(operator('Owner'), 'operator')->test(App\Livewire\Admin\Maintenance::class);

    expect($component->instance()->durationMinutes())->toBeNull();

    // A half-typed value must not blow up the live-updating form.
    $component->set('startsAt', '2026-08-')->set('endsAt', '');
    expect($component->instance()->durationMinutes())->toBeNull();

    // An end before the start is not a duration either.
    $component->set('startsAt', '2026-08-01T22:00')->set('endsAt', '2026-08-01T21:00');
    expect($component->instance()->durationMinutes())->toBeNull();
});
