<?php

use App\Models\Operator;
use App\Models\User;
use App\Support\Settings;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Schema;

it('serves the site normally while it is public', function () {
    Settings::set('site.public', true);

    $this->get('/')->assertOk();
    $this->get('/login')->assertOk();
});

it('shows outsiders a placeholder while the site is hidden', function () {
    Settings::set('site.public', false);

    $response = $this->withServerVariables(['REMOTE_ADDR' => '203.0.113.50'])->get('/');

    // 503, not 200: a 200 invites search engines to index the placeholder as
    // the site's content, and that is much harder to undo than to prevent.
    $response->assertStatus(503)
        ->assertSee(__('coming_soon.title'))
        ->assertHeader('X-Robots-Tag', 'noindex, nofollow, noarchive')
        ->assertHeader('Retry-After', 3600);

    // The portal is hidden too, not just the marketing page.
    $this->withServerVariables(['REMOTE_ADDR' => '203.0.113.50'])->get('/login')->assertStatus(503);
});

it('lets the management VPN through', function () {
    Settings::set('site.public', false);
    config()->set('admin_access.trusted_ranges', ['10.66.0.0/24']);

    $this->withServerVariables(['REMOTE_ADDR' => '10.66.0.4'])->get('/')->assertOk();
    $this->withServerVariables(['REMOTE_ADDR' => '10.66.9.4'])->get('/')->assertStatus(503);
});

it('lets a signed-in operator see the real site from anywhere', function () {
    Settings::set('site.public', false);

    $this->actingAs(operator('Support'), 'operator')
        ->withServerVariables(['REMOTE_ADDR' => '203.0.113.50'])
        ->get('/')
        ->assertOk();

    // Both matter: the operator guard instance keeps its user across requests
    // within one test regardless of which guard is "default", so it has to be
    // logged out explicitly — and the customer has to be put on 'web'
    // explicitly, because a bare actingAs() would target whatever guard is
    // still default (still 'operator', from the call above) rather than reset
    // to it.
    Auth::guard('operator')->logout();

    // A customer account is not staff — the portal stays hidden for them.
    $this->actingAs(User::factory()->create(), 'web')
        ->withServerVariables(['REMOTE_ADDR' => '203.0.113.50'])
        ->get('/')
        ->assertStatus(503);
});

it('does not let a disabled operator bypass the hidden site', function () {
    // EnsureAdmin already refuses a disabled operator (isActive()); this gate
    // used to check only Auth::guard('operator')->check(), which stays true
    // for a disabled operator's live session — an indefinite bypass of
    // maintenance/private-site mode for anyone disabled while signed in.
    Settings::set('site.public', false);

    $op = operator('Support');
    $op->update(['disabled_at' => now()]);

    $this->actingAs($op, 'operator')
        ->withServerVariables(['REMOTE_ADDR' => '203.0.113.50'])
        ->get('/')
        ->assertStatus(503);

    // Chosen deliberately: merely ignored, not logged out — see
    // PublicSiteGate's docblock for why (this gate only decides what ONE
    // request sees; logging out would ripple into whatever else that
    // session is doing, e.g. a concurrent console tab).
    expect(Auth::guard('operator')->check())->toBeTrue();
});

it('does not let an operator holding no console role bypass the hidden site', function () {
    // Mirrors EnsureAdmin's other half: isActive() alone is not enough,
    // the operator must also hold a role that reaches the console.
    Settings::set('site.public', false);

    $op = Operator::factory()->create(); // no role assigned

    $this->actingAs($op, 'operator')
        ->withServerVariables(['REMOTE_ADDR' => '203.0.113.50'])
        ->get('/')
        ->assertStatus(503);
});

it('keeps the console and the webhook reachable while hidden', function () {
    Settings::set('site.public', false);

    // Otherwise the switch could only ever be flipped once.
    $this->actingAs(operator('Owner'), 'operator')
        ->withServerVariables(['REMOTE_ADDR' => '203.0.113.50'])
        ->get(route('admin.settings'))
        ->assertOk();

    // Stripe must still be able to deliver a paid order: whatever the webhook
    // makes of an empty body, the gate must not be what answers.
    $this->withServerVariables(['REMOTE_ADDR' => '203.0.113.50'])
        ->post('/webhooks/stripe', [])
        ->assertStatus(200);
});

it('tells crawlers to stay away while hidden', function () {
    Settings::set('site.public', false);

    $this->withServerVariables(['REMOTE_ADDR' => '203.0.113.50'])
        ->get('/robots.txt')
        ->assertOk()
        ->assertSee('Disallow: /');

    Settings::set('site.public', true);
    $this->get('/robots.txt')->assertOk()->assertSee('Allow: /');
});

it('flips visibility from the console, and only with the capability', function () {
    Settings::set('site.public', true);

    Livewire\Livewire::actingAs(operator('Support'), 'operator')->test(App\Livewire\Admin\Settings::class)
        ->call('toggleSiteVisibility')
        ->assertForbidden();
    expect(Settings::bool('site.public', true))->toBeTrue();

    Livewire\Livewire::actingAs(operator('Owner'), 'operator')->test(App\Livewire\Admin\Settings::class)
        ->call('toggleSiteVisibility');
    expect(Settings::bool('site.public', true))->toBeFalse();
});

it('keeps serving when the settings table is unavailable', function () {
    // A deploy that runs new code before migrations, or a database blip: the
    // gate must fall back to "public", not take the whole site down with a 500.
    Schema::drop('app_settings');

    $this->get('/')->assertOk();
});

it('does not leave the portal drivable through Livewire while hidden', function () {
    Settings::set('site.public', false);

    // A customer with a live session must not be able to keep using the portal
    // through the shared Livewire endpoint while it is supposed to be offline.
    $this->actingAs(User::factory()->create())
        ->withServerVariables(['REMOTE_ADDR' => '203.0.113.50'])
        ->post('/livewire/update', [])
        ->assertStatus(503);

    // Operators keep working — that is what the console needs. Whatever
    // Livewire makes of an empty payload, the gate must not be what answers.
    $status = $this->actingAs(operator('Owner'), 'operator')
        ->withServerVariables(['REMOTE_ADDR' => '203.0.113.50'])
        ->post('/livewire/update', [])
        ->getStatusCode();

    expect($status)->not->toBe(503);
});
