<?php

namespace Tests\Unit;

use App\Support\RequestSource;
use Illuminate\Http\Request;
use Tests\TestCase;

class RequestSourceTest extends TestCase
{
    private const SSR_URL = 'https://ssr.internal.test/frontend/find-vehicle';
    private const PUBLIC_URL = 'https://api.example.test/frontend/find-vehicle';

    public function testRequestOnConfiguredServerSideHostIsServerSide(): void
    {
        config(['app.server_side_render_hosts' => ['ssr.internal.test']]);

        $this->assertTrue(RequestSource::isServerSide($this->request(self::SSR_URL)));
    }

    public function testServerSideHostMatchIsCaseInsensitive(): void
    {
        config(['app.server_side_render_hosts' => ['SSR.Internal.Test']]);

        $this->assertTrue(RequestSource::isServerSide($this->request(self::SSR_URL)));
    }

    public function testServerSideHostsAcceptWildcards(): void
    {
        config(['app.server_side_render_hosts' => ['*.internal.test']]);

        $this->assertTrue(RequestSource::isServerSide($this->request(self::SSR_URL)));
        $this->assertFalse(RequestSource::isServerSide($this->request(
            self::PUBLIC_URL,
            ['HTTP_ORIGIN' => 'https://www.example.test']
        )));
    }

    public function testBrowserRequestOnPublicHostIsNotServerSide(): void
    {
        config(['app.server_side_render_hosts' => ['ssr.internal.test']]);

        $request = $this->request(
            self::PUBLIC_URL,
            ['HTTP_ORIGIN' => 'https://www.example.test']
        );

        $this->assertFalse(RequestSource::isServerSide($request));
    }

    public function testRequestWithoutOriginOrRefererIsTreatedAsServerSide(): void
    {
        config(['app.server_side_render_hosts' => []]);

        $this->assertTrue(RequestSource::isServerSide($this->request(self::PUBLIC_URL)));
    }

    public function testRefererAloneIsEnoughToLookLikeABrowser(): void
    {
        config(['app.server_side_render_hosts' => []]);

        $request = $this->request(
            self::PUBLIC_URL,
            ['HTTP_REFERER' => 'https://www.example.test/used-cars']
        );

        $this->assertFalse(RequestSource::isServerSide($request));
    }

    public function testFingerprintIsStableForTheSameVisitor(): void
    {
        $server = ['REMOTE_ADDR' => '203.0.113.10', 'HTTP_USER_AGENT' => 'Mozilla/5.0'];

        $this->assertSame(
            RequestSource::visitorFingerprint($this->request('https://api.example.test/a', $server)),
            RequestSource::visitorFingerprint($this->request('https://api.example.test/b', $server))
        );
    }

    public function testFingerprintDiffersByAddressAndUserAgent(): void
    {
        $base = RequestSource::visitorFingerprint($this->request('https://api.example.test/a', [
            'REMOTE_ADDR' => '203.0.113.10',
            'HTTP_USER_AGENT' => 'Mozilla/5.0',
        ]));

        $otherAddress = RequestSource::visitorFingerprint($this->request('https://api.example.test/a', [
            'REMOTE_ADDR' => '203.0.113.11',
            'HTTP_USER_AGENT' => 'Mozilla/5.0',
        ]));

        $otherAgent = RequestSource::visitorFingerprint($this->request('https://api.example.test/a', [
            'REMOTE_ADDR' => '203.0.113.10',
            'HTTP_USER_AGENT' => 'Chrome/1.0',
        ]));

        $this->assertNotSame($base, $otherAddress);
        $this->assertNotSame($base, $otherAgent);
    }

    public function testFingerprintPrefersTheForwardedClientAddress(): void
    {
        $behindProxy = RequestSource::visitorFingerprint($this->request('https://api.example.test/a', [
            'REMOTE_ADDR' => '10.0.0.1',
            'HTTP_X_FORWARDED_FOR' => '203.0.113.10, 10.0.0.1',
            'HTTP_USER_AGENT' => 'Mozilla/5.0',
        ]));

        $direct = RequestSource::visitorFingerprint($this->request('https://api.example.test/a', [
            'REMOTE_ADDR' => '203.0.113.10',
            'HTTP_USER_AGENT' => 'Mozilla/5.0',
        ]));

        $this->assertSame($direct, $behindProxy);
    }

    /**
     * @param array<string, string> $server
     */
    private function request(string $uri, array $server = []): Request
    {
        return Request::create($uri, 'POST', [], [], [], $server);
    }
}
