<?php

namespace Tests\Feature;

use App\Livewire\System\Index as SystemIndex;
use App\Models\AuditEvent;
use App\Models\Setting;
use App\Models\User;
use App\Services\DeploymentService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Livewire\Livewire;
use Mockery;
use Tests\TestCase;

/**
 * The dashboard "request certificate" flow: a DNS pre-check that must NOT trigger ACME on a
 * mismatch (Let's-Encrypt rate-limit protection), and a per-user throttle on the action.
 */
class TlsCertificateRequestTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        Cache::flush();
    }

    public function test_dns_mismatch_does_not_trigger_a_handshake(): void
    {
        $svc = Mockery::mock(DeploymentService::class)->makePartial();
        $svc->shouldReceive('domainResolvesHere')->once()->with('example.com')
            ->andReturn(['ok' => false, 'resolved' => ['203.0.113.9'], 'serverIp' => '198.51.100.1']);
        $svc->shouldReceive('probeCertificate')->never(); // never trigger ACME against a wrong host

        $result = $svc->requestCertificate('example.com');

        $this->assertSame('dns_mismatch', $result['status']);
        $this->assertSame('dns_mismatch', $svc->certStatus()['status']);
    }

    public function test_dns_ok_and_handshake_success_is_issued(): void
    {
        $svc = Mockery::mock(DeploymentService::class)->makePartial();
        $svc->shouldReceive('domainResolvesHere')->once()
            ->andReturn(['ok' => true, 'resolved' => ['198.51.100.1'], 'serverIp' => '198.51.100.1']);
        $svc->shouldReceive('probeCertificate')->once()->with('example.com')->andReturnTrue();

        $this->assertSame('issued', $svc->requestCertificate('example.com')['status']);
    }

    public function test_dns_ok_but_handshake_failure_is_failed(): void
    {
        $svc = Mockery::mock(DeploymentService::class)->makePartial();
        $svc->shouldReceive('domainResolvesHere')->once()->andReturn(['ok' => true, 'resolved' => [], 'serverIp' => null]);
        $svc->shouldReceive('probeCertificate')->once()->andReturnFalse();

        $this->assertSame('failed', $svc->requestCertificate('example.com')['status']);
    }

    public function test_external_tls_mode_is_not_applicable_and_never_probes(): void
    {
        Setting::put('tls_mode', 'external');

        $svc = Mockery::mock(DeploymentService::class)->makePartial();
        $svc->shouldReceive('domainResolvesHere')->never();
        $svc->shouldReceive('probeCertificate')->never();

        $this->assertSame('not_applicable', $svc->requestCertificate('example.com')['status']);
    }

    public function test_request_action_is_throttled_per_user(): void
    {
        $this->mock(DeploymentService::class, function ($m) {
            $m->makePartial();
            // Stub the serving state directly (domain() otherwise reads a shared snapshot file).
            $m->shouldReceive('domain')->andReturn('example.com');
            $m->shouldReceive('externalTls')->andReturn(false);
            // The network-touching request is stubbed; the throttle must cap it at 5 calls.
            $m->shouldReceive('requestCertificate')->times(5)
                ->andReturn(['status' => 'issued', 'checkedAt' => now()->toIso8601String()]);
        });

        $this->actingAs(User::factory()->create(['must_change_password' => false]));

        for ($i = 0; $i < 6; $i++) {
            Livewire::test(SystemIndex::class)->call('requestCertificate');
        }

        // 6th call was throttled before reaching the service; 5 audited requests.
        $this->assertSame(5, AuditEvent::where('action', 'tls.cert_request')->count());
    }
}
