package search

import (
	"fmt"
	"strings"
	"testing"
	"time"

	"github.com/glebarez/sqlite"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"
	"senlinai-agent/backend/internal/models"
)

func TestSearchFindsNoteBody(t *testing.T) {
	database, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, models.AutoMigrate(database))
	models.DBService = database
	require.NoError(t, database.Create(&models.SenlinAgentProject{ID: 1, OwnerID: 7, Name: "支付项目"}).Error)
	require.NoError(t, database.Create(&models.SenlinAgentNote{ProjectID: 1, CreatedBy: 7, Title: "接口方案", Markdown: "二维码支付回调设计"}).Error)

	service := NewService()
	results, err := service.Search(7, "回调")

	require.NoError(t, err)
	require.Len(t, results, 1)
	require.Equal(t, "note", results[0].Type)
}

func TestSearchAppliesStableFairGlobalLimitAcrossResultTypes(t *testing.T) {
	database, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, models.AutoMigrate(database))
	models.DBService = database

	baseTime := time.Date(2026, time.July, 21, 12, 0, 0, 0, time.UTC)
	for index := 0; index < 60; index++ {
		project := models.SenlinAgentProject{
			Identity:   fmt.Sprintf("00000000-0000-7001-8000-%012d", index),
			OwnerID:    7,
			Name:       fmt.Sprintf("稳定排序项目 %02d", index),
			Identifier: fmt.Sprintf("SORT-%02d", index),
			UpdatedAt:  baseTime.Add(time.Duration(index) * time.Minute),
		}
		require.NoError(t, database.Create(&project).Error)
		require.NoError(t, database.Create(&models.SenlinAgentTask{
			Identity:  fmt.Sprintf("00000000-0000-7002-8000-%012d", index),
			ProjectID: project.ID,
			CreatedBy: 7,
			Title:     fmt.Sprintf("稳定排序任务 %02d", index),
			Status:    "open",
			UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
		}).Error)
		require.NoError(t, database.Create(&models.SenlinAgentNote{
			Identity:  fmt.Sprintf("00000000-0000-7003-8000-%012d", index),
			ProjectID: project.ID,
			CreatedBy: 7,
			Title:     fmt.Sprintf("稳定排序笔记 %02d", index),
			UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
		}).Error)
	}

	service := NewService(database)
	results, err := service.Search(7, "稳定排序")
	require.NoError(t, err)
	require.Len(t, results, maxSearchResults)

	counts := map[string]int{}
	for _, result := range results {
		counts[result.Type]++
	}
	require.Equal(t, map[string]int{"project": 17, "task": 17, "note": 16}, counts)
	require.Equal(t,
		[]string{"project", "task", "note", "project", "task", "note", "project", "task", "note"},
		resultTypes(results[:9]),
	)
	require.Equal(t, "00000000-0000-7001-8000-000000000059", results[0].ID)
	require.Equal(t, "00000000-0000-7002-8000-000000000059", results[1].ID)
	require.Equal(t, "00000000-0000-7003-8000-000000000059", results[2].ID)

	again, err := service.Search(7, "稳定排序")
	require.NoError(t, err)
	require.Equal(t, results, again)
}

func TestSearchBoundsChineseSnippetsByRune(t *testing.T) {
	database, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, models.AutoMigrate(database))
	models.DBService = database
	project := models.SenlinAgentProject{OwnerID: 7, Name: "摘要项目", Identifier: "SNIPPET"}
	require.NoError(t, database.Create(&project).Error)
	require.NoError(t, database.Create(&models.SenlinAgentNote{
		ProjectID: project.ID,
		CreatedBy: 7,
		Title:     "中文摘要",
		Markdown:  strings.Repeat("森", 300) + "尾",
	}).Error)

	results, err := NewService(database).Search(7, "森")
	require.NoError(t, err)
	require.Len(t, results, 1)
	require.Len(t, []rune(results[0].Snippet), 240)
	require.NotContains(t, results[0].Snippet, "尾")
}

func resultTypes(results []SearchResultDTO) []string {
	types := make([]string, 0, len(results))
	for _, result := range results {
		types = append(types, result.Type)
	}
	return types
}

func TestSearchOnlyReturnsNavigableProjectTaskAndNoteResults(t *testing.T) {
	database, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, models.AutoMigrate(database))
	models.DBService = database
	require.NoError(t, database.Create(&models.SenlinAgentProject{ID: 1, OwnerID: 7, Name: "webhook 支付项目"}).Error)
	require.NoError(t, database.Create(&models.SenlinAgentTask{ProjectID: 1, CreatedBy: 7, Title: "回调任务", Description: "检查 webhook"}).Error)
	require.NoError(t, database.Create(&models.SenlinAgentNote{ProjectID: 1, CreatedBy: 7, Title: "回调笔记", Markdown: "webhook 流程"}).Error)
	require.NoError(t, database.Create(&models.SenlinAgentSource{ProjectID: 1, CreatedBy: 7, Kind: "link", Title: "支付文档", URL: "https://example.com/pay", ContentText: "webhook 签名"}).Error)
	require.NoError(t, database.Create(&models.SenlinAgentInboxItem{ProjectID: 1, CreatedBy: 7, SourceType: "text", Title: "收集项", Body: "webhook 待整理"}).Error)
	require.NoError(t, database.Create(&models.SenlinAgentAISession{ProjectID: 1, CreatedBy: 7, Title: "AI 分析", Context: "webhook 问答"}).Error)

	service := NewService()
	results, err := service.Search(7, "webhook")

	require.NoError(t, err)
	types := make(map[string]bool)
	for _, result := range results {
		types[result.Type] = true
	}
	require.Equal(t, map[string]bool{"project": true, "task": true, "note": true}, types)
	require.Len(t, results, 3)
}

func TestSearchTreatsLikeWildcardsAsLiteralKeywords(t *testing.T) {
	database, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, models.AutoMigrate(database))
	models.DBService = database
	require.NoError(t, database.Create(&models.SenlinAgentProject{ID: 1, OwnerID: 7, Name: "100%_完成", Identifier: "SPECIAL"}).Error)
	require.NoError(t, database.Create(&models.SenlinAgentProject{ID: 2, OwnerID: 7, Name: "普通项目", Identifier: "NORMAL"}).Error)

	percentResults, err := NewService().Search(7, "%")
	require.NoError(t, err)
	require.Len(t, percentResults, 1)
	require.Equal(t, "100%_完成", percentResults[0].Title)

	underscoreResults, err := NewService().Search(7, "_")
	require.NoError(t, err)
	require.Len(t, underscoreResults, 1)
	require.Equal(t, "100%_完成", underscoreResults[0].Title)
}
