package onboarding

import (
	"context"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	storeonboarding "github.com/tidbcloud/AgentTeam/internal/store/onboarding"
)

func TestMockStartPersistsPromptAndMentionWithoutCallingAgentReply(t *testing.T) {
	state := &fakeMockExchangeStateStore{agentTarget: storeonboarding.MockAgentTarget{
		AgentID: "agt_1", AgentMemberID: "mem_agent_1", AgentMachineID: "mch_1", AgentName: "my_assistant", TutorialChannelID: "chn_quick",
	}}
	messages := &fakeMockMessageStore{}
	fanout := &fakeMockFanout{}
	handler := &MockExchangeHandler{enabled: true, state: state, messages: messages, fanout: fanout}

	request := onboardingJSONRequest(t, dto.DesktopOnboardingMockStartRequest{
		TutorialChannelID: "chn_quick",
		AgentID:           "agt_1",
		PromptContent:     "build a game",
		RequestContent:    "@my_assistant please implement it",
		ClientMessageID:   "client-start",
	})
	response, err := handler.MockStart(request)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.Status)
	require.Len(t, messages.humanCalls, 2)
	require.True(t, messages.humanCalls[0].startAgentThread)
	require.Nil(t, messages.humanCalls[0].mention)
	require.False(t, messages.humanCalls[1].startAgentThread)
	require.Equal(t, &MockMention{AgentID: "agt_1", Token: "my_assistant"}, messages.humanCalls[1].mention)
	require.Equal(t, "client-start:request", messages.humanCalls[1].clientMessageID)
	require.Zero(t, messages.agentReplyCalls)
	require.Equal(t, 2, fanout.calls)
	body := response.Body.(dto.DesktopOnboardingMockStartResponse)
	require.Equal(t, "msg_prompt", body.Message.ID)
	require.Equal(t, "thr_quick", body.ReplyThread.ThreadChannelID)
	require.Equal(t, "msg_request", body.RequestMessage.ID)
}

func TestMockReplyUsesLocalizedSavedAttachmentAndRecordsEvidence(t *testing.T) {
	state := &fakeMockExchangeStateStore{replyTarget: storeonboarding.MockReplyTarget{
		MockAgentTarget: storeonboarding.MockAgentTarget{
			AgentID: "agt_1", AgentMemberID: "mem_agent_1", AgentMachineID: "mch_1",
		},
		ThreadScopeID: "thr_quick", ThreadTargetRef: "#quick-start:abc123", RequestMessageID: "msg_request",
	}}
	messages := &fakeMockMessageStore{}
	attachments := &fakeMockAttachmentStore{}
	fanout := &fakeMockFanout{}
	handler := &MockExchangeHandler{
		enabled: true, state: state, messages: messages,
		attachments: attachments, fanout: fanout,
	}
	request := onboardingJSONRequest(t, dto.DesktopOnboardingMockReplyRequest{
		ThreadScopeID: "thr_quick", AgentID: "agt_1", RequestMessageID: "msg_request", ReplyClientMessageID: "client-reply",
	})
	request.Gin.Request.Header.Set("Accept-Language", "ja-JP")

	response, err := handler.MockReply(request)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.Status)
	require.Contains(t, string(attachments.input.Blob), `<html lang="ja">`)
	require.Equal(t, "index.html", attachments.input.Filename)
	require.Equal(t, mockAttachmentContentType, attachments.input.ContentType)
	require.Contains(t, messages.agentReplyContent, "ピクセル版が完成")
	require.Equal(t, []string{"att_mock"}, messages.agentReplyAttachmentIDs)
	require.Equal(t, "client-reply", messages.agentReplyClientMessageID)
	require.Equal(t, VisibleReplyEvidence{
		WorkspaceID: "wsp_1", AgentID: "agt_1", MessageID: "msg_request",
		ReplyMessageID: "msg_reply", CompleteImmediately: true,
	}, state.evidence)
	require.Equal(t, 1, fanout.calls)
	responseBody := response.Body.(dto.DesktopOnboardingMockReplyResponse)
	require.Equal(t, "msg_reply", responseBody.ReplyMessageID)
	require.True(t, responseBody.Completed)
}

