package agent

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

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

	"github.com/tidbcloud/AgentTeam/internal/schedule/manage"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
)

type fakeScheduleControlService struct {
	createInput      manage.UpsertInput
	updateInput      manage.UpsertInput
	updateScheduleID string
	listFilter       manage.ListFilter
	listResult       []manage.ScheduleView
	runOptions       manage.RunListOptions
	runPage          manage.RunPage
	runPages         []manage.RunPage
	runPageCalls     int
	runErr           error
}

func (f *fakeScheduleControlService) ListSchedulesForOwnerWithFilter(_ context.Context, _ string, _ string, filter manage.ListFilter) ([]manage.ScheduleView, error) {
	f.listFilter = filter
	return f.listResult, nil
}

func (f *fakeScheduleControlService) ValidateScheduleForOwner(context.Context, string, string, manage.UpsertInput, string) (manage.ScheduleValidateResult, error) {
	return manage.ScheduleValidateResult{}, nil
}

func (f *fakeScheduleControlService) CreateScheduleForOwner(_ context.Context, _ string, _ string, input manage.UpsertInput) (manage.ScheduleView, error) {
	f.createInput = input
	return manage.ScheduleView{ID: "sch_created", Name: input.Name, AlsoStartThread: input.AlsoStartThread}, nil
}

func (f *fakeScheduleControlService) GetScheduleForOwner(context.Context, string, string, string) (manage.ScheduleView, error) {
	return manage.ScheduleView{}, nil
}

func (f *fakeScheduleControlService) UpdateScheduleForOwner(_ context.Context, _ string, _ string, scheduleID string, input manage.UpsertInput) (manage.ScheduleView, error) {
	f.updateScheduleID = scheduleID
	f.updateInput = input
	return manage.ScheduleView{ID: scheduleID, Name: input.Name, AlsoStartThread: input.AlsoStartThread}, nil
}

func (f *fakeScheduleControlService) PauseScheduleForOwner(context.Context, string, string, string) (manage.ScheduleView, error) {
	return manage.ScheduleView{}, nil
}

func (f *fakeScheduleControlService) ResumeScheduleForOwner(context.Context, string, string, string) (manage.ScheduleView, error) {
	return manage.ScheduleView{}, nil
}

func (f *fakeScheduleControlService) DeleteScheduleForOwner(context.Context, string, string, string) error {
	return nil
}

func (f *fakeScheduleControlService) ListAutomationRunsForScheduleOwnerPage(_ context.Context, _ string, _ string, _ string, options manage.RunListOptions) (manage.RunPage, error) {
	f.runOptions = options
	if f.runErr != nil {
		return manage.RunPage{}, f.runErr
	}
	if len(f.runPages) > 0 {
		page := f.runPages[min(f.runPageCalls, len(f.runPages)-1)]
		f.runPageCalls++
		return page, nil
	}
	return f.runPage, nil
}

func (f *fakeScheduleControlService) ListAutomationRunsForScheduleOwner(context.Context, string, string, string, int) ([]manage.AutomationRunView, error) {
	return nil, nil
}

