<?php

namespace Tests\Feature;

use App\Http\Middleware\PanelScheme;
use App\Livewire\System\Index as SystemIndex;
use App\Models\Setting;
use App\Models\User;
use App\Services\DeploymentService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Livewire\Livewire;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Tests\TestCase;

class ExternalTlsModeTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();

        // Remove the active-domain snapshot so domain() falls back to the DB Setting
        // (configuredDomain()), letting tests control the domain via Setting::put().
        $snapshot = storage_path('framework/active-domain');
        if (is_file($snapshot)) {
            unlink($snapshot);
        }
    }

    protected function tearDown(): void
    {
        // Restore an empty snapshot (bare-IP) after each test so the dev stack is
        // unaffected; the entrypoint will overwrite it on next container start anyway.
        file_put_contents(storage_path('framework/active-domain'), '');

        parent::tearDown();
    }

    private function runPanelScheme(Request $request): SymfonyResponse
    {
        $mw = new PanelScheme(app(DeploymentService::class));

        return $mw->handle($request, fn () => new Response('ok'));
    }

    public function test_external_tls_flag_reflects_the_setting(): void
    {
        $deployment = app(DeploymentService::class);
        $this->assertFalse($deployment->externalTls(), 'default is caddy');

        $deployment->setTlsMode('external');
        $this->assertTrue(app(DeploymentService::class)->externalTls());

        $deployment->setTlsMode('garbage'); // invalid → caddy
        $this->assertFalse(app(DeploymentService::class)->externalTls());
    }

    public function test_caddy_ask_denies_in_external_mode_but_approves_in_caddy_mode(): void
    {
        Setting::put('panel_domain', 'panel.example.com');

        // caddy mode: the configured domain is approved
        $this->get('/_caddy/ask?domain=panel.example.com')->assertOk();

        // external mode: even the configured domain is denied (no ACME)
        app(DeploymentService::class)->setTlsMode('external');
        $this->get('/_caddy/ask?domain=panel.example.com')->assertForbidden();
    }

    public function test_panel_scheme_skips_the_https_redirect_in_external_mode(): void
    {
        Setting::put('panel_domain', 'panel.example.com');
        app(DeploymentService::class)->setTlsMode('external');

        $request = Request::create('http://panel.example.com/dashboard');
        $response = $this->runPanelScheme($request);

        $this->assertSame(200, $response->getStatusCode(), 'external mode must NOT redirect to https');
        $this->assertSame('ok', $response->getContent());
    }

    public function test_panel_scheme_still_forces_https_in_caddy_mode(): void
    {
        Setting::put('panel_domain', 'panel.example.com'); // caddy mode (default)

        $request = Request::create('http://panel.example.com/dashboard');
        $response = $this->runPanelScheme($request);

        $this->assertSame(301, $response->getStatusCode());
        $this->assertStringStartsWith('https://panel.example.com', $response->headers->get('Location'));
    }

    public function test_panel_scheme_still_refuses_a_foreign_host_in_external_mode(): void
    {
        Setting::put('panel_domain', 'panel.example.com');
        app(DeploymentService::class)->setTlsMode('external');

        $this->expectException(HttpException::class);
        $this->runPanelScheme(Request::create('http://evil.example.com/dashboard'));
    }

    public function test_status_card_does_not_claim_lets_encrypt_in_external_mode(): void
    {
        $this->actingAs(User::factory()->create(['must_change_password' => false]));
        Setting::put('panel_domain', 'panel.example.com');
        app(DeploymentService::class)->setTlsMode('external');

        Livewire::test(SystemIndex::class)
            ->assertSee(__('system.tls_active_external_title'))
            ->assertDontSee('Let\'s Encrypt');
    }

    public function test_reverb_client_uses_wss_on_443_for_the_active_domain(): void
    {
        Setting::put('panel_domain', 'panel.example.com');
        app(DeploymentService::class)->setTlsMode('external');

        // External proxy terminates TLS; the request arrives here as plain HTTP on :80.
        app()->instance('request', Request::create('http://panel.example.com/dashboard'));

        $rc = app(DeploymentService::class)->reverbClient();
        $this->assertSame('panel.example.com', $rc['host']);
        $this->assertSame(443, $rc['port'], 'WS must use the HTTPS front door (443), not the internal :80');
        $this->assertSame('https', $rc['scheme'], 'WS must be wss on the active domain');
    }

    public function test_reverb_client_stays_plain_ws_on_the_bare_ip_recovery_path(): void
    {
        Setting::put('panel_domain', 'panel.example.com');

        app()->instance('request', Request::create('http://10.0.0.5/dashboard'));

        $rc = app(DeploymentService::class)->reverbClient();
        $this->assertSame('10.0.0.5', $rc['host']);
        $this->assertSame('http', $rc['scheme'], 'the bare-IP recovery path has no TLS');
    }

    public function test_external_mode_forces_https_url_generation_for_the_active_domain(): void
    {
        Setting::put('panel_domain', 'panel.example.com');
        app(DeploymentService::class)->setTlsMode('external');

        // External proxy terminates TLS; the app itself sees a plain-HTTP request here.
        $response = $this->runPanelScheme(Request::create('http://panel.example.com/dashboard'));
        $this->assertSame(200, $response->getStatusCode());

        // Generated URLs (assets, routes) must be https://<domain> so the HTTPS page's CSP
        // ('self') does not block them — the bug that left the panel with no CSS/JS.
        $this->assertSame('https://panel.example.com/build/app.css', url('build/app.css'));
        $this->assertStringStartsWith('https://panel.example.com', route('login'));
    }
}
