package gateway

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/proto"

	gatewayv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/agent-gateway/gateway/v1"
	gatewayentityv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/agent-gateway/gateway/v1"
	sessionv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/agent-gateway/session/v1"
	gatewayapi "github.com/tidbcloud/aisupport/services/agent-gateway/internal/services/gateway/api"
)

func TestHandlerProcessLarkMessage(t *testing.T) {
	t.Parallel()

	ctrl := gomock.NewController(t)
	service := gatewayapi.NewMockService(ctrl)
	service.EXPECT().ProcessLarkMessage(gomock.Any(), gatewayapi.ProcessLarkMessageRequest{
		Context: &gatewayv1.MessageContext{
			InstanceId:       "ticket-manager",
			WorkingDirectory: "/workspace",
		},
		Message: &gatewayentityv1.LarkMessage{
			MessageId:   "message-1",
			RootId:      "root-1",
			ThreadId:    "thread-1",
			ChatId:      "chat-1",
			ChatType:    "group",
			MessageType: "text",
			Content:     `{"text":"hello"}`,
			SenderType:  "user",
			SenderEmail: "user@example.com",
			Mentions:    []*gatewayentityv1.LarkMention{{Key: "@_user_1", OpenId: "bot-1"}},
		},
	}).Return(gatewayapi.ProcessLarkMessageResult{
		Task:    &sessionv1.Task{TaskId: "task-1"},
		Created: true,
	}, nil)
	handler := &Handler{service: service}

	response, err := handler.ProcessLarkMessage(context.Background(), &gatewayv1.ProcessLarkMessageRequest{
		Context: &gatewayv1.MessageContext{
			InstanceId:       "ticket-manager",
			WorkingDirectory: "/workspace",
		},
		Message: &gatewayentityv1.LarkMessage{
			MessageId:   "message-1",
			RootId:      "root-1",
			ThreadId:    "thread-1",
			ChatId:      "chat-1",
			ChatType:    "group",
			MessageType: "text",
			Content:     `{"text":"hello"}`,
			SenderType:  "user",
			SenderEmail: "user@example.com",
			Mentions:    []*gatewayentityv1.LarkMention{{Key: "@_user_1", OpenId: "bot-1"}},
		},
	})

	require.NoError(t, err)
	require.Equal(t, gatewayv1.ProcessLarkMessageDisposition_PROCESS_LARK_MESSAGE_DISPOSITION_ACCEPTED, response.Disposition)
	require.Equal(t, "task-1", response.Task.TaskId)
}

func TestHandlerProcessMessage(t *testing.T) {
	t.Parallel()

	ctrl := gomock.NewController(t)
	service := gatewayapi.NewMockService(ctrl)
	service.EXPECT().ProcessMessage(gomock.Any(), gomock.Any()).DoAndReturn(
		func(_ context.Context, req gatewayapi.ProcessMessageRequest) (gatewayapi.ProcessMessageResult, error) {
			require.True(t, proto.Equal(&gatewayv1.MessageContext{
				InstanceId:       "ticket-manager",
				WorkingDirectory: "/workspace",
				AgentId:          "remote",
				AgentModel:       "model-override",
			}, req.Context))
			require.Equal(t, "remote", req.Context.GetAgentId())
			require.True(t, proto.Equal(&gatewayentityv1.ApplicationMessage{
				MessageId:   "sync-456",
				SessionKey:  "workspace-timeline/123",
				SenderEmail: "ticket-manager@example.com",
				Content:     "summarize",
			}, req.Message))
			return gatewayapi.ProcessMessageResult{
				Task:    &sessionv1.Task{TaskId: "task-1"},
				Created: true,
			}, nil
		},
	)
	handler := &Handler{service: service}

	response, err := handler.ProcessMessage(context.Background(), &gatewayv1.ProcessMessageRequest{
		Context: &gatewayv1.MessageContext{
			InstanceId:       "ticket-manager",
			WorkingDirectory: "/workspace",
			AgentId:          "remote",
			AgentModel:       "model-override",
		},
		Message: &gatewayentityv1.ApplicationMessage{
			MessageId:   "sync-456",
			SessionKey:  "workspace-timeline/123",
			SenderEmail: "ticket-manager@example.com",
			Content:     "summarize",
		},
	})

	require.NoError(t, err)
	require.Equal(t, gatewayv1.ProcessMessageDisposition_PROCESS_MESSAGE_DISPOSITION_ACCEPTED, response.Disposition)
	require.Equal(t, "task-1", response.Task.TaskId)
}

func TestHandlerReturnsDuplicatedDisposition(t *testing.T) {
	t.Parallel()

	ctrl := gomock.NewController(t)
	service := gatewayapi.NewMockService(ctrl)
	service.EXPECT().ProcessLarkMessage(gomock.Any(), gomock.Any()).Return(gatewayapi.ProcessLarkMessageResult{
		Task: &sessionv1.Task{TaskId: "task-1"},
	}, nil)
	handler := &Handler{service: service}

	response, err := handler.ProcessLarkMessage(context.Background(), &gatewayv1.ProcessLarkMessageRequest{})

	require.NoError(t, err)
	require.Equal(t, gatewayv1.ProcessLarkMessageDisposition_PROCESS_LARK_MESSAGE_DISPOSITION_DUPLICATED, response.Disposition)
}

