<?php

namespace Tests\Feature;

use App\Livewire\Health\Index;
use App\Models\HealthCheck;
use App\Models\User;
use App\Services\HealthService;
use App\Support\Confirm\ConfirmToken;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Mockery;
use Tests\TestCase;

class HealthComponentTest extends TestCase
{
    use RefreshDatabase;

    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }

    private function operator(): User
    {
        return User::factory()->operator()->create(['must_change_password' => false]);
    }

    private function viewer(): User
    {
        return User::factory()->viewer()->create(['must_change_password' => false]);
    }

    public function test_viewer_cannot_open_health(): void
    {
        $this->actingAs($this->viewer())->get('/health')->assertForbidden();
    }

    public function test_operator_can_open_health(): void
    {
        $this->actingAs($this->operator())->get('/health')->assertOk();
    }

    public function test_operator_adds_an_http_check_and_it_is_audited(): void
    {
        $this->actingAs($this->operator());

        Livewire::test(Index::class)
            ->set('type', 'http')
            ->set('target', 'https://svc.example.com/health')
            ->call('addCheck')
            ->assertHasNoErrors();

        $this->assertDatabaseHas('health_checks', ['type' => 'http', 'target' => 'https://svc.example.com/health', 'port' => null]);
        $this->assertDatabaseHas('audit_events', ['action' => 'health.check_add']);
    }

    public function test_http_check_rejects_a_non_url(): void
    {
        $this->actingAs($this->operator());

        Livewire::test(Index::class)
            ->set('type', 'http')
            ->set('target', 'not a url; rm -rf')
            ->call('addCheck')
            ->assertHasErrors('target');

        $this->assertDatabaseCount('health_checks', 0);
    }

    public function test_tcp_check_requires_a_host_and_port(): void
    {
        $this->actingAs($this->operator());

        Livewire::test(Index::class)
            ->set('type', 'tcp')
            ->set('target', 'db.example.com')
            ->set('port', 5432)
            ->call('addCheck')
            ->assertHasNoErrors();

        $this->assertDatabaseHas('health_checks', ['type' => 'tcp', 'target' => 'db.example.com', 'port' => 5432]);
    }

    public function test_tcp_check_rejects_a_missing_port(): void
    {
        $this->actingAs($this->operator());

        Livewire::test(Index::class)
            ->set('type', 'tcp')
            ->set('target', 'db.example.com')
            ->set('port', null)
            ->call('addCheck')
            ->assertHasErrors('port');
    }

    public function test_scan_probes_each_check(): void
    {
        $this->actingAs($this->operator());
        HealthCheck::create(['type' => 'http', 'target' => 'https://svc.example.com']);

        $health = Mockery::mock(HealthService::class);
        $health->shouldReceive('probe')->once()->andReturn(['ok' => true, 'latency' => 42, 'detail' => 'HTTP 200']);
        app()->instance(HealthService::class, $health);

        Livewire::test(Index::class)
            ->call('scan')
            ->assertOk()
            ->assertSet('ready', true)
            ->assertSee(__('health.status_up'))
            ->assertViewHas('up', 1);
    }

    public function test_operator_deletes_a_check_via_a_confirmed_token(): void
    {
        $this->actingAs($this->operator());
        $check = HealthCheck::create(['type' => 'http', 'target' => 'https://gone.example.com']);
        $token = ConfirmToken::issue('healthCheckDeleted', ['uuid' => $check->uuid], 'health.check_delete', $check->target, null);
        ConfirmToken::confirm($token);

        Livewire::test(Index::class)->call('deleteCheck', $token)->assertOk();

        $this->assertDatabaseMissing('health_checks', ['id' => $check->id]);
    }
}
