package trigger

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"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/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
	"github.com/tidbcloud/AgentTeam/internal/trigger"
)

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

	ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
	ctx.Request = httptest.NewRequest("GET", "/api/v1/workspaces/wsp_1/triggers?source_kind=event&event_kind=github_pull_request.*&limit=25&sourceKind=time&eventKind=gitea_pull_request.*", nil)

	filter, err := listFilterFromRequest(ctx)
	require.NoError(t, err)
	require.Equal(t, trigger.SourceKindEvent, filter.SourceKind)
	require.Equal(t, "github_pull_request.*", filter.EventKind)
	require.Equal(t, 25, filter.Limit)
}

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

	ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
	ctx.Request = httptest.NewRequest("GET", "/api/v1/workspaces/wsp_1/triggers?sourceKind=event&eventKind=github_pull_request.review_submitted", nil)

	filter, err := listFilterFromRequest(ctx)
	require.NoError(t, err)
	require.Equal(t, trigger.SourceKindEvent, filter.SourceKind)
	require.Equal(t, "github_pull_request.review_submitted", filter.EventKind)
	require.Equal(t, trigger.DefaultListLimit, filter.Limit)
}

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

	ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
	ctx.Request = httptest.NewRequest("GET", "/api/v1/workspaces/wsp_1/triggers?limit=many", nil)

	_, err := listFilterFromRequest(ctx)
	require.EqualError(t, err, "invalid limit")
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/triggers", nil)
	setTriggerHandlerAuth(ctx)

	api.HandleWorkspaceScoped(newRouteHandlers(Dependencies{}).collection.List)(ctx)

	require.Equal(t, http.StatusNotImplemented, recorder.Code)
	require.Equal(t, "Trigger service is not configured", jsonError(t, recorder))
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/triggers?source_kind=event&event_kind=github_pull_request.*&limit=25", nil)
	setTriggerHandlerAuth(ctx)
	service := &recordingTriggerService{}

	api.HandleWorkspaceScoped(newRouteHandlers(Dependencies{TriggerService: service}).collection.List)(ctx)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, "user_1", service.listOwnerUserID)
	require.Equal(t, "workspace_1", service.listWorkspaceID)
	require.Equal(t, trigger.SourceKindEvent, service.listFilter.SourceKind)
	require.Equal(t, "github_pull_request.*", service.listFilter.EventKind)
	require.Equal(t, 25, service.listFilter.Limit)

	var payload []trigger.TriggerView
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &payload))
	require.Len(t, payload, 1)
	require.Equal(t, "trg_list", payload[0].ID)
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/triggers", nil)
	setTriggerHandlerAuth(ctx)
	service := &recordingTriggerService{}

	api.HandleWorkspaceScoped(newRouteHandlers(Dependencies{TriggerService: service}).collection.List)(ctx)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, trigger.DefaultListLimit, service.listFilter.Limit)
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_1/triggers", strings.NewReader("{"))
	ctx.Request.Header.Set("Content-Type", "application/json")
	setTriggerHandlerAuth(ctx)

	handleTriggerWorkspaceJSON[dto.TriggerMutationRequest](newRouteHandlers(Dependencies{TriggerService: &fakeTriggerService{}}).collection.Create)(ctx)

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	require.Equal(t, "invalid request body", jsonError(t, recorder))
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_1/triggers", strings.NewReader(`{
		"name":"GitHub watch",
		"source":{"sourceKind":"event"},
		"target":{"ref":"chn_1"},
		"messageTemplate":"{{event_summary}} @helper",
		"mentions":[{"type":" agent ","id":" agt_helper ","token":" @helper "}],
		"humanNotificationMode":"none"
	}`))
	ctx.Request.Header.Set("Content-Type", "application/json")
	setTriggerHandlerAuth(ctx)
	service := &recordingTriggerService{}

	handleTriggerWorkspaceJSON[dto.TriggerMutationRequest](newRouteHandlers(Dependencies{TriggerService: service}).collection.Create)(ctx)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, trigger.HumanNotificationModeNone, service.createInput.HumanNotificationMode)
	require.True(t, service.createInput.MentionHintsSet)
	require.Len(t, service.createInput.MentionHints, 1)
	require.Equal(t, "agent", service.createInput.MentionHints[0].Type)
	require.Equal(t, "agt_helper", service.createInput.MentionHints[0].ID)
	require.Equal(t, "@helper", service.createInput.MentionHints[0].Token)
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/triggers/trg_1/runs?limit=many", nil)
	ctx.Params = gin.Params{{Key: "triggerId", Value: "trg_1"}}
	setTriggerHandlerAuth(ctx)

	api.HandleWorkspaceScoped(newRouteHandlers(Dependencies{TriggerService: &fakeTriggerService{}}).runs.List)(ctx)

	require.Equal(t, http.StatusBadRequest, recorder.Code)
	require.Equal(t, "invalid limit", jsonError(t, recorder))
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodDelete, "/api/v1/workspaces/wsp_1/triggers/trg_1", nil)
	ctx.Params = gin.Params{{Key: "triggerId", Value: "trg_1"}}
	setTriggerHandlerAuth(ctx)
	service := &recordingTriggerService{}

	api.HandleWorkspaceScoped(newRouteHandlers(Dependencies{TriggerService: service}).detail.Delete)(ctx)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, "user_1", service.deleteOwnerUserID)
	require.Equal(t, "workspace_1", service.deleteWorkspaceID)
	require.Equal(t, "trg_1", service.deleteTriggerID)
	var payload struct {
		Deleted bool `json:"deleted"`
	}
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &payload))
	require.True(t, payload.Deleted)
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/schedules/sch_1", nil)
	ctx.Params = gin.Params{{Key: "scheduleId", Value: "sch_1"}}
	setTriggerHandlerAuth(ctx)
	service := &recordingTriggerService{}

	api.HandleWorkspaceScoped(newRouteHandlers(Dependencies{TriggerService: service}).detail.Get)(ctx)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, "sch_1", service.getTriggerID)
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/schedules/sch_1/runs?limit=5", nil)
	ctx.Params = gin.Params{{Key: "scheduleId", Value: "sch_1"}}
	setTriggerHandlerAuth(ctx)
	service := &recordingTriggerService{}

	api.HandleWorkspaceScoped(newRouteHandlers(Dependencies{TriggerService: service}).runs.List)(ctx)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, "sch_1", service.runsTriggerID)
	require.Equal(t, 5, service.runsLimit)
}

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

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_1/triggers/trg_1/runs", nil)
	ctx.Params = gin.Params{{Key: "triggerId", Value: "trg_1"}}
	setTriggerHandlerAuth(ctx)
	service := &recordingTriggerService{}

	api.HandleWorkspaceScoped(newRouteHandlers(Dependencies{TriggerService: service}).runs.List)(ctx)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.Equal(t, "user_1", service.runsOwnerUserID)
	require.Equal(t, "workspace_1", service.runsWorkspaceID)
	require.Equal(t, "trg_1", service.runsTriggerID)
	require.Equal(t, 20, service.runsLimit)
}

func setTriggerHandlerAuth(ctx *gin.Context) {
	api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "user_1", Name: "alice"}})
	api.SetCurrentWorkspace(ctx, workspace.Summary{WorkspaceID: "workspace_1"})
}

func jsonError(t *testing.T, recorder *httptest.ResponseRecorder) string {
	t.Helper()
	var payload struct {
		Error string `json:"error"`
	}
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &payload))
	return payload.Error
}

type fakeTriggerService struct{}

func (fakeTriggerService) ListForOwnerWithFilter(ctx context.Context, ownerUserID string, workspaceID string, filter trigger.ListFilter) ([]trigger.TriggerView, error) {
	return nil, nil
}

func (fakeTriggerService) ValidateForOwner(ctx context.Context, ownerUserID string, workspaceID string, input trigger.UpsertInput, existingTriggerID string) (trigger.ValidateResult, error) {
	return trigger.ValidateResult{}, nil
}

func (fakeTriggerService) CreateForOwner(ctx context.Context, ownerUserID string, workspaceID string, input trigger.UpsertInput) (trigger.TriggerView, error) {
	return trigger.TriggerView{}, nil
}

func (fakeTriggerService) GetForOwner(ctx context.Context, ownerUserID string, workspaceID string, triggerID string) (trigger.TriggerView, error) {
	return trigger.TriggerView{}, nil
}

func (fakeTriggerService) UpdateForOwner(ctx context.Context, ownerUserID string, workspaceID string, triggerID string, input trigger.UpsertInput) (trigger.TriggerView, error) {
	return trigger.TriggerView{}, nil
}

func (fakeTriggerService) PauseForOwner(ctx context.Context, ownerUserID string, workspaceID string, triggerID string) (trigger.TriggerView, error) {
	return trigger.TriggerView{}, nil
}

func (fakeTriggerService) ResumeForOwner(ctx context.Context, ownerUserID string, workspaceID string, triggerID string) (trigger.TriggerView, error) {
	return trigger.TriggerView{}, nil
}

func (fakeTriggerService) DeleteForOwner(ctx context.Context, ownerUserID string, workspaceID string, triggerID string) error {
	return nil
}

func (fakeTriggerService) ListRunsForOwner(ctx context.Context, ownerUserID string, workspaceID string, triggerID string, limit int) ([]trigger.RunView, error) {
	return nil, nil
}

type recordingTriggerService struct {
	fakeTriggerService

	listOwnerUserID string
	listWorkspaceID string
	listFilter      trigger.ListFilter

	createInput trigger.UpsertInput

	getTriggerID string

	deleteOwnerUserID string
	deleteWorkspaceID string
	deleteTriggerID   string

	runsOwnerUserID string
	runsWorkspaceID string
	runsTriggerID   string
	runsLimit       int
}

func (service *recordingTriggerService) ListForOwnerWithFilter(ctx context.Context, ownerUserID string, workspaceID string, filter trigger.ListFilter) ([]trigger.TriggerView, error) {
	service.listOwnerUserID = ownerUserID
	service.listWorkspaceID = workspaceID
	service.listFilter = filter
	return []trigger.TriggerView{{ID: "trg_list", Name: "List trigger"}}, nil
}

func (service *recordingTriggerService) CreateForOwner(ctx context.Context, ownerUserID string, workspaceID string, input trigger.UpsertInput) (trigger.TriggerView, error) {
	service.createInput = input
	return trigger.TriggerView{}, nil
}

func (service *recordingTriggerService) GetForOwner(ctx context.Context, ownerUserID string, workspaceID string, triggerID string) (trigger.TriggerView, error) {
	service.getTriggerID = triggerID
	return trigger.TriggerView{}, nil
}

func (service *recordingTriggerService) DeleteForOwner(ctx context.Context, ownerUserID string, workspaceID string, triggerID string) error {
	service.deleteOwnerUserID = ownerUserID
	service.deleteWorkspaceID = workspaceID
	service.deleteTriggerID = triggerID
	return nil
}

func (service *recordingTriggerService) ListRunsForOwner(ctx context.Context, ownerUserID string, workspaceID string, triggerID string, limit int) ([]trigger.RunView, error) {
	service.runsOwnerUserID = ownerUserID
	service.runsWorkspaceID = workspaceID
	service.runsTriggerID = triggerID
	service.runsLimit = limit
	return []trigger.RunView{}, nil
}
