/*
 * Copyright 2025 coze-dev Authors
 *
 * 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 workflow

import (
	"context"
	"errors"
	"strconv"
	"testing"

	"github.com/cloudwego/eino/schema"
	"github.com/stretchr/testify/assert"
	"go.uber.org/mock/gomock"

	messageentity "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
	"github.com/coze-dev/coze-studio/backend/api/model/workflow"
	crossagentrun "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agentrun"
	"github.com/coze-dev/coze-studio/backend/crossdomain/contract/agentrun/agentrunmock"
	crossupload "github.com/coze-dev/coze-studio/backend/crossdomain/contract/upload"
	"github.com/coze-dev/coze-studio/backend/crossdomain/contract/upload/uploadmock"
	agententity "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
	uploadentity "github.com/coze-dev/coze-studio/backend/domain/upload/entity"
	"github.com/coze-dev/coze-studio/backend/domain/upload/service"
)

func TestApplicationService_makeChatFlowUserInput(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockUpload := uploadmock.NewMockUploader(ctrl)
	crossupload.SetDefaultSVC(mockUpload)

	tests := []struct {
		name      string
		message   *workflow.EnterMessage
		setupMock func()
		expected  string
		expectErr bool
	}{
		{
			name: "content type text",
			message: &workflow.EnterMessage{
				ContentType: "text",
				Content:     "hello",
			},
			setupMock: func() {},
			expected:  "hello",
			expectErr: false,
		},
		{
			name: "content type object_string with text",
			message: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "text", "text": "hello world"}]`,
			},
			setupMock: func() {},
			expected:  "hello world",
			expectErr: false,
		},
		{
			name: "content type object_string with file",
			message: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "file", "file_id": "123"}]`,
			},
			setupMock: func() {
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{
					File: &uploadentity.File{Url: "https://example.com/file"},
				}, nil)
			},
			expected:  "https://example.com/file",
			expectErr: false,
		},
		{
			name: "content type object_string with text and file",
			message: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "text", "text": "see this file"}, {"type": "file", "file_id": "123"}]`,
			},
			setupMock: func() {
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{
					File: &uploadentity.File{Url: "https://example.com/file"},
				}, nil)
			},
			expected:  "see this file,https://example.com/file",
			expectErr: false,
		},
		{
			name: "get file error",
			message: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "file", "file_id": "123"}]`,
			},
			setupMock: func() {
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(nil, errors.New("get file error"))
			},
			expectErr: true,
		},
		{
			name: "file not found",
			message: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "file", "file_id": "123"}]`,
			},
			setupMock: func() {
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{
					File: nil,
				}, nil)
			},
			expectErr: true,
		},
		{
			name: "invalid content type",
			message: &workflow.EnterMessage{
				ContentType: "invalid",
			},
			setupMock: func() {},
			expectErr: true,
		},
		{
			name: "invalid json",
			message: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `invalid-json`,
			},
			setupMock: func() {},
			expectErr: true,
		},
	}

	w := &ApplicationService{}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.setupMock()
			result, err := w.makeChatFlowUserInput(ctx, tt.message)
			if tt.expectErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.expected, result)
			}
		})
	}
}

func Test_toConversationMessage(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockUpload := uploadmock.NewMockUploader(ctrl)
	crossupload.SetDefaultSVC(mockUpload)

	bizID, cid, userID, roundID, sectionID := int64(2), int64(1), int64(4), int64(3), int64(5)

	tests := []struct {
		name        string
		msg         *workflow.EnterMessage
		messageType messageentity.MessageType
		setupMock   func()
		expected    *messageentity.Message
		expectErr   bool
	}{
		{
			name: "content type text",
			msg: &workflow.EnterMessage{
				ContentType: "text",
				Content:     "hello",
			},
			messageType: messageentity.MessageTypeQuestion,
			setupMock:   func() {},
			expected: &messageentity.Message{
				Role:           schema.User,
				ConversationID: cid,
				AgentID:        bizID,
				RunID:          roundID,
				Content:        "hello",
				ContentType:    messageentity.ContentTypeText,
				MessageType:    messageentity.MessageTypeQuestion,
				UserID:         strconv.FormatInt(userID, 10),
				SectionID:      sectionID,
			},
			expectErr: false,
		},
		{
			name: "content type object_string with text",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "text", "text": "hello"}]`,
			},
			messageType: messageentity.MessageTypeQuestion,
			setupMock:   func() {},
			expected: &messageentity.Message{
				Role:           schema.User,
				MessageType:    messageentity.MessageTypeQuestion,
				ConversationID: cid,
				AgentID:        bizID,
				UserID:         strconv.FormatInt(userID, 10),
				RunID:          roundID,
				ContentType:    messageentity.ContentTypeMix,
				MultiContent: []*messageentity.InputMetaData{
					{Type: messageentity.InputTypeText, Text: "hello"},
				},
				SectionID: sectionID,
			},
			expectErr: false,
		},
		{
			name: "content type object_string with file",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "file", "file_id": "123"}]`,
			},
			messageType: messageentity.MessageTypeQuestion,
			setupMock: func() {
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{
					File: &uploadentity.File{Url: "https://example.com/file", TosURI: "tos://uri", Name: "file.txt"},
				}, nil)
			},
			expected: &messageentity.Message{
				Role:           schema.User,
				MessageType:    messageentity.MessageTypeQuestion,
				ConversationID: cid,
				AgentID:        bizID,
				UserID:         strconv.FormatInt(userID, 10),
				RunID:          roundID,
				ContentType:    messageentity.ContentTypeMix,
				MultiContent: []*messageentity.InputMetaData{
					{
						Type: "file",
						FileData: []*messageentity.FileData{
							{Url: "https://example.com/file", URI: "tos://uri", Name: "file.txt"},
						},
					},
				},
				SectionID: sectionID,
			},
			expectErr: false,
		},
		{
			name: "get file error",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "file", "file_id": "123"}]`,
			},
			setupMock: func() {
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(nil, errors.New("get file error"))
			},
			expectErr: true,
		},
		{
			name: "file not found",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "file", "file_id": "123"}]`,
			},
			setupMock: func() {
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{}, nil)
			},
			expectErr: true,
		},
		{
			name: "invalid content type",
			msg: &workflow.EnterMessage{
				ContentType: "invalid",
			},
			setupMock: func() {},
			expectErr: true,
		},
		{
			name: "invalid json",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     "invalid-json",
			},
			setupMock: func() {},
			expectErr: true,
		},
		{
			name: "invalid input type",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "invalid"}]`,
			},
			setupMock: func() {},
			expectErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.setupMock()
			result, err := toConversationMessage(ctx, bizID, cid, userID, roundID, sectionID, tt.messageType, tt.msg)
			if tt.expectErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.expected, result)
			}
		})
	}
}

func Test_toSchemaMessage(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockUpload := uploadmock.NewMockUploader(ctrl)
	crossupload.SetDefaultSVC(mockUpload)

	tests := []struct {
		name      string
		msg       *workflow.EnterMessage
		setupMock func()
		expected  *schema.Message
		expectErr bool
	}{
		{
			name: "content type text",
			msg: &workflow.EnterMessage{
				ContentType: "text",
				Content:     "hello",
			},
			setupMock: func() {},
			expected: &schema.Message{
				Role:    schema.User,
				Content: "hello",
			},
			expectErr: false,
		},
		{
			name: "content type object_string with text",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "text", "text": "hello"}]`,
			},
			setupMock: func() {},
			expected: &schema.Message{
				Role: schema.User,
				MultiContent: []schema.ChatMessagePart{
					{Type: schema.ChatMessagePartTypeText, Text: "hello"},
				},
			},
			expectErr: false,
		},
		{
			name: "content type object_string with image",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "image", "file_id": "123"}]`,
			},
			setupMock: func() {
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{
					File: &uploadentity.File{Url: "https://example.com/image.png"},
				}, nil)
			},
			expected: &schema.Message{
				Role: schema.User,
				MultiContent: []schema.ChatMessagePart{
					{
						Type:     schema.ChatMessagePartTypeImageURL,
						ImageURL: &schema.ChatMessageImageURL{URL: "https://example.com/image.png"},
					},
				},
			},
			expectErr: false,
		},
		{
			name: "content type object_string with various file types",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "file", "file_id": "1"}, {"type": "audio", "file_id": "2"}, {"type": "video", "file_id": "3"}]`,
			},
			setupMock: func() {
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 1}).Return(&service.GetFileResponse{File: &uploadentity.File{Url: "https://example.com/file"}}, nil)
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 2}).Return(&service.GetFileResponse{File: &uploadentity.File{Url: "https://example.com/audio"}}, nil)
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 3}).Return(&service.GetFileResponse{File: &uploadentity.File{Url: "https://example.com/video"}}, nil)
			},
			expected: &schema.Message{
				Role: schema.User,
				MultiContent: []schema.ChatMessagePart{
					{Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URL: "https://example.com/file"}},
					{Type: schema.ChatMessagePartTypeAudioURL, AudioURL: &schema.ChatMessageAudioURL{URL: "https://example.com/audio"}},
					{Type: schema.ChatMessagePartTypeVideoURL, VideoURL: &schema.ChatMessageVideoURL{URL: "https://example.com/video"}},
				},
			},
			expectErr: false,
		},
		{
			name: "get file error",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "file", "file_id": "123"}]`,
			},
			setupMock: func() {
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(nil, errors.New("get file error"))
			},
			expectErr: true,
		},
		{
			name: "file not found",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "file", "file_id": "123"}]`,
			},
			setupMock: func() {
				mockUpload.EXPECT().GetFile(gomock.Any(), &service.GetFileRequest{ID: 123}).Return(&service.GetFileResponse{}, nil)
			},
			expectErr: true,
		},
		{
			name: "invalid content type",
			msg: &workflow.EnterMessage{
				ContentType: "invalid",
			},
			setupMock: func() {},
			expectErr: true,
		},
		{
			name: "invalid json",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     "invalid-json",
			},
			setupMock: func() {},
			expectErr: true,
		},
		{
			name: "invalid input type",
			msg: &workflow.EnterMessage{
				ContentType: "object_string",
				Content:     `[{"type": "invalid"}]`,
			},
			setupMock: func() {},
			expectErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.setupMock()
			result, err := toSchemaMessage(ctx, tt.msg)
			if tt.expectErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.expected, result)
			}
		})
	}
}

func Test_makeChatFlowHistoryMessages(t *testing.T) {
	ctx := context.Background()
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockAgentRun := agentrunmock.NewMockAgentRun(ctrl)
	crossagentrun.SetDefaultSVC(mockAgentRun)
	mockUpload := uploadmock.NewMockUploader(ctrl)
	crossupload.SetDefaultSVC(mockUpload)

	bizID, conversationID, userID, sectionID, connectorID := int64(2), int64(1), int64(3), int64(4), int64(5)

	tests := []struct {
		name      string
		messages  []*workflow.EnterMessage
		setupMock func()
		expected  []*messageentity.Message
		expectErr bool
	}{
		{
			name:      "empty messages",
			messages:  []*workflow.EnterMessage{},
			setupMock: func() {},
			expected:  []*messageentity.Message{},
			expectErr: false,
		},
		{
			name: "one user message",
			messages: []*workflow.EnterMessage{
				{Role: "user", ContentType: "text", Content: "hello"},
			},
			setupMock: func() {
				mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(&agententity.RunRecordMeta{ID: 100}, nil).Times(1)
			},
			expected: []*messageentity.Message{
				{
					Role:           schema.User,
					ConversationID: conversationID,
					AgentID:        bizID,
					RunID:          100,
					Content:        "hello",
					ContentType:    messageentity.ContentTypeText,
					MessageType:    messageentity.MessageTypeQuestion,
					UserID:         strconv.FormatInt(userID, 10),
					SectionID:      sectionID,
				},
			},
			expectErr: false,
		},
		{
			name: "user and assistant message",
			messages: []*workflow.EnterMessage{
				{Role: "user", ContentType: "text", Content: "hello"},
				{Role: "assistant", ContentType: "text", Content: "hi"},
			},
			setupMock: func() {
				mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(&agententity.RunRecordMeta{ID: 100}, nil).Times(1)
			},
			expected: []*messageentity.Message{
				{
					Role:           schema.User,
					ConversationID: conversationID,
					AgentID:        bizID,
					RunID:          100,
					Content:        "hello",
					ContentType:    messageentity.ContentTypeText,
					MessageType:    messageentity.MessageTypeQuestion,
					UserID:         strconv.FormatInt(userID, 10),
					SectionID:      sectionID,
				},
				{
					Role:           schema.User,
					ConversationID: conversationID,
					AgentID:        bizID,
					RunID:          100,
					Content:        "hi",
					ContentType:    messageentity.ContentTypeText,
					MessageType:    messageentity.MessageTypeAnswer,
					UserID:         strconv.FormatInt(userID, 10),
					SectionID:      sectionID,
				},
			},
			expectErr: false,
		},
		{
			name: "only assistant message",
			messages: []*workflow.EnterMessage{
				{Role: "assistant", ContentType: "text", Content: "hi"},
			},
			setupMock: func() {},
			expected:  []*messageentity.Message{},
			expectErr: false,
		},
		{
			name: "create run record error",
			messages: []*workflow.EnterMessage{
				{Role: "user", ContentType: "text", Content: "hello"},
			},
			setupMock: func() {
				mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil, errors.New("db error"))
			},
			expectErr: true,
		},
		{
			name: "invalid role",
			messages: []*workflow.EnterMessage{
				{Role: "system", ContentType: "text", Content: "hello"},
			},
			setupMock: func() {},
			expectErr: true,
		},
		{
			name: "toConversationMessage error",
			messages: []*workflow.EnterMessage{
				{Role: "user", ContentType: "invalid", Content: "hello"},
			},
			setupMock: func() {
				mockAgentRun.EXPECT().Create(gomock.Any(), gomock.Any()).Return(&agententity.RunRecordMeta{ID: 100}, nil)
			},
			expectErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.setupMock()
			result, err := makeChatFlowHistoryMessages(ctx, bizID, conversationID, userID, sectionID, connectorID, tt.messages)
			if tt.expectErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.expected, result)
			}
		})
	}
}