func TestProcessLarkMessageGRPCGatewayRoute(t *testing.T) {
	t.Parallel()

	ctrl := gomock.NewController(t)
	service := gatewayapi.NewMockService(ctrl)
	service.EXPECT().ProcessLarkMessage(gomock.Any(), gomock.Any()).DoAndReturn(
		func(_ context.Context, req gatewayapi.ProcessLarkMessageRequest) (gatewayapi.ProcessLarkMessageResult, error) {
			require.True(t, proto.Equal(&gatewayv1.MessageContext{
				InstanceId:       "ticket-manager",
				WorkingDirectory: "/workspace",
				AgentId:          "remote",
				AgentModel:       "model-override",
			}, req.Context))
			require.Equal(t, "message-1", req.Message.GetMessageId())
			return gatewayapi.ProcessLarkMessageResult{
				Task:    &sessionv1.Task{TaskId: "task-1"},
				Created: true,
			}, nil
		},
	)
	handler := &Handler{service: service}
	mux := runtime.NewServeMux()
	require.NoError(t, gatewayv1.RegisterGatewayServiceHandlerServer(context.Background(), mux, handler))
	req := httptest.NewRequest(http.MethodPost, "/api/v1/agent-gateway/lark/messages:process", bytes.NewBufferString(`{
  "context": {
    "instanceId": "ticket-manager",
    "workingDirectory": "/workspace",
    "agentId": "remote",
    "agentModel": "model-override"
  },
  "message": {
    "messageId": "message-1",
    "chatId": "chat-1",
    "messageType": "text",
    "content": "{\"text\":\"hello\"}",
    "senderOpenId": "user-1"
  }
}`))
	req.Header.Set("Content-Type", "application/json")
	resp := httptest.NewRecorder()

	mux.ServeHTTP(resp, req)

	require.Equal(t, http.StatusOK, resp.Code)
	var body struct {
		Disposition string `json:"disposition"`
		Task        struct {
			TaskID string `json:"taskId"`
		} `json:"task"`
	}
	require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &body))
	require.Equal(t, "PROCESS_LARK_MESSAGE_DISPOSITION_ACCEPTED", body.Disposition)
	require.Equal(t, "task-1", body.Task.TaskID)
}

func TestProcessMessageGRPCGatewayRoute(t *testing.T) {
	t.Parallel()

	ctrl := gomock.NewController(t)
	service := gatewayapi.NewMockService(ctrl)
	service.EXPECT().ProcessMessage(gomock.Any(), gomock.Any()).DoAndReturn(
		func(_ context.Context, req gatewayapi.ProcessMessageRequest) (gatewayapi.ProcessMessageResult, error) {
			require.True(t, proto.Equal(&gatewayv1.MessageContext{
				InstanceId:       "ticket-manager",
				WorkingDirectory: "/workspace",
				AgentId:          "remote",
				AgentModel:       "model-override",
			}, req.Context))
			require.Equal(t, "remote", req.Context.GetAgentId())
			require.True(t, proto.Equal(&gatewayentityv1.ApplicationMessage{
				MessageId:   "sync-456",
				SessionKey:  "workspace-timeline/123",
				SenderEmail: "ticket-manager@example.com",
				Content:     "summarize",
			}, req.Message))
			return gatewayapi.ProcessMessageResult{
				Task:    &sessionv1.Task{TaskId: "task-1"},
				Created: true,
			}, nil
		},
	)
	handler := &Handler{service: service}
	mux := runtime.NewServeMux()
	require.NoError(t, gatewayv1.RegisterGatewayServiceHandlerServer(context.Background(), mux, handler))
	req := httptest.NewRequest(http.MethodPost, "/api/v1/agent-gateway/messages:process", bytes.NewBufferString(`{
  "context": {
    "instanceId": "ticket-manager",
    "workingDirectory": "/workspace",
    "agentId": "remote",
    "agentModel": "model-override"
  },
  "message": {
    "messageId": "sync-456",
    "sessionKey": "workspace-timeline/123",
    "senderEmail": "ticket-manager@example.com",
    "content": "summarize"
  }
}`))
	req.Header.Set("Content-Type", "application/json")
	resp := httptest.NewRecorder()

	mux.ServeHTTP(resp, req)

	require.Equal(t, http.StatusOK, resp.Code)
	var body struct {
		Disposition string `json:"disposition"`
		Task        struct {
			TaskID string `json:"taskId"`
		} `json:"task"`
	}
	require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &body))
	require.Equal(t, "PROCESS_MESSAGE_DISPOSITION_ACCEPTED", body.Disposition)
	require.Equal(t, "task-1", body.Task.TaskID)
}

func TestProcessMessageGRPCGatewayError(t *testing.T) {
	t.Parallel()

	ctrl := gomock.NewController(t)
	service := gatewayapi.NewMockService(ctrl)
	service.EXPECT().ProcessMessage(gomock.Any(), gomock.Any()).Return(
		gatewayapi.ProcessMessageResult{},
		status.Error(codes.InvalidArgument, "working_directory is required"),
	)
	handler := &Handler{service: service}
	mux := runtime.NewServeMux()
	require.NoError(t, gatewayv1.RegisterGatewayServiceHandlerServer(context.Background(), mux, handler))
	req := httptest.NewRequest(http.MethodPost, "/api/v1/agent-gateway/messages:process", bytes.NewBufferString(`{
  "context": {"instanceId": "ticket-manager"},
  "message": {"messageId": "sync-456"}
}`))
	req.Header.Set("Content-Type", "application/json")
	resp := httptest.NewRecorder()

	mux.ServeHTTP(resp, req)

	require.Equal(t, http.StatusBadRequest, resp.Code)
}
