//
//  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 models

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"ragflow/internal/common"
	"sort"
	"strings"
	"testing"
)

func initProviderManagerWithGiteeForTest(t *testing.T) {
	t.Helper()

	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "gitee.json"), readProviderConfig(t, "gitee.json"), 0o600); err != nil {
		t.Fatalf("write gitee config: %v", err)
	}
	if err := InitProviderManager(dir); err != nil {
		t.Fatalf("InitProviderManager: %v", err)
	}
}

func newGiteeForListModelsTest(baseURL string) *GiteeModel {
	return NewGiteeModel(map[string]string{"default": baseURL}, URLSuffix{Models: "models"})
}

func newGiteeForChatTest(baseURL string) *GiteeModel {
	return NewGiteeModel(map[string]string{"default": baseURL}, URLSuffix{Chat: "chat/completions"})
}

func TestGiteeStreamAcceptsTerminalWithoutDelta(t *testing.T) {
	withSSRFBypass(t)
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Errorf("method=%s, want POST", r.Method)
		}
		w.Header().Set("Content-Type", "text/event-stream")
		_, _ = io.WriteString(w, `data: {"choices":[{"finish_reason":"stop"}]}`+"\n\n")
	}))
	defer srv.Close()

	apiKey := "test-key"
	var sawDone bool
	err := newGiteeForChatTest(srv.URL).ChatStreamlyWithSender(
		t.Context(),
		"gitee-model",
		[]Message{{Role: "user", Content: "x"}},
		&APIConfig{ApiKey: &apiKey}, nil, nil,
		func(content *string, _ *string) error {
			sawDone = content != nil && *content == "[DONE]"
			return nil
		},
	)
	if err != nil {
		t.Fatalf("stream: %v", err)
	}
	if !sawDone {
		t.Fatal("expected [DONE] sentinel")
	}
}

func deepSeekAliasModelsForTest(t *testing.T) map[string]Model {
	t.Helper()

	pm := GetProviderManager()
	if pm == nil {
		t.Fatal("provider manager is nil")
	}

	aliasModels := map[string]Model{}
	for _, model := range pm.AllModels {
		if !strings.HasPrefix(model.Name, "deepseek-") {
			continue
		}
		for _, alias := range model.Alias {
			if alias == "" {
				continue
			}
			if existing, ok := aliasModels[alias]; ok {
				t.Fatalf("duplicate DeepSeek alias %q for %q and %q", alias, existing.Name, model.Name)
			}
			aliasModels[alias] = model
		}
	}
	if len(aliasModels) == 0 {
		t.Fatal("no DeepSeek aliases found in all_models.json")
	}
	return aliasModels
}

func TestGiteeListModelsMapsAllDeepSeekAliasesToModelMetadata(t *testing.T) {
	withSSRFBypass(t)
	ctx := t.Context()
	initProviderManagerWithGiteeForTest(t)
	aliasModels := deepSeekAliasModelsForTest(t)
	aliases := make([]string, 0, len(aliasModels))
	for alias := range aliasModels {
		aliases = append(aliases, alias)
	}
	sort.Strings(aliases)

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			t.Errorf("method=%s, want GET", r.Method)
		}
		if r.URL.Path != "/models" {
			t.Errorf("path=%s, want /models", r.URL.Path)
		}
		if got := r.Header.Get("Content-Type"); got != "application/json" {
			t.Errorf("Content-Type=%q, want application/json", got)
		}

		resp := ModelList{
			Object: "list",
			Models: make([]ModelListItem, 0, len(aliases)+1),
		}
		for _, alias := range aliases {
			resp.Models = append(resp.Models, ModelListItem{ID: alias})
		}
		resp.Models = append(resp.Models, ModelListItem{ID: "unknown-model"})

		w.Header().Set("Content-Type", "application/json")
		if err := json.NewEncoder(w).Encode(resp); err != nil {
			t.Fatalf("encode response: %v", err)
		}
	}))
	defer srv.Close()

	models, err := newGiteeForListModelsTest(srv.URL).ListModels(ctx, &APIConfig{})
	if err != nil {
		t.Fatalf("ListModels: %v", err)
	}
	if got, want := len(models), len(aliases)+1; got != want {
		t.Fatalf("len(models)=%d, want %d", got, want)
	}

	for i, alias := range aliases {
		model := models[i]
		expected := aliasModels[alias]
		if model.Name != alias {
			t.Fatalf("models[%d].Name=%q, want %q", i, model.Name, alias)
		}
		if (model.MaxTokens == nil) != (expected.MaxTokens == nil) {
			t.Fatalf("models[%d] alias %q MaxTokens nil=%t, want nil=%t", i, alias, model.MaxTokens == nil, expected.MaxTokens == nil)
		}
		if model.MaxTokens != nil && expected.MaxTokens != nil && *model.MaxTokens != *expected.MaxTokens {
			t.Fatalf("models[%d] alias %q MaxTokens=%d, want %d", i, alias, *model.MaxTokens, *expected.MaxTokens)
		}
		if strings.Join(model.ModelTypes, ",") != strings.Join(expected.ModelTypes, ",") {
			t.Fatalf("models[%d] alias %q ModelTypes=%v, want %v", i, alias, model.ModelTypes, expected.ModelTypes)
		}
		if (model.Thinking == nil) != (expected.Thinking == nil) {
			t.Fatalf("models[%d] alias %q Thinking nil=%t, want nil=%t", i, alias, model.Thinking == nil, expected.Thinking == nil)
		}
	}

	unknown := models[len(models)-1]
	if unknown.Name != "unknown-model" {
		t.Fatalf("unknown.Name=%q, want unknown-model", unknown.Name)
	}
	if unknown.MaxTokens != nil {
		t.Fatalf("unknown.MaxTokens=%v, want nil", *unknown.MaxTokens)
	}
	if len(unknown.ModelTypes) != 0 {
		t.Fatalf("unknown.ModelTypes=%v, want empty", unknown.ModelTypes)
	}
}

