<?php

use App\Services\DashboardService;
use App\Services\RecurrenceService;
use App\Services\TaskService;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use Tests\Support\Auth\AuthTestTrait;
use Tests\Support\Categories\CategoryTestTrait;

/**
 * @internal
 */
final class SearchServiceTest extends CIUnitTestCase
{
    use DatabaseTestTrait;
    use AuthTestTrait;
    use CategoryTestTrait;

    protected $namespace = 'App';
    protected $refresh   = true;
    protected $migrate   = true;
    protected $seed      = '';

    private TaskService $tasks;
    private RecurrenceService $recurrence;
    private DashboardService $dash;

    protected function setUp(): void
    {
        parent::setUp();
        $this->tasks      = new TaskService();
        $this->recurrence = new RecurrenceService();
        $this->dash       = new DashboardService();
        $this->dash->todayOverride = '2026-08-29';
    }

    /**
     * @param array<string, string> $override
     *
     * @return array<string, mixed>
     */
    private function makeTask(int $userId, int $categoryId, string $title, array $override = []): array
    {
        $result = $this->tasks->create($userId, $this->tasks->collect(array_merge([
            'title'       => $title,
            'priority'    => '1',
            'category_id' => (string) $categoryId,
            'due_date'    => '2026-08-29',
            'due_time'    => '',
        ], $override)));
        $this->assertTrue($result['ok'] ?? false, (string) ($result['message'] ?? 'create failed'));

        return $result['task'];
    }

    /**
     * @param array<string, string> $override
     *
     * @return array{date: string, status: string, category: string, from: string, to: string}
     */
    private function filters(array $override = []): array
    {
        return array_merge($this->dash->defaults(), $override);
    }

    /**
     * @return list<string>
     */
    private function titles(array $rows): array
    {
        return array_values(array_map(static fn (array $row): string => (string) $row['title'], $rows));
    }

    public function testNormalizeTrimsAndEmptySearchReturnsNoRows(): void
    {
        $user = $this->createUser('search-svc-empty@example.com', 1);
        $cat  = $this->createCategory((int) $user['id'], 'Personal');
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'Send Client Invoice');

