package im

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

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

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

// capturingNotificationStore records cursor/limit calls per surface for
// route-level assertions. It implements both ChannelNotificationStore and
// ThreadNotificationStore.
type capturingNotificationStore struct {
	panel          message.ScopeNotificationPanel
	channelGetCall struct {
		workspaceID string
		scopeID     string
		cursor      string
		limit       int
	}
	threadGetCall struct {
		workspaceID string
		scopeID     string
		cursor      string
		limit       int
	}
}

func (s *capturingNotificationStore) GetChannelNotificationPanelForUser(_ context.Context, _ string, workspaceID string, channelID string, agentCursor string, agentLimit int) (message.ScopeNotificationPanel, error) {
	s.channelGetCall.workspaceID = workspaceID
	s.channelGetCall.scopeID = channelID
	s.channelGetCall.cursor = agentCursor
	s.channelGetCall.limit = agentLimit
	return s.panel, nil
}

func (s *capturingNotificationStore) SetChannelNotificationSelfStateForUser(context.Context, string, string, string, string) (message.ScopeNotificationPanel, error) {
	return s.panel, nil
}

func (s *capturingNotificationStore) SetChannelNotificationAgentWakeStateForUser(context.Context, string, string, string, string, string) (message.ScopeNotificationPanel, error) {
	return s.panel, nil
}

func (s *capturingNotificationStore) GetThreadNotificationPanelForUser(_ context.Context, _ string, workspaceID string, threadChannelID string, agentCursor string, agentLimit int) (message.ScopeNotificationPanel, error) {
	s.threadGetCall.workspaceID = workspaceID
	s.threadGetCall.scopeID = threadChannelID
	s.threadGetCall.cursor = agentCursor
	s.threadGetCall.limit = agentLimit
	return s.panel, nil
}

func (s *capturingNotificationStore) SetThreadNotificationSelfStateForUser(context.Context, string, string, string, string) (message.ScopeNotificationPanel, error) {
	return s.panel, nil
}

func (s *capturingNotificationStore) SetThreadNotificationAgentWakeStateForUser(context.Context, string, string, string, string, string) (message.ScopeNotificationPanel, error) {
	return s.panel, nil
}

func (s *capturingNotificationStore) SetNotificationAgentsWakeStateForUser(context.Context, string, string, string, string, string) (message.ScopeNotificationPanel, error) {
	return s.panel, nil
}

func requireIMHTTPError(t *testing.T, err error, status int, message string) {
	t.Helper()
	var httpErr api.HTTPError
	require.ErrorAs(t, err, &httpErr)
	require.Equal(t, status, httpErr.Status)
	require.Equal(t, message, httpErr.Message)
}

func TestScopeNotificationsHandlerGetPassesCursorAndLimitForChannelScope(t *testing.T) {
	gin.SetMode(gin.TestMode)
	store := &capturingNotificationStore{
		panel: message.ScopeNotificationPanel{ScopeID: "chn_1", ScopeType: "channel"},
	}
	handler := &ScopeExtrasHandler{
		channelNotifications: store,
		conversations: fakeConversationStore{item: message.ConversationItem{
			ScopeID: "chn_1",
			Kind:    message.ConversationKindChannel,
		}},
	}

	request := imWorkspaceRequest(gin.Param{Key: "scopeId", Value: "chn_1"})
	request.Gin.Request = httptest.NewRequest(http.MethodGet, "/?agentCursor=abc&agentLimit=42", nil)
	response, err := handler.NotificationsGet(request)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.Status)
	require.Equal(t, "abc", store.channelGetCall.cursor)
	require.Equal(t, 42, store.channelGetCall.limit)
	require.Equal(t, "chn_1", store.channelGetCall.scopeID)
	require.Equal(t, "chn_1", response.Body.(dto.ScopeNotificationPanel).ScopeID)
}

func TestScopeNotificationsHandlerGetPassesCursorAndLimitForThreadScope(t *testing.T) {
	gin.SetMode(gin.TestMode)
	store := &capturingNotificationStore{
		panel: message.ScopeNotificationPanel{ScopeID: "thr_1", ScopeType: "thread"},
	}
	handler := &ScopeExtrasHandler{
		threadNotifications: store,
		conversations: fakeConversationStore{item: message.ConversationItem{
			ScopeID: "thr_1",
			Kind:    message.ConversationKindThread,
		}},
	}

	request := imWorkspaceRequest(gin.Param{Key: "scopeId", Value: "thr_1"})
	request.Gin.Request = httptest.NewRequest(http.MethodGet, "/?agentCursor=xyz&agentLimit=7", nil)
	response, err := handler.NotificationsGet(request)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.Status)
	require.Equal(t, "xyz", store.threadGetCall.cursor)
	require.Equal(t, 7, store.threadGetCall.limit)
	require.Equal(t, "thr_1", store.threadGetCall.scopeID)
	require.Equal(t, "thr_1", response.Body.(dto.ScopeNotificationPanel).ScopeID)
}

