//
//  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 (
	"testing"
)

func TestGetFloat(t *testing.T) {
	tests := []struct {
		name string
		m    map[string]interface{}
		key  string
		want float64
	}{
		{"present", map[string]interface{}{"score": 3.5}, "score", 3.5},
		{"missing", map[string]interface{}{}, "score", 0},
		{"wrong type", map[string]interface{}{"score": "3.5"}, "score", 0},
		{"nil value", map[string]interface{}{"score": nil}, "score", 0},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := getFloat(tt.m, tt.key); got != tt.want {
				t.Errorf("getFloat() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestGetStringDef(t *testing.T) {
	tests := []struct {
		name string
		m    map[string]interface{}
		key  string
		def  string
		want string
	}{
		{"present", map[string]interface{}{"name": "foo"}, "name", "default", "foo"},
		{"missing", map[string]interface{}{}, "name", "default", "default"},
		{"wrong type", map[string]interface{}{"name": 42}, "name", "default", "default"},
		{"empty string", map[string]interface{}{"name": ""}, "name", "default", ""},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := getStringDef(tt.m, tt.key, tt.def); got != tt.want {
				t.Errorf("getStringDef() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestGetSlice(t *testing.T) {
	tests := []struct {
		name string
		m    map[string]interface{}
		key  string
		want []interface{}
	}{
		{"present", map[string]interface{}{"items": []interface{}{1, 2, 3}}, "items", []interface{}{1, 2, 3}},
		{"missing", map[string]interface{}{}, "items", nil},
		{"wrong type", map[string]interface{}{"items": "not a slice"}, "items", nil},
		{"nil value", map[string]interface{}{"items": nil}, "items", nil},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := getSlice(tt.m, tt.key)
			if len(got) != len(tt.want) {
				t.Errorf("getSlice() len = %d, want %d", len(got), len(tt.want))
				return
			}
			for i := range got {
				if got[i] != tt.want[i] {
					t.Errorf("getSlice()[%d] = %v, want %v", i, got[i], tt.want[i])
				}
			}
		})
	}
}

func TestToFloat64Slice(t *testing.T) {
	tests := []struct {
		name   string
		v      interface{}
		want   []float64
		wantOk bool
	}{
		{"valid", []interface{}{1.0, 2.5, 3.0}, []float64{1.0, 2.5, 3.0}, true},
		{"empty", []interface{}{}, []float64{}, true},
		{"wrong type", "not a slice", nil, false},
		{"non-float element", []interface{}{1.0, "x"}, nil, false},
		{"nil", nil, nil, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := toFloat64Slice(tt.v)
			if (got != nil) != tt.wantOk {
				t.Errorf("toFloat64Slice() ok = %v, want %v", got != nil, tt.wantOk)
				return
			}
			if len(got) != len(tt.want) {
				t.Errorf("toFloat64Slice() len = %d, want %d", len(got), len(tt.want))
				return
			}
			for i := range got {
				if got[i] != tt.want[i] {
					t.Errorf("toFloat64Slice()[%d] = %v, want %v", i, got[i], tt.want[i])
				}
			}
		})
	}
}

func TestAsMap(t *testing.T) {
	tests := []struct {
		name string
		v    interface{}
		want map[string]interface{}
	}{
		{"valid map", map[string]interface{}{"a": 1}, map[string]interface{}{"a": 1}},
		{"nil", nil, nil},
		{"wrong type", "not a map", nil},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := asMap(tt.v)
			if len(got) != len(tt.want) {
				t.Errorf("asMap() len = %d, want %d", len(got), len(tt.want))
				return
			}
			for k, v := range tt.want {
				if got[k] != v {
					t.Errorf("asMap()[%q] = %v, want %v", k, got[k], v)
				}
			}
		})
	}
}

func TestBoostSimilarity(t *testing.T) {
	cm := map[string]interface{}{"similarity": 1.0}
	boostSimilarity(cm, 0.5)
	if cm["similarity"] != 1.5 {
		t.Errorf("expected 1.5, got %v", cm["similarity"])
	}

	boostSimilarity(cm, 0.0)
	if cm["similarity"] != 1.5 {
		t.Errorf("expected 1.5 (unchanged), got %v", cm["similarity"])
	}
}

func TestTopDocFromChunks_Empty(t *testing.T) {
	docID, docMap := topDocFromChunks(nil)
	if docID != "" || docMap != nil {
		t.Errorf("expected empty, got docID=%q, map=%v", docID, docMap)
	}
}

func TestTopDocFromChunks_SingleDoc(t *testing.T) {
	chunks := []map[string]interface{}{
		map[string]interface{}{"doc_id": "doc1", "kb_id": "kb1", "similarity": 0.8},
		map[string]interface{}{"doc_id": "doc1", "kb_id": "kb1", "similarity": 0.6},
	}
	docID, docMap := topDocFromChunks(chunks)
	if docID != "doc1" {
		t.Errorf("expected doc1, got %q", docID)
	}
	if docMap["doc1"] != "kb1" {
		t.Errorf("expected kb1, got %q", docMap["doc1"])
	}
}

func TestTopDocFromChunks_MultiDoc(t *testing.T) {
	chunks := []map[string]interface{}{
		map[string]interface{}{"doc_id": "doc1", "kb_id": "kb1", "similarity": 0.3},
		map[string]interface{}{"doc_id": "doc2", "kb_id": "kb2", "similarity": 0.9},
		map[string]interface{}{"doc_id": "doc1", "kb_id": "kb1", "similarity": 0.4},
	}
	docID, docMap := topDocFromChunks(chunks)
	if docID != "doc2" {
		t.Errorf("expected doc2 (accumulated 0.9 > doc1's 0.7), got %q", docID)
	}
	if docMap["doc1"] != "kb1" {
		t.Errorf("expected kb1, got %q", docMap["doc1"])
	}
	if docMap["doc2"] != "kb2" {
		t.Errorf("expected kb2, got %q", docMap["doc2"])
	}
}

func TestTopDocFromChunks_NoDocID(t *testing.T) {
	chunks := []map[string]interface{}{
		map[string]interface{}{"similarity": 0.8},
	}
	docID, docMap := topDocFromChunks(chunks)
	if docID != "" || docMap != nil {
		t.Errorf("expected empty, got docID=%q, map=%v", docID, docMap)
	}
}

func TestParseTOCEntries_Empty(t *testing.T) {
	got := parseTOCEntries(nil)
	if len(got) != 0 {
		t.Errorf("expected 0 entries, got %d", len(got))
	}
}

func TestParseTOCEntries_SingleEntry(t *testing.T) {
	chunks := []map[string]interface{}{
		{"content_with_weight": `{"level": 1, "title": "Intro", "ids": ["c1"]}`},
	}
	got := parseTOCEntries(chunks)
	if len(got) != 1 {
		t.Fatalf("expected 1 entry, got %d", len(got))
	}
	if got[0].Level != 1 || got[0].Title != "Intro" || len(got[0].IDs) != 1 || got[0].IDs[0] != "c1" {
		t.Errorf("unexpected entry: %+v", got[0])
	}
}

func TestParseTOCEntries_MultiEntry(t *testing.T) {
	chunks := []map[string]interface{}{
		{"content_with_weight": `[
			{"level": 1, "title": "Intro", "ids": ["c1"]},
			{"level": 2, "title": "Details", "ids": ["c2", "c3"]}
		]`},
	}
	got := parseTOCEntries(chunks)
	if len(got) != 2 {
		t.Fatalf("expected 2 entries, got %d", len(got))
	}
	if got[0].Title != "Intro" || got[1].Title != "Details" {
		t.Errorf("unexpected titles: %q, %q", got[0].Title, got[1].Title)
	}
	if len(got[1].IDs) != 2 {
		t.Errorf("expected 2 IDs for Details, got %v", got[1].IDs)
	}
}

func TestParseTOCEntries_InvalidJSON(t *testing.T) {
	chunks := []map[string]interface{}{
		{"content_with_weight": `not json`},
	}
	got := parseTOCEntries(chunks)
	if len(got) != 0 {
		t.Errorf("expected 0 entries for invalid JSON, got %d", len(got))
	}
}

func TestParseTOCEntries_MissingField(t *testing.T) {
	chunks := []map[string]interface{}{
		{"other_field": "value"},
	}
	got := parseTOCEntries(chunks)
	if len(got) != 0 {
		t.Errorf("expected 0 entries for missing content_with_weight, got %d", len(got))
	}
}

func TestSortAndTrimChunks_Nil(t *testing.T) {
	got := sortAndTrimChunks(nil, 3)
	if got != nil {
		t.Errorf("expected nil, got %v", got)
	}
}

func TestSortAndTrimChunks_Trim(t *testing.T) {
	chunks := []map[string]interface{}{
		map[string]interface{}{"similarity": 0.3},
		map[string]interface{}{"similarity": 0.9},
		map[string]interface{}{"similarity": 0.6},
	}
	got := sortAndTrimChunks(chunks, 2)
	if len(got) != 2 {
		t.Fatalf("expected 2 chunks, got %d", len(got))
	}
	if got[0]["similarity"] != 0.9 {
		t.Errorf("expected first similarity 0.9, got %v", got[0])
	}
	if got[1]["similarity"] != 0.6 {
		t.Errorf("expected second similarity 0.6, got %v", got[1])
	}
}

func TestSortAndTrimChunks_AllKept(t *testing.T) {
	chunks := []map[string]interface{}{
		map[string]interface{}{"similarity": 0.3},
		map[string]interface{}{"similarity": 0.9},
	}
	got := sortAndTrimChunks(chunks, 5)
	if len(got) != 2 {
		t.Errorf("expected all 2 chunks kept, got %d", len(got))
	}
}

func TestIndexName(t *testing.T) {
	if got := getIndexName("tenant1"); got != "ragflow_tenant1" {
		t.Errorf("expected 'ragflow_tenant1', got %q", got)
	}
}