        $this->assertSame('invoice', $this->dash->normalizeSearchQuery('  invoice  '));
        $this->assertSame('', $this->dash->normalizeSearchQuery('   '));
        $this->assertSame([], $this->dash->searchOccurrences((int) $user['id'], $this->filters(['date' => 'all']), '   '));
        $this->assertSame([], $this->dash->searchOccurrences((int) $user['id'], $this->filters(['date' => 'all']), ''));
    }

    public function testPartialCaseInsensitiveTitleMatch(): void
    {
        $user = $this->createUser('search-svc-match@example.com', 1);
        $cat  = $this->createCategory((int) $user['id'], 'Personal');
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'Send Client Invoice');
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'Buy milk');

        $all = $this->filters(['date' => 'all']);
        $this->assertSame(['Send Client Invoice'], $this->titles($this->dash->searchOccurrences((int) $user['id'], $all, 'invoice')));
        $this->assertSame(['Send Client Invoice'], $this->titles($this->dash->searchOccurrences((int) $user['id'], $all, 'Invoice')));
        $this->assertSame(['Send Client Invoice'], $this->titles($this->dash->searchOccurrences((int) $user['id'], $all, 'client')));
        $this->assertSame(['Send Client Invoice'], $this->titles($this->dash->searchOccurrences((int) $user['id'], $all, 'send cli')));
        $this->assertSame(['Send Client Invoice'], $this->titles($this->dash->searchOccurrences((int) $user['id'], $all, 'Send')));
        $this->assertSame(['Buy milk'], $this->titles($this->dash->searchOccurrences((int) $user['id'], $all, 'milk')));
        $this->assertSame([], $this->titles($this->dash->searchOccurrences((int) $user['id'], $all, 'xyzzy')));
    }

    public function testSearchDoesNotMatchCategoryOrPriorityLabels(): void
    {
        $user = $this->createUser('search-svc-scope@example.com', 1);
        $cat  = $this->createCategory((int) $user['id'], 'Invoice');
        $this->makeTask((int) $user['id'], (int) $cat['id'], 'Buy milk', ['priority' => '1']);

        $this->assertSame([], $this->titles($this->dash->searchOccurrences(
            (int) $user['id'],
            $this->filters(['date' => 'all']),
            'Invoice',
        )));
        $this->assertSame([], $this->titles($this->dash->searchOccurrences(
            (int) $user['id'],
            $this->filters(['date' => 'all']),
            'Urgent',
        )));
    }

    public function testSearchHonorsDateStatusCategoryAndIsolation(): void
    {
        $alice = $this->createUser('search-svc-a@example.com', 1);
        $bob   = $this->createUser('search-svc-b@example.com', 1);
        $work  = $this->createCategory((int) $alice['id'], 'Work');
        $home  = $this->createCategory((int) $alice['id'], 'Personal');
        $bobCat = $this->createCategory((int) $bob['id'], 'Personal');
        $this->makeTask((int) $alice['id'], (int) $work['id'], 'Send Client Invoice');
        $this->makeTask((int) $alice['id'], (int) $home['id'], 'Home invoice', ['due_date' => '2026-08-30']);
        $done = $this->makeTask((int) $alice['id'], (int) $work['id'], 'Paid invoice');
        $this->recurrence->completeOccurrence((int) $alice['id'], (int) db_connect()->table('task_occurrences')->where('task_id', $done['id'])->get()->getRowArray()['id']);
        $this->makeTask((int) $bob['id'], (int) $bobCat['id'], 'Send Client Invoice');

        $tomorrowPersonal = $this->titles($this->dash->searchOccurrences((int) $alice['id'], $this->filters([
            'date'     => 'tomorrow',
            'status'   => 'pending',
            'category' => (string) $home['id'],
        ]), 'invoice'));
        $this->assertSame(['Home invoice'], $tomorrowPersonal);

        $todayWork = $this->titles($this->dash->searchOccurrences((int) $alice['id'], $this->filters([
            'category' => (string) $work['id'],
        ]), 'invoice'));
        $this->assertSame(['Send Client Invoice'], $todayWork);

        $completed = $this->titles($this->dash->searchOccurrences((int) $alice['id'], $this->filters([
            'date'   => 'all',
            'status' => 'completed',
        ]), 'invoice'));
        $this->assertSame(['Paid invoice'], $completed);

        $aliceAll = $this->titles($this->dash->searchOccurrences((int) $alice['id'], $this->filters([
            'date'   => 'all',
            'status' => 'all',
        ]), 'invoice'));
        $this->assertCount(3, $aliceAll);
        $this->assertContains('Send Client Invoice', $aliceAll);
        $this->assertContains('Home invoice', $aliceAll);
        $this->assertContains('Paid invoice', $aliceAll);

        $bobOnly = $this->titles($this->dash->searchOccurrences((int) $bob['id'], $this->filters(['date' => 'all']), 'invoice'));
        $this->assertSame(['Send Client Invoice'], $bobOnly);
    }

    public function testHighlightEscapesAndPreservesOriginalCase(): void
    {
        $html = $this->dash->highlightTitle('Send Client Invoice', 'invoice');
        $this->assertSame('Send Client <mark class="search-hit">Invoice</mark>', $html);

        $xss = $this->dash->highlightTitle('<script>alert(1)</script> Invoice', 'invoice');
        $this->assertStringContainsString('&lt;script&gt;', $xss);
        $this->assertStringNotContainsString('<script>alert', $xss);
        $this->assertStringContainsString('<mark class="search-hit">Invoice</mark>', $xss);

        $this->assertSame('Important & Urgent', $this->dash->priorityLabel(1));
        $this->assertSame('Important but Not Urgent', $this->dash->priorityLabel(2));
        $this->assertSame('Not Important but Urgent', $this->dash->priorityLabel(3));
        $this->assertSame('Not Important & Not Urgent', $this->dash->priorityLabel(4));
    }

    public function testSafeReturnAllowsSearchAndStripsUnknownKeys(): void
    {
        $this->assertSame(
            '/tasks/search?date=today&status=pending&category=all&q=invoice',
            $this->dash->safeReturn('tasks/search?date=today&status=pending&category=all&q=invoice&next=https://evil.example'),
        );
        $this->assertSame('/dashboard', $this->dash->safeReturn('https://evil.example/tasks/search'));
        $this->assertSame('/dashboard', $this->dash->safeReturn('tasks/search/../sign-in'));
    }
}
