//
//  Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

package service

import (
	"strings"
	"testing"

	"ragflow/internal/dao"
	"ragflow/internal/entity"
)

func setupSearchServiceTestDB(t *testing.T) {
	t.Helper()
	db := setupServiceTestDB(t)
	if err := db.AutoMigrate(&entity.Search{}); err != nil {
		t.Fatalf("failed to migrate search: %v", err)
	}
	pushServiceDB(t, db)
}

func createSearchServiceTestSearch(t *testing.T, id, tenantID, name string) {
	t.Helper()

	status := string(entity.StatusValid)
	if err := dao.DB.Create(&entity.Search{
		ID:           id,
		TenantID:     tenantID,
		Name:         name,
		CreatedBy:    tenantID,
		SearchConfig: entity.JSONMap{},
		Status:       &status,
	}).Error; err != nil {
		t.Fatalf("failed to create search: %v", err)
	}
}

func TestSearchServiceCreateRejectsEmptyName(t *testing.T) {
	setupSearchServiceTestDB(t)

	ctx := t.Context()
	_, err := NewSearchService().CreateSearch(ctx, "tenant-1", "   ", nil)
	if err == nil {
		t.Fatal("expected empty name validation error")
	}
	if !strings.Contains(err.Error(), "empty") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestSearchServiceUpdateRejectsUnauthorizedSearchID(t *testing.T) {
	setupSearchServiceTestDB(t)

	ctx := t.Context()
	req := &UpdateSearchRequest{
		Name:         "New Name",
		SearchConfig: map[string]interface{}{},
	}
	_, err := NewSearchService().UpdateSearch(ctx, "user-2", "invalid_search_id", req)
	if err == nil {
		t.Fatal("expected authorization error")
	}
	if err.Error() != "no authorization" {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestSearchServiceCreateAndUpdateRoundTrip(t *testing.T) {
	setupSearchServiceTestDB(t)
	ctx := t.Context()
	created, err := NewSearchService().CreateSearch(ctx, "tenant-1", "My Search", nil)
	if err != nil {
		t.Fatalf("CreateSearch failed: %v", err)
	}
	if created.SearchID == "" {
		t.Fatal("expected non-empty search_id")
	}

	// A different user must not be able to update it.
	req := &UpdateSearchRequest{
		Name:         "Hijacked Name",
		SearchConfig: map[string]interface{}{},
	}
	_, err = NewSearchService().UpdateSearch(ctx, "user-2", created.SearchID, req)
	if err == nil || err.Error() != "no authorization" {
		t.Fatalf("expected no authorization, got %v", err)
	}

	// The owner can update name + merge config.
	req = &UpdateSearchRequest{
		Name:         "Updated Name",
		SearchConfig: map[string]interface{}{"summary": true},
	}
	updated, err := NewSearchService().UpdateSearch(ctx, "tenant-1", created.SearchID, req)
	if err != nil {
		t.Fatalf("owner UpdateSearch failed: %v", err)
	}
	if updated.Name != "Updated Name" {
		t.Fatalf("expected updated name, got %q", updated.Name)
	}
	if updated.SearchConfig["summary"] != true {
		t.Fatalf("expected merged search_config, got %#v", updated.SearchConfig)
	}

	persisted, err := dao.NewSearchDAO().GetByID(ctx, dao.DB, created.SearchID)
	if err != nil {
		t.Fatalf("get updated search: %v", err)
	}
	if persisted.Name != "Updated Name" {
		t.Fatalf("expected persisted name, got %q", persisted.Name)
	}
}

func TestSearchServiceListSearchesReturnsOwnerDisplayFields(t *testing.T) {
	setupSearchServiceTestDB(t)

	if err := dao.DB.Create(&entity.User{
		ID:       "user-1",
		Nickname: "search owner",
		Status:   sptr("1"),
	}).Error; err != nil {
		t.Fatalf("failed to create user: %v", err)
	}
	createSearchServiceTestSearch(t, "search-1", "user-1", "Search One")

	ctx := t.Context()
	result, err := NewSearchService().ListSearches(ctx, "user-1", "", 0, 0, "create_time", true, nil)
	if err != nil {
		t.Fatalf("ListSearches failed: %v", err)
	}
	if result.Total != 1 || len(result.SearchApps) != 1 {
		t.Fatalf("expected one search app, got total=%d len=%d", result.Total, len(result.SearchApps))
	}
	if got, want := result.SearchApps[0]["nickname"], "search owner"; got != want {
		t.Fatalf("nickname = %v, want %v", got, want)
	}
}

func TestSearchServiceListSearchesNicknameFallsBackToTenantID(t *testing.T) {
	setupSearchServiceTestDB(t)

	createSearchServiceTestSearch(t, "search-1", "user-1", "Search One")

	ctx := t.Context()
	result, err := NewSearchService().ListSearches(ctx, "user-1", "", 0, 0, "create_time", true, nil)
	if err != nil {
		t.Fatalf("ListSearches failed: %v", err)
	}
	if result.Total != 1 || len(result.SearchApps) != 1 {
		t.Fatalf("expected one search app, got total=%d len=%d", result.Total, len(result.SearchApps))
	}
	if got, want := result.SearchApps[0]["nickname"], "user-1"; got != want {
		t.Fatalf("nickname = %v, want %v", got, want)
	}
}