func TestGiteeListModelsKeepsOwnedBySuffixAfterAliasMetadataLookup(t *testing.T) {
	withSSRFBypass(t)
	ctx := t.Context()
	initProviderManagerWithGiteeForTest(t)

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_, _ = io.WriteString(w, `{"object":"list","data":[{"id":"deepseek/deepseek-v4-pro","owned_by":"gitee"}]}`)
	}))
	defer srv.Close()

	models, err := newGiteeForListModelsTest(srv.URL).ListModels(ctx, &APIConfig{})
	if err != nil {
		t.Fatalf("ListModels: %v", err)
	}
	if len(models) != 1 {
		t.Fatalf("len(models)=%d, want 1", len(models))
	}
	model := models[0]
	if model.Name != "deepseek/deepseek-v4-pro@gitee" {
		t.Fatalf("Name=%q, want deepseek/deepseek-v4-pro@gitee", model.Name)
	}
	if model.MaxTokens == nil || *model.MaxTokens != 1048576 {
		t.Fatalf("MaxTokens=%v, want 1048576", model.MaxTokens)
	}
	if len(model.ModelTypes) != 1 || model.ModelTypes[0] != "chat" {
		t.Fatalf("ModelTypes=%v, want [chat]", model.ModelTypes)
	}
	if model.Thinking == nil {
		t.Fatal("Thinking=nil, want DeepSeek V4 Pro thinking metadata")
	}
}

func TestGiteeListModelsIntegration(t *testing.T) {
	withSSRFBypass(t)
	ctx := t.Context()
	if common.GetEnv(common.EnvGiteeListModelsIntegration) != "1" {
		t.Skip("set GITEE_LIST_MODELS_INTEGRATION=1 to call the real Gitee models endpoint")
	}

	initProviderManagerWithGiteeForTest(t)

	baseURL := common.GetEnv(common.EnvGiteeBaseUrl)
	if baseURL == "" {
		baseURL = "https://api.moark.ai/v1"
	}
	apiConfig := &APIConfig{}
	if apiKey := common.GetEnv(common.EnvGiteeApiKey); apiKey != "" {
		apiConfig.ApiKey = &apiKey
	}

	models, err := newGiteeForListModelsTest(baseURL).ListModels(ctx, apiConfig)
	if err != nil {
		t.Fatalf("real Gitee ListModels: %v", err)
	}
	if len(models) == 0 {
		t.Fatal("real Gitee ListModels returned no models")
	}

	var matchedDeepSeek int
	for _, model := range models {
		if strings.Contains(strings.ToLower(model.Name), "deepseek") && len(model.ModelTypes) > 0 {
			matchedDeepSeek++
		}
	}
	if matchedDeepSeek == 0 {
		t.Fatalf("real Gitee ListModels returned %d models but none of the DeepSeek models mapped to all_models metadata: %s", len(models), fmt.Sprint(models))
	}
}
