const request = require('supertest');
const app = require('../../app');
const { Task, Project, Area, Note, Tag, User } = require('../../models');
const { createTestUser } = require('../helpers/testUtils');
const moment = require('moment-timezone');

describe('Universal Search Routes', () => {
    let user, agent;

    beforeEach(async () => {
        user = await createTestUser({
            email: 'search-test@example.com',
        });

        // Create authenticated agent
        agent = request.agent(app);
        await agent.post('/api/login').send({
            email: 'search-test@example.com',
            password: 'password123',
        });
    });

    describe('GET /api/search', () => {
        describe('Authentication', () => {
            it('should require authentication', async () => {
                const response = await request(app).get('/api/search');
                expect(response.status).toBe(401);
                expect(response.body.error).toBe('Authentication required');
            });
        });

        describe('Basic Search', () => {
            beforeEach(async () => {
                // Create test data
                await Task.create({
                    user_id: user.id,
                    name: 'Buy groceries',
                    note: 'Milk, eggs, bread',
                    priority: 1,
                    status: 0,
                });

                await Task.create({
                    user_id: user.id,
                    name: 'Call dentist',
                    note: 'Schedule appointment',
                    priority: 2,
                    status: 0,
                });

                await Project.create({
                    user_id: user.id,
                    name: 'Website redesign',
                    description: 'Redesign company website',
                    state: 'active',
                });

                await Note.create({
                    user_id: user.id,
                    title: 'Meeting notes',
                    content: 'Discussed project timeline',
                });
            });

            it('should search across all entity types by default', async () => {
                const response = await agent
                    .get('/api/search')
                    .query({ q: '' });

                expect(response.status).toBe(200);
                expect(response.body.results).toBeDefined();
                expect(response.body.results.length).toBeGreaterThan(0);

                const types = new Set(response.body.results.map((r) => r.type));
                expect(types.has('Task')).toBe(true);
                expect(types.has('Project')).toBe(true);
                expect(types.has('Note')).toBe(true);
            });

            it('should search tasks by name', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'groceries',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(1);
                expect(tasks[0].name).toContain('groceries');
            });

            it('should search tasks by note content', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'eggs',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(1);
            });

            it('should search projects by name', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'Website',
                });

                expect(response.status).toBe(200);
                const projects = response.body.results.filter(
                    (r) => r.type === 'Project'
                );
                expect(projects.length).toBeGreaterThanOrEqual(1);
                expect(projects[0].name).toContain('Website');
            });

            it('should search notes by title', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'Meeting',
                });

                expect(response.status).toBe(200);
                const notes = response.body.results.filter(
                    (r) => r.type === 'Note'
                );
                expect(notes.length).toBeGreaterThanOrEqual(1);
                expect(notes[0].title).toContain('Meeting');
            });

            it('should be case-insensitive', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'GROCERIES',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(1);
            });

            it('should handle empty search query', async () => {
                const response = await agent
                    .get('/api/search')
                    .query({ q: '' });

                expect(response.status).toBe(200);
                expect(response.body.results).toBeDefined();
                expect(Array.isArray(response.body.results)).toBe(true);
            });
        });

        describe('Filter by Entity Type', () => {
            beforeEach(async () => {
                await Task.create({
                    user_id: user.id,
                    name: 'Test task',
                    status: 0,
                });

                await Project.create({
                    user_id: user.id,
                    name: 'Test project',
                    state: 'active',
                });

                await Note.create({
                    user_id: user.id,
                    title: 'Test note',
                    content: 'Content',
                });

                await Area.create({
                    user_id: user.id,
                    name: 'Test area',
                });

                await Tag.create({
                    user_id: user.id,
                    name: 'test-tag',
                });
            });

            it('should filter by Task only', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'Test',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const types = new Set(response.body.results.map((r) => r.type));
                expect(types.has('Task')).toBe(true);
                expect(types.has('Project')).toBe(false);
                expect(types.has('Note')).toBe(false);
            });

            it('should filter by multiple types', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'Test',
                    filters: 'Task,Project',
                });

                expect(response.status).toBe(200);
                const types = new Set(response.body.results.map((r) => r.type));
                expect(types.has('Task')).toBe(true);
                expect(types.has('Project')).toBe(true);
                expect(types.has('Note')).toBe(false);
                expect(types.has('Area')).toBe(false);
            });

            it('should filter by Note only', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'Test',
                    filters: 'Note',
                });

                expect(response.status).toBe(200);
                const types = new Set(response.body.results.map((r) => r.type));
                expect(types.has('Note')).toBe(true);
                expect(types.has('Task')).toBe(false);
            });

            it('should filter by Area only', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'Test',
                    filters: 'Area',
                });

                expect(response.status).toBe(200);
                const types = new Set(response.body.results.map((r) => r.type));
                expect(types.has('Area')).toBe(true);
                expect(types.has('Task')).toBe(false);
            });

            it('should filter by Tag only', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'test',
                    filters: 'Tag',
                });

                expect(response.status).toBe(200);
                const types = new Set(response.body.results.map((r) => r.type));
                expect(types.has('Tag')).toBe(true);
                expect(types.has('Task')).toBe(false);
            });
        });

        describe('Filter by Priority', () => {
            beforeEach(async () => {
                await Task.create({
                    user_id: user.id,
                    name: 'Low priority task',
                    priority: 0,
                    status: 0,
                });

                await Task.create({
                    user_id: user.id,
                    name: 'Medium priority task',
                    priority: 1,
                    status: 0,
                });

                await Task.create({
                    user_id: user.id,
                    name: 'High priority task',
                    priority: 2,
                    status: 0,
                });

                await Project.create({
                    user_id: user.id,
                    name: 'High priority project',
                    priority: 'high',
                    state: 'active',
                });
            });

            it('should filter tasks by low priority', async () => {
                const response = await agent.get('/api/search').query({
                    priority: 'low',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(1);
                expect(tasks.every((t) => t.priority === 0)).toBe(true);
            });

            it('should filter tasks by medium priority', async () => {
                const response = await agent.get('/api/search').query({
                    priority: 'medium',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(1);
                expect(tasks.every((t) => t.priority === 1)).toBe(true);
            });

            it('should filter tasks by high priority', async () => {
                const response = await agent.get('/api/search').query({
                    priority: 'high',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(1);
                expect(tasks.every((t) => t.priority === 2)).toBe(true);
            });

            it('should filter projects by priority', async () => {
                const response = await agent.get('/api/search').query({
                    priority: 'high',
                    filters: 'Project',
                });

                expect(response.status).toBe(200);
                const projects = response.body.results.filter(
                    (r) => r.type === 'Project'
                );
                expect(projects.length).toBeGreaterThanOrEqual(1);
                expect(projects.every((p) => p.priority === 'high')).toBe(true);
            });
        });

        describe('Filter by Due Date', () => {
            beforeEach(async () => {
                const now = moment();

                // Task due today
                await Task.create({
                    user_id: user.id,
                    name: 'Task due today',
                    due_date: now.format('YYYY-MM-DD HH:mm:ss'),
                    status: 0,
                });

                // Task due tomorrow
                await Task.create({
                    user_id: user.id,
                    name: 'Task due tomorrow',
                    due_date: now
                        .clone()
                        .add(1, 'day')
                        .format('YYYY-MM-DD HH:mm:ss'),
                    status: 0,
                });

                // Task due next week
                await Task.create({
                    user_id: user.id,
                    name: 'Task due next week',
                    due_date: now
                        .clone()
                        .add(5, 'days')
                        .format('YYYY-MM-DD HH:mm:ss'),
                    status: 0,
                });

                // Task due next month
                await Task.create({
                    user_id: user.id,
                    name: 'Task due next month',
                    due_date: now
                        .clone()
                        .add(20, 'days')
                        .format('YYYY-MM-DD HH:mm:ss'),
                    status: 0,
                });
            });

            it('should filter tasks due today', async () => {
                const response = await agent.get('/api/search').query({
                    due: 'today',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(1);
                expect(tasks.some((t) => t.name === 'Task due today')).toBe(
                    true
                );
            });

            it('should filter tasks due tomorrow', async () => {
                const response = await agent.get('/api/search').query({
                    due: 'tomorrow',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(1);
                expect(tasks.some((t) => t.name === 'Task due tomorrow')).toBe(
                    true
                );
            });

            it('should filter tasks due next week', async () => {
                const response = await agent.get('/api/search').query({
                    due: 'next_week',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(2); // Should include today, tomorrow, and next week
            });

            it('should filter tasks due next month', async () => {
                const response = await agent.get('/api/search').query({
                    due: 'next_month',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(3); // Should include all tasks due within 30 days
            });
        });

        describe('Filter by Tags', () => {
            let workTag, personalTag, urgentTag;

            beforeEach(async () => {
                // Create tags
                workTag = await Tag.create({
                    user_id: user.id,
                    name: 'work',
                });

                personalTag = await Tag.create({
                    user_id: user.id,
                    name: 'personal',
                });

                urgentTag = await Tag.create({
                    user_id: user.id,
                    name: 'urgent',
                });

                // Create tasks with tags
                const task1 = await Task.create({
                    user_id: user.id,
                    name: 'Work task',
                    status: 0,
                });
                await task1.addTag(workTag);

                const task2 = await Task.create({
                    user_id: user.id,
                    name: 'Personal task',
                    status: 0,
                });
                await task2.addTag(personalTag);

                const task3 = await Task.create({
                    user_id: user.id,
                    name: 'Urgent work task',
                    status: 0,
                });
                await task3.addTag(workTag);
                await task3.addTag(urgentTag);

                // Create project with tag
                const project1 = await Project.create({
                    user_id: user.id,
                    name: 'Work project',
                    state: 'active',
                });
                await project1.addTag(workTag);

                // Create note with tag
                const note1 = await Note.create({
                    user_id: user.id,
                    title: 'Personal note',
                    content: 'Some content',
                });
                await note1.addTag(personalTag);
            });

            it('should filter by single tag', async () => {
                const response = await agent.get('/api/search').query({
                    tags: 'work',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBe(2); // Work task and Urgent work task
            });

            it('should filter by multiple tags', async () => {
                const response = await agent.get('/api/search').query({
                    tags: 'work,urgent',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                // Should return tasks that have either work OR urgent tag
                expect(tasks.length).toBeGreaterThanOrEqual(1);
            });

            it('should filter projects by tag', async () => {
                const response = await agent.get('/api/search').query({
                    tags: 'work',
                    filters: 'Project',
                });

                expect(response.status).toBe(200);
                const projects = response.body.results.filter(
                    (r) => r.type === 'Project'
                );
                expect(projects.length).toBe(1);
                expect(projects[0].name).toBe('Work project');
            });

            it('should filter notes by tag', async () => {
                const response = await agent.get('/api/search').query({
                    tags: 'personal',
                    filters: 'Note',
                });

                expect(response.status).toBe(200);
                const notes = response.body.results.filter(
                    (r) => r.type === 'Note'
                );
                expect(notes.length).toBe(1);
                expect(notes[0].title).toBe('Personal note');
            });

            it('should return empty results for non-existent tag', async () => {
                const response = await agent.get('/api/search').query({
                    tags: 'nonexistent',
                });

                expect(response.status).toBe(200);
                expect(response.body.results).toEqual([]);
            });
        });

        describe('Combined Filters', () => {
            beforeEach(async () => {
                const now = moment();
                const workTag = await Tag.create({
                    user_id: user.id,
                    name: 'work',
                });

                // High priority work task due today with tag
                const task1 = await Task.create({
                    user_id: user.id,
                    name: 'Important work meeting',
                    priority: 2,
                    due_date: now.format('YYYY-MM-DD HH:mm:ss'),
                    status: 0,
                });
                await task1.addTag(workTag);

                // Low priority personal task
                await Task.create({
                    user_id: user.id,
                    name: 'Personal errand',
                    priority: 0,
                    status: 0,
                });

                // Medium priority work task (no due date)
                const task3 = await Task.create({
                    user_id: user.id,
                    name: 'Work review',
                    priority: 1,
                    status: 0,
                });
                await task3.addTag(workTag);
            });

            it('should combine search query and priority filter', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'work',
                    priority: 'high',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(1);
                expect(tasks.every((t) => t.priority === 2)).toBe(true);
            });

            it('should combine priority, due date, and tag filters', async () => {
                const response = await agent.get('/api/search').query({
                    priority: 'high',
                    due: 'today',
                    tags: 'work',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBeGreaterThanOrEqual(1);
                expect(tasks[0].name).toBe('Important work meeting');
            });

            it('should combine all filters with search query', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'meeting',
                    priority: 'high',
                    due: 'today',
                    tags: 'work',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBe(1);
                expect(tasks[0].name).toBe('Important work meeting');
            });
        });

        describe('Extras Filters', () => {
            beforeEach(async () => {
                const project = await Project.create({
                    user_id: user.id,
                    name: 'Extras Project',
                    state: 'active',
                });

                const workTag = await Tag.create({
                    user_id: user.id,
                    name: 'extras-tag',
                });

                const recurringTemplate = await Task.create({
                    user_id: user.id,
                    name: 'Recurring Template',
                    recurrence_type: 'weekly',
                    status: 0,
                });

                await Task.create({
                    user_id: user.id,
                    name: 'Recurring Instance',
                    recurring_parent_id: recurringTemplate.id,
                    status: 0,
                });

                await Task.create({
                    user_id: user.id,
                    name: 'Regular Task',
                    status: 0,
                });

                await Task.create({
                    user_id: user.id,
                    name: 'Overdue Task',
                    due_date: moment().subtract(2, 'days').toDate(),
                    status: 0,
                });

                await Task.create({
                    user_id: user.id,
                    name: 'Completed Overdue Task',
                    due_date: moment().subtract(3, 'days').toDate(),
                    completed_at: new Date(),
                    status: 3,
                });

                await Task.create({
                    user_id: user.id,
                    name: 'Content Task',
                    note: 'Detailed context lives here',
                    status: 0,
                });

                await Task.create({
                    user_id: user.id,
                    name: 'Deferred Task',
                    defer_until: moment().add(2, 'days').toDate(),
                    status: 0,
                });

                const taggedTask = await Task.create({
                    user_id: user.id,
                    name: 'Tagged Task',
                    status: 0,
                });
                await taggedTask.addTag(workTag);

                await Task.create({
                    user_id: user.id,
                    name: 'Project Task',
                    project_id: project.id,
                    status: 0,
                });
            });

            const getTaskNames = (response) =>
                response.body.results
                    .filter((r) => r.type === 'Task')
                    .map((task) => task.original_name || task.name);

            it('should include recurring tasks in default search with their original names', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'Recurring',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const names = getTaskNames(response);
                expect(names).toContain('Recurring Template');
                expect(names).toContain('Recurring Instance');

                // Verify template shows original name, not recurrence type label
                const template = response.body.results.find(
                    (r) =>
                        r.type === 'Task' &&
                        r.recurrence_type === 'weekly' &&
                        !r.recurring_parent_id
                );
                expect(template).toBeDefined();
                expect(template.name).toBe('Recurring Template');
            });

            it('should return only recurring tasks when extras contains recurring', async () => {
                const response = await agent.get('/api/search').query({
                    filters: 'Task',
                    extras: 'recurring',
                });

                expect(response.status).toBe(200);
                const names = getTaskNames(response);
                expect(names).toEqual(
                    expect.arrayContaining([
                        'Recurring Template',
                        'Recurring Instance',
                    ])
                );
                expect(names).not.toContain('Regular Task');
            });

            it('should return overdue tasks and exclude completed ones', async () => {
                const response = await agent.get('/api/search').query({
                    filters: 'Task',
                    extras: 'overdue',
                });

                expect(response.status).toBe(200);
                const names = getTaskNames(response);
                expect(names).toContain('Overdue Task');
                expect(names).not.toContain('Completed Overdue Task');
            });

            it('should return tasks that have content', async () => {
                const response = await agent.get('/api/search').query({
                    filters: 'Task',
                    extras: 'has_content',
                });

                expect(response.status).toBe(200);
                const names = getTaskNames(response);
                expect(names).toContain('Content Task');
                expect(names).not.toContain('Regular Task');
            });

            it('should return deferred tasks', async () => {
                const response = await agent.get('/api/search').query({
                    filters: 'Task',
                    extras: 'deferred',
                });

                expect(response.status).toBe(200);
                const names = getTaskNames(response);
                expect(names).toContain('Deferred Task');
                expect(names).not.toContain('Regular Task');
            });

            it('should return tasks with tags', async () => {
                const response = await agent.get('/api/search').query({
                    filters: 'Task',
                    extras: 'has_tags',
                });

                expect(response.status).toBe(200);
                const names = getTaskNames(response);
                expect(names).toContain('Tagged Task');
                expect(names).not.toContain('Regular Task');
            });

            it('should return tasks assigned to projects', async () => {
                const response = await agent.get('/api/search').query({
                    filters: 'Task',
                    extras: 'assigned_to_project',
                });

                expect(response.status).toBe(200);
                const names = getTaskNames(response);
                expect(names).toContain('Project Task');
                expect(names).not.toContain('Regular Task');
            });

            it('should return only projects that have tags when extras include has_tags', async () => {
                const taggedProject = await Project.create({
                    user_id: user.id,
                    name: 'Tagged Project',
                    state: 'active',
                });
                const plainProject = await Project.create({
                    user_id: user.id,
                    name: 'Plain Project',
                    state: 'active',
                });
                const projectTag = await Tag.create({
                    user_id: user.id,
                    name: 'project-tag',
                });
                await taggedProject.addTag(projectTag);

                const response = await agent.get('/api/search').query({
                    filters: 'Project',
                    extras: 'has_tags',
                });

                expect(response.status).toBe(200);
                const projectResults = response.body.results.filter(
                    (r) => r.type === 'Project'
                );
                const projectNames = projectResults.map((p) => p.name);
                expect(projectNames).toContain('Tagged Project');
                expect(projectNames).not.toContain('Plain Project');
            });
        });

        describe('User Isolation', () => {
            let otherUser, otherAgent;

            beforeEach(async () => {
                // Create another user
                otherUser = await createTestUser({
                    email: 'other-user@example.com',
                });

                otherAgent = request.agent(app);
                await otherAgent.post('/api/login').send({
                    email: 'other-user@example.com',
                    password: 'password123',
                });

                // Create data for first user
                await Task.create({
                    user_id: user.id,
                    name: 'User 1 task',
                    status: 0,
                });

                // Create data for second user
                await Task.create({
                    user_id: otherUser.id,
                    name: 'User 2 task',
                    status: 0,
                });
            });

            it('should only return results for authenticated user', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'task',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBe(1);
                expect(tasks[0].name).toBe('User 1 task');
            });

            it('should not return other users data', async () => {
                const response = await otherAgent.get('/api/search').query({
                    q: 'task',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const tasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks.length).toBe(1);
                expect(tasks[0].name).toBe('User 2 task');
            });
        });

        describe('Result Format', () => {
            beforeEach(async () => {
                await Task.create({
                    user_id: user.id,
                    name: 'Test task',
                    note: 'Task description',
                    priority: 1,
                    status: 0,
                });

                await Project.create({
                    user_id: user.id,
                    name: 'Test project',
                    description: 'Project description',
                    state: 'active',
                    priority: 'medium',
                });

                await Note.create({
                    user_id: user.id,
                    title: 'Test note',
                    content: 'Note content goes here',
                });
            });

            it('should return task with correct structure', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'Test',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const task = response.body.results.find(
                    (r) => r.type === 'Task'
                );
                expect(task).toBeDefined();
                expect(task.type).toBe('Task');
                expect(task.id).toBeDefined();
                expect(task.uid).toBeDefined();
                expect(task.name).toBe('Test task');
                expect(task.description).toBe('Task description');
                expect(task.priority).toBe(1);
                expect(task.status).toBe(0);
            });

            it('should return project with correct structure', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'Test',
                    filters: 'Project',
                });

                expect(response.status).toBe(200);
                const project = response.body.results.find(
                    (r) => r.type === 'Project'
                );
                expect(project).toBeDefined();
                expect(project.type).toBe('Project');
                expect(project.id).toBeDefined();
                expect(project.uid).toBeDefined();
                expect(project.name).toBe('Test project');
                expect(project.description).toBe('Project description');
                expect(project.priority).toBe('medium');
                expect(project.status).toBe('not_started');
            });

            it('should return note with correct structure', async () => {
                const response = await agent.get('/api/search').query({
                    q: 'Test',
                    filters: 'Note',
                });

                expect(response.status).toBe(200);
                const note = response.body.results.find(
                    (r) => r.type === 'Note'
                );
                expect(note).toBeDefined();
                expect(note.type).toBe('Note');
                expect(note.id).toBeDefined();
                expect(note.uid).toBeDefined();
                expect(note.name).toBe('Test note');
                expect(note.title).toBe('Test note');
                expect(note.description).toBe('Note content goes here');
            });
        });

        describe('Edge Cases', () => {
            it('should handle special characters in search query', async () => {
                await Task.create({
                    user_id: user.id,
                    name: 'Task with special chars: @#$%',
                    status: 0,
                });

                const response = await agent.get('/api/search').query({
                    q: '@#$',
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                // Should not error, even if results might be empty
                expect(response.body.results).toBeDefined();
            });

            it('should handle very long search queries', async () => {
                const longQuery = 'a'.repeat(1000);
                const response = await agent.get('/api/search').query({
                    q: longQuery,
                });

                expect(response.status).toBe(200);
                expect(response.body.results).toBeDefined();
            });

            it('should handle invalid filter types gracefully', async () => {
                const response = await agent.get('/api/search').query({
                    filters: 'InvalidType',
                });

                expect(response.status).toBe(200);
                expect(response.body.results).toEqual([]);
            });

            it('should respect limit of 20 results per type', async () => {
                // Create 25 tasks
                const tasks = Array.from({ length: 25 }, (_, i) =>
                    Task.create({
                        user_id: user.id,
                        name: `Task ${i + 1}`,
                        status: 0,
                    })
                );
                await Promise.all(tasks);

                const response = await agent.get('/api/search').query({
                    filters: 'Task',
                });

                expect(response.status).toBe(200);
                const returnedTasks = response.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(returnedTasks.length).toBeLessThanOrEqual(20);
            });

            it('should perform case-insensitive search for ASCII characters', async () => {
                // NOTE: SQLite's LOWER() function only supports ASCII characters
                // For Unicode (Cyrillic, Greek, etc.), search is case-sensitive
                // This is a known limitation of SQLite without ICU extension

                // Create test data with mixed case ASCII
                await Task.create({
                    user_id: user.id,
                    name: 'Important Meeting',
                    note: 'Discussion about Project',
                    status: 0,
                });

                await Project.create({
                    user_id: user.id,
                    name: 'Website Redesign',
                    description: 'Complete overhaul of company site',
                    state: 'active',
                });

                // Test lowercase search finds uppercase ASCII
                const response1 = await agent.get('/api/search').query({
                    q: 'important',
                    filters: 'Task',
                });
                expect(response1.status).toBe(200);
                const tasks1 = response1.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks1.length).toBeGreaterThanOrEqual(1);
                expect(tasks1.some((t) => t.name.includes('Important'))).toBe(
                    true
                );

                // Test uppercase search finds mixed case ASCII
                const response2 = await agent.get('/api/search').query({
                    q: 'MEETING',
                    filters: 'Task',
                });
                expect(response2.status).toBe(200);
                const tasks2 = response2.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks2.length).toBeGreaterThanOrEqual(1);
                expect(
                    tasks2.some((t) => t.name.toLowerCase().includes('meeting'))
                ).toBe(true);

                // Test search in note content (case-insensitive)
                const response3 = await agent.get('/api/search').query({
                    q: 'project',
                    filters: 'Task',
                });
                expect(response3.status).toBe(200);
                const tasks3 = response3.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks3.length).toBeGreaterThanOrEqual(1);

                // Test project search (case-insensitive)
                const response4 = await agent.get('/api/search').query({
                    q: 'website',
                    filters: 'Project',
                });
                expect(response4.status).toBe(200);
                const projects = response4.body.results.filter(
                    (r) => r.type === 'Project'
                );
                expect(projects.length).toBeGreaterThanOrEqual(1);
                expect(projects.some((p) => p.name.includes('Website'))).toBe(
                    true
                );
            });

            it('should demonstrate Cyrillic search limitation', async () => {
                // NOTE: This test documents the current Cyrillic search limitation
                // SQLite's LOWER() only works with ASCII, so our search uses:
                //   - JavaScript toLowerCase() on search query: "Тест" -> "тест"
                //   - SQLite LOWER() on database: "Тест Русский" -> "Тест Русский" (unchanged)
                // Result: Case-insensitive search doesn't work for Cyrillic
                // Future improvement: Add ICU extension, FTS5, or normalized search fields

                // Create test data with Cyrillic text (all lowercase to match search query)
                await Task.create({
                    user_id: user.id,
                    name: 'тест русский',
                    note: 'заметка по-русски',
                    status: 0,
                });

                await Task.create({
                    user_id: user.id,
                    name: 'завдання українське',
                    note: 'нотатка українською',
                    status: 0,
                });

                // Lowercase search DOES work when database text is also lowercase
                const response1 = await agent.get('/api/search').query({
                    q: 'тест',
                    filters: 'Task',
                });
                expect(response1.status).toBe(200);
                const tasks1 = response1.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks1.length).toBeGreaterThanOrEqual(1);
                expect(tasks1.some((t) => t.name.includes('тест'))).toBe(true);

                // Ukrainian lowercase search works
                const response2 = await agent.get('/api/search').query({
                    q: 'завдання',
                    filters: 'Task',
                });
                expect(response2.status).toBe(200);
                const tasks2 = response2.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks2.length).toBeGreaterThanOrEqual(1);
                expect(tasks2.some((t) => t.name.includes('завдання'))).toBe(
                    true
                );

                // Search in note content works
                const response3 = await agent.get('/api/search').query({
                    q: 'заметка',
                    filters: 'Task',
                });
                expect(response3.status).toBe(200);
                const tasks3 = response3.body.results.filter(
                    (r) => r.type === 'Task'
                );
                expect(tasks3.length).toBeGreaterThanOrEqual(1);
                expect(
                    tasks3.some((t) => t.description?.includes('заметка'))
                ).toBe(true);

                // Now test the limitation: Create uppercase Cyrillic task
                await Task.create({
                    user_id: user.id,
                    name: 'ТЕСТ UPPERCASE',
                    note: 'UPPERCASE заметка',
                    status: 0,
                });

                // Lowercase search will NOT find uppercase Cyrillic (limitation)
                const response4 = await agent.get('/api/search').query({
                    q: 'тест uppercase',
                    filters: 'Task',
                });
                expect(response4.status).toBe(200);
                const tasks4 = response4.body.results.filter(
                    (r) => r.type === 'Task'
                );
                // Won't find it because JavaScript lowercased query doesn't match uppercase Cyrillic in DB
                expect(tasks4.some((t) => t.name.includes('UPPERCASE'))).toBe(
                    false
                );
            });
        });
    });

    describe('Pagination', () => {
        it('should paginate search results with limit and offset', async () => {
            // Create 25 tasks to test pagination (more than default limit of 20)
            const tasks = [];
            for (let i = 1; i <= 25; i++) {
                tasks.push(
                    await Task.create({
                        user_id: user.id,
                        name: `Paginated Task ${i}`,
                        note: 'Test pagination',
                        status: 0,
                    })
                );
            }

            // First page: get first 10 results
            const response1 = await agent.get('/api/search').query({
                filters: 'Task',
                limit: 10,
                offset: 0,
            });

            expect(response1.status).toBe(200);
            expect(response1.body.results).toBeDefined();
            expect(response1.body.pagination).toBeDefined();
            expect(response1.body.pagination.total).toBeGreaterThanOrEqual(25);
            expect(response1.body.pagination.limit).toBe(10);
            expect(response1.body.pagination.offset).toBe(0);
            expect(response1.body.results.length).toBe(10);
            expect(response1.body.pagination.hasMore).toBe(true);

            // Second page: get next 10 results
            const response2 = await agent.get('/api/search').query({
                filters: 'Task',
                limit: 10,
                offset: 10,
            });

            expect(response2.status).toBe(200);
            expect(response2.body.pagination.offset).toBe(10);
            expect(response2.body.results.length).toBe(10);
            expect(response2.body.pagination.hasMore).toBe(true);

            // Third page: get remaining results
            const response3 = await agent.get('/api/search').query({
                filters: 'Task',
                limit: 10,
                offset: 20,
            });

            expect(response3.status).toBe(200);
            expect(response3.body.pagination.offset).toBe(20);
            expect(response3.body.results.length).toBeGreaterThanOrEqual(5);
        });

        it('should support pagination with tag filters', async () => {
            // Create a tag
            const tag = await Tag.create({
                user_id: user.id,
                name: 'pagination-test',
            });

            // Create 30 tasks with the tag
            for (let i = 1; i <= 30; i++) {
                const task = await Task.create({
                    user_id: user.id,
                    name: `Tagged Task ${i}`,
                    status: 0,
                });
                // Associate task with tag using Sequelize association
                await task.addTag(tag);
            }

            // Get first page
            const response1 = await agent.get('/api/search').query({
                filters: 'Task',
                tags: 'pagination-test',
                limit: 15,
                offset: 0,
            });

            expect(response1.status).toBe(200);
            expect(response1.body.pagination).toBeDefined();
            expect(response1.body.pagination.total).toBe(30);
            expect(response1.body.results.length).toBe(15);
            expect(response1.body.pagination.hasMore).toBe(true);

            // Get second page
            const response2 = await agent.get('/api/search').query({
                filters: 'Task',
                tags: 'pagination-test',
                limit: 15,
                offset: 15,
            });

            expect(response2.status).toBe(200);
            expect(response2.body.results.length).toBe(15);
            expect(response2.body.pagination.hasMore).toBe(false);
        });

        it('should maintain backward compatibility without pagination params', async () => {
            // Create 5 tasks
            for (let i = 1; i <= 5; i++) {
                await Task.create({
                    user_id: user.id,
                    name: `Task ${i}`,
                    status: 0,
                });
            }

            // Request without pagination params
            const response = await agent.get('/api/search').query({
                filters: 'Task',
            });

            expect(response.status).toBe(200);
            expect(response.body.results).toBeDefined();
            // Should NOT include pagination metadata when no params provided
            expect(response.body.pagination).toBeUndefined();
            expect(response.body.results.length).toBeGreaterThanOrEqual(5);
        });

        it('should handle offset beyond total results', async () => {
            // Create 10 tasks
            for (let i = 1; i <= 10; i++) {
                await Task.create({
                    user_id: user.id,
                    name: `Task ${i}`,
                    status: 0,
                });
            }

            // Request with offset beyond available results
            const response = await agent.get('/api/search').query({
                filters: 'Task',
                limit: 10,
                offset: 100,
            });

            expect(response.status).toBe(200);
            expect(response.body.pagination).toBeDefined();
            expect(response.body.pagination.total).toBeGreaterThanOrEqual(10);
            expect(response.body.results.length).toBe(0);
            expect(response.body.pagination.hasMore).toBe(false);
        });

        it('should use default limit of 20 when limit param is provided without value', async () => {
            // Create 25 tasks
            for (let i = 1; i <= 25; i++) {
                await Task.create({
                    user_id: user.id,
                    name: `Task ${i}`,
                    status: 0,
                });
            }

            // Request with limit param but no value (or invalid value)
            const response = await agent.get('/api/search').query({
                filters: 'Task',
                limit: '',
            });

            expect(response.status).toBe(200);
            expect(response.body.pagination).toBeDefined();
            expect(response.body.pagination.limit).toBe(20);
            expect(response.body.results.length).toBe(20);
        });
    });
});