func TestScheduleHandlerCreatePassesAlsoStartThread(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	scheduleService := &fakeScheduleControlService{}
	handler := &ScheduleHandler{
		auth: fakeAgentAuthenticator{agent: agentIdentity},
		owners: &fakeTriggerOwnerStore{
			ownerUserID:   "usr_00000000000001",
			targetScopeID: "chn_all",
		},
		schedules: scheduleService,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/schedules", handler.Create)

	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/schedules", bytes.NewReader([]byte(`{
		"name":"Threaded reminder",
		"schedule":{"kind":"after","seconds":300},
		"target":"#all",
		"template":"start a thread later @helper",
		"mentions":[{"type":" agent ","id":" agt_helper ","token":" @helper "}],
		"startThread":true
	}`)))
	request.Header.Set("Authorization", "Bearer test-token")
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.True(t, scheduleService.createInput.AlsoStartThread)
	require.Equal(t, "chn_all", scheduleService.createInput.Target.Ref)
	require.True(t, scheduleService.createInput.MentionHintsSet)
	require.Len(t, scheduleService.createInput.MentionHints, 1)
	require.Equal(t, "agent", scheduleService.createInput.MentionHints[0].Type)
	require.Equal(t, "agt_helper", scheduleService.createInput.MentionHints[0].ID)
	require.Equal(t, "@helper", scheduleService.createInput.MentionHints[0].Token)
	require.Contains(t, response.Body.String(), `"alsoStartThread":true`)
}

func TestScheduleHandlerUpdatePassesAlsoStartThread(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	scheduleService := &fakeScheduleControlService{}
	handler := &ScheduleHandler{
		auth: fakeAgentAuthenticator{agent: agentIdentity},
		owners: &fakeTriggerOwnerStore{
			ownerUserID:   "usr_00000000000001",
			targetScopeID: "chn_all",
		},
		schedules: scheduleService,
	}

	router := gin.New()
	router.PATCH("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/schedules/:scheduleId", handler.Update)

	request := httptest.NewRequest(http.MethodPatch, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/schedules/sch_1", bytes.NewReader([]byte(`{
		"name":"Threaded reminder",
		"schedule":{"kind":"after","seconds":300},
		"target":"#all",
		"template":"start a thread later",
		"startThread":true
	}`)))
	request.Header.Set("Authorization", "Bearer test-token")
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "sch_1", scheduleService.updateScheduleID)
	require.True(t, scheduleService.updateInput.AlsoStartThread)
	require.Equal(t, "chn_all", scheduleService.updateInput.Target.Ref)
}

func TestScheduleHandlerListForwardsFilterQuery(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	scheduleService := &fakeScheduleControlService{}
	handler := &ScheduleHandler{
		auth: fakeAgentAuthenticator{agent: agentIdentity},
		owners: &fakeTriggerOwnerStore{
			ownerUserID:   "usr_00000000000001",
			targetScopeID: "chn_all",
		},
		schedules: scheduleService,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/schedules", handler.List)

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/schedules?status=paused&source=time&target=%23all", nil)
	request.Header.Set("Authorization", "Bearer test-token")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "paused", scheduleService.listFilter.Status)
	require.Equal(t, manage.SourceKindTime, scheduleService.listFilter.SourceKind)
	// The single self-describing target is resolved server-side to the stored
	// scope id, and its kind is derived from the author target syntax.
	require.Equal(t, manage.TargetKindChannel, scheduleService.listFilter.TargetKind)
	require.Equal(t, "chn_all", scheduleService.listFilter.TargetRef)
}

func TestScheduleHandlerListEmitsCurrentAndLegacyPagination(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	scheduleService := &fakeScheduleControlService{
		listResult: []manage.ScheduleView{{ID: "sch_1"}, {ID: "sch_2"}},
	}
	handler := &ScheduleHandler{
		auth: fakeAgentAuthenticator{agent: agentIdentity},
		owners: &fakeTriggerOwnerStore{
			ownerUserID: "usr_00000000000001",
		},
		schedules: scheduleService,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/schedules", handler.List)

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/schedules?limit=1&page=true", nil)
	request.Header.Set("Authorization", "Bearer test-token")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, 2, scheduleService.listFilter.Limit)
	require.True(t, scheduleService.listFilter.Page)

	var payload map[string]any
	require.NoError(t, json.Unmarshal(response.Body.Bytes(), &payload))
	require.Len(t, payload["schedules"], 1)
	require.Equal(t, payload["hasMore"], payload["has_more"])
	require.Equal(t, true, payload["hasMore"])
	require.Equal(t, payload["nextCursor"], payload["next_cursor"])
	require.Equal(t, "1", payload["nextCursor"])
}

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

	cases := map[string]manage.TargetKind{
		"#tsk_1":      manage.TargetKindTask,
		"#ops":        manage.TargetKindChannel,
		"#ops:abc123": manage.TargetKindReplyThread,
		"dm:@user":    manage.TargetKindDM,
	}
	for target, expected := range cases {
		actual, err := scheduleListTargetKind(target)
		require.NoError(t, err, "target=%q", target)
		require.Equal(t, expected, actual, "target=%q", target)
	}

	_, err := scheduleListTargetKind("workflow:plan_1")
	require.ErrorContains(t, err, "does not support schedules")
}
