<?php

use App\Models\Branch;
use App\Models\Member;
use App\Models\User;
use Illuminate\Support\Facades\DB;

test('phone-prefix search matches by digit prefix', function () {
    $branch = Branch::factory()->create();
    $target = Member::factory()->create(['branch_id' => $branch->id, 'phone_primary' => '263771234567']);
    Member::factory()->create(['branch_id' => $branch->id, 'phone_primary' => '263779999999']);
    $superAdmin = User::factory()->superAdmin()->create();

    $response = $this->actingAs($superAdmin)->getJson('/api/v1/members/search?q=263771');

    $response->assertOk();
    $ids = collect($response->json('data'))->pluck('id');
    expect($ids)->toContain($target->id);
    expect($ids)->toHaveCount(1);
});

test('member number exact search', function () {
    $branch = Branch::factory()->create();
    $target = Member::factory()->create(['branch_id' => $branch->id]);
    Member::factory()->create(['branch_id' => $branch->id]);
    $superAdmin = User::factory()->superAdmin()->create();

    $response = $this->actingAs($superAdmin)->getJson('/api/v1/members/search?q='.$target->member_number);

    $response->assertOk();
    expect($response->json('data'))->toHaveCount(1);
    expect($response->json('data.0.id'))->toBe($target->id);
});

/**
 * MySQL's InnoDB FULLTEXT index does not see rows inserted earlier in the
 * same uncommitted transaction (a documented InnoDB limitation), and Pest's
 * RefreshDatabase wraps each test in one — so a MATCH...AGAINST query
 * against fixture data created in the same test always returns zero rows
 * here, regardless of whether the query itself is correct (confirmed via a
 * manual tinker run against committed data: the identical query correctly
 * matches). These tests therefore verify the generated SQL directly rather
 * than depending on MySQL to execute the FULLTEXT match live.
 */
test('fulltext multi-token name search builds an AND-ed boolean-mode query', function () {
    $branch = Branch::factory()->create();
    $superAdmin = User::factory()->superAdmin()->create();

    $captured = null;
    DB::listen(function ($query) use (&$captured) {
        if (str_contains($query->sql, 'MATCH(first_name')) {
            $captured = $query;
        }
    });

    $this->actingAs($superAdmin)->getJson('/api/v1/members/search?q=Tendai Moyo')->assertOk();

    expect($captured)->not->toBeNull();
    expect($captured->bindings[0])->toBe('+Tendai* +Moyo*');
});

test('a 2-character token falls back to a LIKE prefix match', function () {
    $branch = Branch::factory()->create();
    $target = Member::factory()->create(['branch_id' => $branch->id, 'first_name' => 'Al', 'last_name' => 'Wongwe']);
    Member::factory()->create(['branch_id' => $branch->id, 'first_name' => 'Zanele', 'last_name' => 'Ndlovu']);
    $superAdmin = User::factory()->superAdmin()->create();

    $response = $this->actingAs($superAdmin)->getJson('/api/v1/members/search?q=Al');

    $response->assertOk();
    $ids = collect($response->json('data'))->pluck('id');
    expect($ids)->toContain($target->id);
});

test('operator-character injection returns 200 with a sanitized boolean-mode query', function () {
    $branch = Branch::factory()->create();
    Member::factory()->create(['branch_id' => $branch->id, 'first_name' => 'Tendai', 'last_name' => 'Moyo']);
    $superAdmin = User::factory()->superAdmin()->create();

    $captured = null;
    DB::listen(function ($query) use (&$captured) {
        if (str_contains($query->sql, 'MATCH(first_name')) {
            $captured = $query;
        }
    });

    $response = $this->actingAs($superAdmin)->getJson('/api/v1/members/search?'.http_build_query(['q' => '"tendai*) +x']));

    $response->assertOk();
    // The raw operator characters must never reach the AGAINST expression unescaped.
    expect($captured->bindings[0])->not->toContain('"')->not->toContain(')');
});

test('typeahead query is capped at 10 results', function () {
    $branch = Branch::factory()->create();
    Member::factory()->count(15)->create(['branch_id' => $branch->id, 'last_name' => 'Moyo']);
    $superAdmin = User::factory()->superAdmin()->create();

    $captured = null;
    DB::listen(function ($query) use (&$captured) {
        if (str_contains($query->sql, 'from `members`') && str_contains($query->sql, 'limit')) {
            $captured = $query;
        }
    });

    $this->actingAs($superAdmin)->getJson('/api/v1/members/search?q=Moyo')->assertOk();

    expect($captured->sql)->toContain('limit 10');
});

test('search results are scoped to accessible branches', function () {
    $branchA = Branch::factory()->create();
    $branchB = Branch::factory()->create();
    $branchAdminA = User::factory()->branchAdmin()->create(['branch_id' => $branchA->id]);

    $captured = null;
    DB::listen(function ($query) use (&$captured) {
        if (str_contains($query->sql, 'MATCH(first_name')) {
            $captured = $query;
        }
    });

    $this->actingAs($branchAdminA)->getJson('/api/v1/members/search?q=Tendai Moyo')->assertOk();

    expect($captured->sql)->toContain('`branch_id` in');
    expect($captured->bindings)->toContain($branchA->id);
});

test('unauthenticated search is rejected', function () {
    $this->getJson('/api/v1/members/search?q=Tendai')->assertStatus(401);
});
