package v1

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"testing"

	"github.com/pingcap-inc/pchub/pkg/errors"
	"github.com/pingcap-inc/pchub/pkg/log"
	"github.com/pingcap-inc/pingcat/internal/pkg/code"
	v1 "github.com/pingcap-inc/pingcat/model/v1"
)

func TestCustomRoutePromptTemplate(t *testing.T) {
	// Define the expected output based on the registered plugins in appPluginsHub
	expectedDescription := "mock plugin: only useful for when you need to test whether the plugin route works"
	expectedName := "mock plugin"
	question := "test plugin route"

	// Call the customeRoutePromptTemplate function
	actualPrompt := fmt.Sprintf(customeRoutePromptTemplate(), question)

	if !strings.Contains(actualPrompt, expectedDescription) {
		t.Errorf("Expected output:\n%s\nActual output:\n%s", expectedDescription, actualPrompt)
	}
	if !strings.Contains(actualPrompt, expectedName) {
		t.Errorf("Expected output:\n%s\nActual output:\n%s", expectedName, actualPrompt)
	}
	if !strings.Contains(actualPrompt, question) {
		t.Errorf("Expected output:\n%s\nActual output:\n%s", question, actualPrompt)
	}
}

func TestGetRouter(t *testing.T) {
	firstRouter := GetRouter()
	secondRouter := GetRouter()
	// test not nil router and signleton
	if firstRouter == nil || firstRouter != secondRouter {
		t.Errorf("Expected the same router, but got different ones")
	}

	if firstRouter.routePrompt == "" {
		t.Errorf("Expected the router to have a routePrompt, but got empty string")
	}
}

func TestSimpleLLMRouterRoute(t *testing.T) {
	router := GetRouter()
	expectdApp := NewMockPlugin()

	action, answer, error := router.Route(context.Background(), "test plugin route", testLLMwraper)
	if error != nil {
		t.Errorf("Expected no error, but got %v", error)
	}
	if !strings.Contains(strings.ToLower(action), strings.ToLower(expectdApp.Name())) {
		t.Errorf("Expected output:\n%s\nActual output:\n%s", "mock plugin", action)
	}
	if answer == "" {
		t.Errorf("Expected output:\n%s\nActual output:\n%s", "empty string", answer)
	}
}

func testLLMwraper(ctx context.Context, prompt string) (string, error) {
	msgs := []*v1.GPTChatMessage{
		{
			Content: prompt,
			Role:    "user",
		},
	}

	apik, orgID := os.Getenv("OPENAI_API_KEY"), os.Getenv("OPENAI_ORG_ID")
	reqBody := OpenAIChatReqData{
		Model: "gpt-3.5-turbo",
		Messages: append([]*v1.GPTChatMessage{
			{
				Role:    "system",
				Content: "You are an in-house assistant to PingCAP, that means you serve as an internal member of the PingCAP company. Your name is PingCat and your avatar is a cat image created by Midjourney(another image AI).",
			},
		}, msgs...),
	}

	reqb, err := json.Marshal(&reqBody)
	if err != nil {
		return "", errors.WithCode(code.ErrEncodingJSON, err.Error())
	}

	req, _ := http.NewRequestWithContext(ctx, "POST", ChatURL, bytes.NewReader(reqb))
	req.Header.Set("Authorization", "Bearer "+apik)
	if orgID != "" {
		req.Header.Set("OpenAI-Organization", orgID)
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		log.L(ctx).Error(err.Error())

		return "", errors.WithCode(code.ErrOpenAIHttpRequest, "error when request openai chat api, err: %s", err.Error())
	}
	defer func() {
		_ = resp.Body.Close()
	}()

	if resp.StatusCode == http.StatusOK {
		b, _ := io.ReadAll(resp.Body)
		var res OpenAIChatResData
		if err := json.Unmarshal(b, &res); err != nil {
			log.L(ctx).Error(err.Error())

			return "", errors.WithCode(code.ErrDecodingJSON, "error when unmarshal openai chat api res, err: %s", err.Error())
		}

		if len(res.Choices) == 0 || res.Choices[0].Message == nil {
			return "", errors.WithCode(code.ErrNoValidMessage, "no valid message content in openai response, err: %s", err.Error())
		}

		return res.Choices[0].Message.Content, nil
	}
	b, _ := io.ReadAll(resp.Body)
	var res OpenAIChatErrRes
	if err := json.Unmarshal(b, &res); err != nil {
		log.L(ctx).Error(err.Error())

		return "", errors.WithCode(code.ErrDecodingJSON, "error when unmarshal openai chat api err res, err: %s", err.Error())
	}
	if res.Error.Code == OAIErrCodeLengthExceeded {
		return "", errors.WithCode(code.ErrContextLengthExceeded, "error when request openai chat api, err: %s", res.Error.Message)
	}

	return "", errors.WithCode(code.ErrOpenAIHttpRequest, "error when request openai chat api, err: %s", res.Error.Message)

}