func TestScopeNotificationsHandlerGetRejectsInvalidLimit(t *testing.T) {
	gin.SetMode(gin.TestMode)
	store := &capturingNotificationStore{}
	handler := &ScopeExtrasHandler{
		channelNotifications: store,
		conversations: fakeConversationStore{item: message.ConversationItem{
			ScopeID: "chn_1",
			Kind:    message.ConversationKindChannel,
		}},
	}

	for _, raw := range []string{"0", "-1", "abc", "1.5"} {
		request := imWorkspaceRequest(gin.Param{Key: "scopeId", Value: "chn_1"})
		request.Gin.Request = httptest.NewRequest(http.MethodGet, "/?agentLimit="+raw, nil)
		_, err := handler.NotificationsGet(request)
		requireIMHTTPError(t, err, http.StatusBadRequest, "agentLimit must be a positive integer")
	}
}

func TestScopeNotificationsHandlerGetRejectsOversizeLimit(t *testing.T) {
	gin.SetMode(gin.TestMode)
	store := &capturingNotificationStore{}
	handler := &ScopeExtrasHandler{
		channelNotifications: store,
		conversations: fakeConversationStore{item: message.ConversationItem{
			ScopeID: "chn_1",
			Kind:    message.ConversationKindChannel,
		}},
	}

	request := imWorkspaceRequest(gin.Param{Key: "scopeId", Value: "chn_1"})
	request.Gin.Request = httptest.NewRequest(http.MethodGet, "/?agentLimit=501", nil)
	_, err := handler.NotificationsGet(request)
	requireIMHTTPError(t, err, http.StatusBadRequest, "agentLimit exceeds the maximum page size")
}

func TestScopeNotificationsHandlerGetRejectsOversizeCursor(t *testing.T) {
	gin.SetMode(gin.TestMode)
	store := &capturingNotificationStore{}
	handler := &ScopeExtrasHandler{
		channelNotifications: store,
		conversations: fakeConversationStore{item: message.ConversationItem{
			ScopeID: "chn_1",
			Kind:    message.ConversationKindChannel,
		}},
	}

	huge := make([]byte, 2048)
	for i := range huge {
		huge[i] = 'a'
	}
	request := imWorkspaceRequest(gin.Param{Key: "scopeId", Value: "chn_1"})
	request.Gin.Request = httptest.NewRequest(http.MethodGet, "/?agentCursor="+string(huge), nil)
	_, err := handler.NotificationsGet(request)
	requireIMHTTPError(t, err, http.StatusBadRequest, "agentCursor is too long")
}

func TestScopeNotificationsHandlerAgentsSetRoutesChannelScope(t *testing.T) {
	gin.SetMode(gin.TestMode)
	store := &capturingNotificationStore{
		panel: message.ScopeNotificationPanel{ScopeID: "chn_1", ScopeType: "channel"},
	}
	handler := &ScopeExtrasHandler{
		channelNotifications: store,
		conversations: fakeConversationStore{item: message.ConversationItem{
			ScopeID: "chn_1",
			Kind:    message.ConversationKindChannel,
		}},
	}

	request := imWorkspaceJSON(dto.NotificationWakeStateRequest{WakeState: "no-wake"}, gin.Param{Key: "scopeId", Value: "chn_1"})
	response, err := handler.NotificationsAgentsSet(request)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.Status)
	require.Equal(t, "chn_1", response.Body.(dto.ScopeNotificationPanel).ScopeID)
	require.Equal(t, "channel", response.Body.(dto.ScopeNotificationPanel).ScopeType)
}

func TestScopeNotificationsHandlerAgentsSetRoutesThreadScope(t *testing.T) {
	gin.SetMode(gin.TestMode)
	store := &capturingNotificationStore{
		panel: message.ScopeNotificationPanel{ScopeID: "thr_1", ScopeType: "thread"},
	}
	handler := &ScopeExtrasHandler{
		threadNotifications: store,
		conversations: fakeConversationStore{item: message.ConversationItem{
			ScopeID: "thr_1",
			Kind:    message.ConversationKindThread,
		}},
	}

	request := imWorkspaceJSON(dto.NotificationWakeStateRequest{WakeState: "auto-wake"}, gin.Param{Key: "scopeId", Value: "thr_1"})
	response, err := handler.NotificationsAgentsSet(request)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.Status)
	require.Equal(t, "thr_1", response.Body.(dto.ScopeNotificationPanel).ScopeID)
	require.Equal(t, "thread", response.Body.(dto.ScopeNotificationPanel).ScopeType)
}
