<?php

namespace Tests\Tenant;

use Illuminate\Support\Facades\Queue;
use Illuminate\Testing\TestResponse;
use Mtc\MercuryDataModels\SearchTermLog;
use Tests\StubSearchTermFilter;
use Tests\TenantTestCase;

class SearchTermTrackingTest extends TenantTestCase
{
    private const BROWSER_HEADERS = [
        'Origin' => 'https://www.example.test',
        'User-Agent' => 'Mozilla/5.0 (Macintosh)',
        'X-Forwarded-For' => '203.0.113.10',
    ];

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

        // Term matching runs on a MySQL fulltext index that SQLite has no equivalent for, and is
        // not what these tests are about.
        config(['filter.filters.search' => StubSearchTermFilter::class]);

        // The test request arrives on "localhost", so anything else marks it as browser traffic.
        config(['app.server_side_render_hosts' => ['ssr.internal.test']]);
    }

    public function testBrowserSearchIsCounted(): void
    {
        $this->search('BMW X5')->assertOk();

        $log = SearchTermLog::query()->sole();
        $this->assertSame('BMW X5', $log->term);
        $this->assertSame('bmw x5', $log->normalized_term);
        $this->assertSame(1, $log->hits);
        $this->assertSame(now()->format('Y-m-d'), $log->date->format('Y-m-d'));
    }

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

        $this->search('BMW X5')->assertOk();

        $this->assertSame(0, SearchTermLog::query()->count());
    }

    public function testRequestWithoutBrowserHeadersIsNotCounted(): void
    {
        $this->postJson(route('vehicles.index'), $this->payload('BMW X5'))->assertOk();

        $this->assertSame(0, SearchTermLog::query()->count());
    }

    public function testSearchWithoutATermIsNotCounted(): void
    {
        $this->postJson(route('vehicles.index'), [], self::BROWSER_HEADERS)->assertOk();

        $this->assertSame(0, SearchTermLog::query()->count());
    }

    public function testBlankTermIsNotCounted(): void
    {
        $this->search('   ')->assertOk();

        $this->assertSame(0, SearchTermLog::query()->count());
    }

    public function testRepeatRequestsFromTheSameVisitorOnlyCountOnce(): void
    {
        $this->search('BMW X5')->assertOk();
        $this->search('BMW X5')->assertOk();
        $this->search('bmw x5')->assertOk();

        $this->assertSame(1, SearchTermLog::query()->sole()->hits);
    }

    public function testTheSameTermFromAnotherVisitorCountsAgain(): void
    {
        $this->search('BMW X5')->assertOk();
        $this->search('BMW X5', ['X-Forwarded-For' => '203.0.113.99'])->assertOk();

        $this->assertSame(2, SearchTermLog::query()->sole()->hits);
    }

    public function testDifferentTermsAreCountedSeparately(): void
    {
        $this->search('BMW X5')->assertOk();
        $this->search('Audi Q5')->assertOk();

        $this->assertSame(2, SearchTermLog::query()->count());
        $this->assertSame(
            ['audi q5', 'bmw x5'],
            SearchTermLog::query()->orderBy('normalized_term')->pluck('normalized_term')->all()
        );
    }

    public function testTrackingFailureDoesNotBreakTheSearch(): void
    {
        SearchTermLog::query()->getConnection()->getSchemaBuilder()->drop('search_term_logs');

        $this->search('BMW X5')->assertOk();
    }

    /**
     * @param array<string, string> $headers
     */
    private function search(string $term, array $headers = []): TestResponse
    {
        return $this->postJson(
            route('vehicles.index'),
            $this->payload($term),
            array_merge(self::BROWSER_HEADERS, $headers)
        );
    }

    /**
     * @return array{selections: array<int, array{type: string, value: string}>}
     */
    private function payload(string $term): array
    {
        return ['selections' => [['type' => 'search', 'value' => $term]]];
    }
}