type fakeMockExchangeStateStore struct {
	agentTarget storeonboarding.MockAgentTarget
	replyTarget storeonboarding.MockReplyTarget
	evidence    VisibleReplyEvidence
}

func (store *fakeMockExchangeStateStore) ResolveMockAgentTarget(context.Context, string, string, string, string) (storeonboarding.MockAgentTarget, error) {
	return store.agentTarget, nil
}

func (store *fakeMockExchangeStateStore) ResolveMockReplyTarget(context.Context, string, string, string, string, string, string) (storeonboarding.MockReplyTarget, error) {
	return store.replyTarget, nil
}

func (store *fakeMockExchangeStateStore) RecordVisibleReply(_ context.Context, evidence storeonboarding.VisibleReplyEvidence) error {
	store.evidence = evidence
	return nil
}

type mockHumanCall struct {
	targetScopeID    string
	content          string
	mention          *MockMention
	clientMessageID  string
	startAgentThread bool
}

type fakeMockMessageStore struct {
	humanCalls                []mockHumanCall
	agentReplyCalls           int
	agentReplyContent         string
	agentReplyAttachmentIDs   []string
	agentReplyClientMessageID string
}

func (store *fakeMockMessageStore) SendDesktopOnboardingMockMessageForUser(_ context.Context, _ identity.User, _ string, targetScopeID string, content string, mention *MockMention, clientMessageID string, startAgentThread bool) (message.AppMessage, message.MessageDelivery, error) {
	store.humanCalls = append(store.humanCalls, mockHumanCall{
		targetScopeID: targetScopeID, content: content, mention: mention, clientMessageID: clientMessageID, startAgentThread: startAgentThread,
	})
	if startAgentThread {
		appMessage := message.AppMessage{ID: "msg_prompt", ChannelID: targetScopeID, Content: content, CreatedAt: time.Now()}
		return appMessage, message.MessageDelivery{
			MessageID: appMessage.ID, AppMessage: &appMessage,
			StartedReplyThread: &message.ReplyThreadSummary{ThreadChannelID: "thr_quick", RootMessageID: appMessage.ID},
		}, nil
	}
	appMessage := message.AppMessage{ID: "msg_request", ChannelID: targetScopeID, Content: content, CreatedAt: time.Now()}
	return appMessage, message.MessageDelivery{MessageID: appMessage.ID, AppMessage: &appMessage}, nil
}

func (store *fakeMockMessageStore) SendDesktopOnboardingMockAgentReply(_ context.Context, _ MockAgentIdentity, _ string, content string, attachmentIDs []string, clientMessageID string) (message.MessageDelivery, error) {
	store.agentReplyCalls++
	store.agentReplyContent = content
	store.agentReplyAttachmentIDs = append([]string(nil), attachmentIDs...)
	store.agentReplyClientMessageID = clientMessageID
	appMessage := message.AppMessage{ID: "msg_reply", ChannelID: "thr_quick", Content: content, CreatedAt: time.Now()}
	return message.MessageDelivery{MessageID: appMessage.ID, AppMessage: &appMessage}, nil
}

type fakeMockAttachmentStore struct {
	input MockAttachment
}

func (store *fakeMockAttachmentStore) UploadDesktopOnboardingMockAttachment(_ context.Context, _ string, _ string, _ string, input MockAttachment) (string, error) {
	store.input = input
	return "att_mock", nil
}

type fakeMockFanout struct {
	calls int
}

func (fanout *fakeMockFanout) DeliverCommittedMessage(context.Context, string, message.AppMessage, message.MessageDelivery) error {
	fanout.calls++
	return nil
}

func onboardingJSONRequest[T any](t *testing.T, body T) api.WorkspaceScopedJSON[T] {
	t.Helper()
	request := onboardingRequest(t)
	return api.WorkspaceScopedJSON[T]{WorkspaceScopedRequest: request, Body: body}
}
