//
//  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 (
	"context"
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"ragflow/internal/entity"
	modelModule "ragflow/internal/entity/models"
)

// GenerateRelatedQuestions generates related search questions for chat/searchbot endpoints.
func GenerateRelatedQuestions(ctx context.Context, tenantID, question, searchID string, searchSvc *SearchService, tenantSvc *TenantService, modelProviderSvc *ModelProviderService) ([]string, error) {
	if modelProviderSvc == nil {
		return nil, fmt.Errorf("model provider service not configured")
	}
	searchConfig := relatedQuestionsSearchConfig(ctx, searchID, searchSvc)
	modelID := relatedQuestionsModelID(ctx, tenantID, searchConfig, tenantSvc)
	prompt, err := LoadPrompt("related_question")
	if err != nil {
		return nil, err
	}
	messages := []modelModule.Message{
		{Role: "system", Content: prompt},
		{Role: "user", Content: "\nKeywords: " + question + "\nRelated search terms:\n    "},
	}
	response, err := modelProviderSvc.Chat(ctx, tenantID, modelID, messages, relatedQuestionsConfig(searchConfig))
	if err != nil {
		return nil, err
	}
	if response != nil && response.Answer != nil {
		return parseRelatedQuestions(*response.Answer), nil
	}
	return []string{}, nil
}

func relatedQuestionsSearchConfig(ctx context.Context, searchID string, searchSvc *SearchService) map[string]interface{} {
	if searchID == "" || searchSvc == nil {
		return map[string]interface{}{}
	}
	if detail, err := searchSvc.GetDetail(ctx, searchID); err == nil && detail != nil {
		return relatedQuestionsSearchConfigFromDetail(detail)
	}
	return map[string]interface{}{}
}

func relatedQuestionsSearchConfigFromDetail(detail map[string]interface{}) map[string]interface{} {
	if sc, ok := detail["search_config"].(map[string]interface{}); ok && sc != nil {
		return sc
	}
	if sc, ok := detail["search_config"].(entity.JSONMap); ok && sc != nil {
		return map[string]interface{}(sc)
	}
	return map[string]interface{}{}
}

func relatedQuestionsModelID(ctx context.Context, tenantID string, searchConfig map[string]interface{}, tenantSvc *TenantService) string {
	modelID, _ := searchConfig["chat_id"].(string)
	if modelID != "" || tenantSvc == nil {
		return modelID
	}
	defaultModel, err := tenantSvc.GetDefaultModelName(ctx, tenantID, entity.ModelTypeChat)
	if err == nil {
		modelID = defaultModel
	}
	return modelID
}

func relatedQuestionsConfig(searchConfig map[string]interface{}) *modelModule.ChatConfig {
	var genConf map[string]interface{}
	switch v := searchConfig["llm_setting"].(type) {
	case map[string]interface{}:
		genConf = v
	case entity.JSONMap:
		genConf = map[string]interface{}(v)
	}
	if genConf == nil {
		return &modelModule.ChatConfig{Temperature: float64Ptr(0.9)}
	}
	cfg := &modelModule.ChatConfig{}
	for key, value := range genConf {
		if key == "parameter" {
			continue
		}
		switch key {
		case "stream":
			if v, ok := value.(bool); ok {
				cfg.Stream = &v
			}
		case "thinking":
			if v, ok := value.(bool); ok {
				cfg.Thinking = &v
			}
		case "max_tokens":
			if v, ok := intFromRelatedQuestionConfig(value); ok {
				cfg.MaxTokens = &v
			}
		case "temperature":
			if v, ok := floatFromRelatedQuestionConfig(value); ok {
				cfg.Temperature = &v
			}
		case "top_p":
			if v, ok := floatFromRelatedQuestionConfig(value); ok && v > 0 {
				cfg.TopP = &v
			}
		case "do_sample":
			if v, ok := value.(bool); ok {
				cfg.DoSample = &v
			}
		case "stop":
			if stops := stringSliceFromRelatedQuestionConfig(value); len(stops) > 0 {
				cfg.Stop = &stops
			}
		case "model_class":
			if v, ok := value.(string); ok {
				cfg.ModelClass = &v
			}
		case "effort":
			if v, ok := value.(string); ok {
				cfg.Effort = &v
			}
		case "verbosity":
			if v, ok := value.(string); ok {
				cfg.Verbosity = &v
			}
		}
	}
	return cfg
}

func intFromRelatedQuestionConfig(value interface{}) (int, bool) {
	switch v := value.(type) {
	case int:
		return v, true
	case int64:
		return int(v), true
	case float64:
		return int(v), true
	case json.Number:
		if n, err := v.Int64(); err == nil {
			return int(n), true
		}
	}
	return 0, false
}

func floatFromRelatedQuestionConfig(value interface{}) (float64, bool) {
	switch v := value.(type) {
	case float64:
		return v, true
	case float32:
		return float64(v), true
	case int:
		return float64(v), true
	case int64:
		return float64(v), true
	case json.Number:
		if n, err := v.Float64(); err == nil {
			return n, true
		}
	}
	return 0, false
}

func stringSliceFromRelatedQuestionConfig(value interface{}) []string {
	switch v := value.(type) {
	case []string:
		return v
	case []interface{}:
		result := make([]string, 0, len(v))
		for _, item := range v {
			if s, ok := item.(string); ok {
				result = append(result, s)
			}
		}
		return result
	}
	return nil
}

var relatedQuestionLineRe = regexp.MustCompile(`^\d+\.\s`)

func parseRelatedQuestions(text string) []string {
	var result []string
	for _, line := range strings.Split(text, "\n") {
		if relatedQuestionLineRe.MatchString(line) {
			result = append(result, relatedQuestionLineRe.ReplaceAllString(line, ""))
		}
	}
	if result == nil {
		return []string{}
	}
	return result
}

func float64Ptr(v float64) *float64 { return &v }
