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/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/trigger"
)

// --- fakes for TriggerHandler narrow interfaces ---

type fakeTriggerOwnerStore struct {
	ownerUserID   string
	targetScopeID string
	ownerErr      error
	targetErr     error
}

func (f *fakeTriggerOwnerStore) AgentManagedByUser(_ context.Context, _ string, _ string) (string, error) {
	return f.ownerUserID, f.ownerErr
}

func (f *fakeTriggerOwnerStore) ResolveWritableAgentTargetScopeID(_ context.Context, _ agent.AuthenticatedAgent, _ string) (string, error) {
	return f.targetScopeID, f.targetErr
}

type fakeTriggerControlService struct {
	triggers    []trigger.TriggerView
	summaries   []trigger.TriggerSummaryView
	trigger     trigger.TriggerView
	listErr     error
	getErr      error
	deleteErr   error
	createInput trigger.UpsertInput
	runViews    []trigger.RunView
	runLimit    int
	getID       string
	runsID      string
}

func (f *fakeTriggerControlService) ListSummariesForOwnerWithFilter(_ context.Context, _ string, _ string, filter trigger.ListFilter) ([]trigger.TriggerSummaryView, error) {
	if f.listErr != nil {
		return nil, f.listErr
	}
	summaries := f.summaries
	if summaries == nil {
		summaries = make([]trigger.TriggerSummaryView, 0, len(f.triggers))
		for _, view := range f.triggers {
			summaries = append(summaries, trigger.TriggerSummaryView{
				ID:               view.ID,
				Name:             view.Name,
				Status:           view.Status,
				SourceKind:       view.SourceKind,
				TimeMode:         view.TimeMode,
				Target:           view.Target,
				NextOccurrenceAt: view.NextOccurrenceAt,
				LastFailureCode:  view.LastFailureCode,
			})
		}
	}
	offset := filter.Offset
	if offset > len(summaries) {
		offset = len(summaries)
	}
	summaries = summaries[offset:]
	if filter.Limit > 0 && len(summaries) > filter.Limit {
		summaries = summaries[:filter.Limit]
	}
	return summaries, nil
}

func (f *fakeTriggerControlService) ValidateForOwner(context.Context, string, string, trigger.UpsertInput, string) (trigger.ValidateResult, error) {
	return trigger.ValidateResult{}, nil
}

func (f *fakeTriggerControlService) CreateForOwner(_ context.Context, _ string, _ string, input trigger.UpsertInput) (trigger.TriggerView, error) {
	f.createInput = input
	return trigger.TriggerView{}, nil
}

func (f *fakeTriggerControlService) GetForOwner(_ context.Context, _ string, _ string, triggerID string) (trigger.TriggerView, error) {
	f.getID = triggerID
	return f.trigger, f.getErr
}

func (f *fakeTriggerControlService) UpdateForOwner(context.Context, string, string, string, trigger.UpsertInput) (trigger.TriggerView, error) {
	return trigger.TriggerView{}, nil
}

func (f *fakeTriggerControlService) PauseForOwner(context.Context, string, string, string) (trigger.TriggerView, error) {
	return trigger.TriggerView{}, nil
}

func (f *fakeTriggerControlService) ResumeForOwner(context.Context, string, string, string) (trigger.TriggerView, error) {
	return trigger.TriggerView{}, nil
}

func (f *fakeTriggerControlService) DeleteForOwner(_ context.Context, _ string, _ string, _ string) error {
	return f.deleteErr
}

func (f *fakeTriggerControlService) ListRunsForOwner(_ context.Context, _ string, _ string, triggerID string, limit int) ([]trigger.RunView, error) {
	f.runsID = triggerID
	f.runLimit = limit
	return f.runViews, nil
}

// --- tests ---

func TestTriggerHandlerListRejectsUnauthenticated(t *testing.T) {
	gin.SetMode(gin.TestMode)
	handler := &TriggerHandler{}

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

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/triggers", nil)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusUnauthorized, response.Code)
}

func TestTriggerHandlerListReturnsNotConfigured(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	handler := &TriggerHandler{
		auth:     fakeAgentAuthenticator{agent: agentIdentity},
		owners:   &fakeTriggerOwnerStore{},
		triggers: nil, // not configured
	}

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

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/triggers", nil)
	request.Header.Set("Authorization", "Bearer test-token")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusNotImplemented, response.Code)
	require.Contains(t, response.Body.String(), "not configured")
}

func TestTriggerHandlerListSuccess(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	handler := &TriggerHandler{
		auth:   fakeAgentAuthenticator{agent: agentIdentity},
		owners: &fakeTriggerOwnerStore{ownerUserID: "usr_00000000000001"},
		triggers: &fakeTriggerControlService{
			triggers: []trigger.TriggerView{{ID: "trg_1", Name: "Daily Report", MessageTemplate: "body stays on trigger_get"}},
		},
	}

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

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/triggers", nil)
	request.Header.Set("Authorization", "Bearer test-token")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	var result []map[string]interface{}
	require.NoError(t, json.Unmarshal(response.Body.Bytes(), &result))
	require.Len(t, result, 1)
	require.Equal(t, "trg_1", result[0]["id"])
	require.NotContains(t, response.Body.String(), "messageTemplate")
	require.NotContains(t, response.Body.String(), "body stays on trigger_get")
}

func TestTriggerHandlerListPageReturnsCursorMetadata(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	handler := &TriggerHandler{
		auth:   fakeAgentAuthenticator{agent: agentIdentity},
		owners: &fakeTriggerOwnerStore{ownerUserID: "usr_00000000000001"},
		triggers: &fakeTriggerControlService{
			triggers: []trigger.TriggerView{
				{ID: "trg_1", Name: "Daily Report"},
				{ID: "trg_2", Name: "Weekly Report"},
			},
		},
	}

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

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

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	var result struct {
		Triggers   []map[string]any `json:"triggers"`
		Limit      int              `json:"limit"`
		HasMore    bool             `json:"has_more"`
		NextCursor string           `json:"next_cursor"`
	}
	require.NoError(t, json.Unmarshal(response.Body.Bytes(), &result))
	require.Len(t, result.Triggers, 1)
	require.Equal(t, "trg_1", result.Triggers[0]["id"])
	require.Equal(t, 1, result.Limit)
	require.True(t, result.HasMore)
	require.Equal(t, "1", result.NextCursor)
}

func TestTriggerHandlerGetSuccess(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	handler := &TriggerHandler{
		auth:   fakeAgentAuthenticator{agent: agentIdentity},
		owners: &fakeTriggerOwnerStore{ownerUserID: "usr_00000000000001"},
		triggers: &fakeTriggerControlService{
			trigger: trigger.TriggerView{ID: "trg_1", Name: "Daily Report"},
		},
	}

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

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/triggers/trg_1", nil)
	request.Header.Set("Authorization", "Bearer test-token")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
}

func TestTriggerHandlerScheduleGetUsesScheduleID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	triggerService := &fakeTriggerControlService{}
	handler := &TriggerHandler{
		auth:     fakeAgentAuthenticator{agent: agentIdentity},
		owners:   &fakeTriggerOwnerStore{ownerUserID: "usr_00000000000001"},
		triggers: triggerService,
	}

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

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/schedules/sch_1", 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, "sch_1", triggerService.getID)
}

func TestTriggerHandlerScheduleRunsUsesScheduleID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	triggerService := &fakeTriggerControlService{}
	handler := &TriggerHandler{
		auth:     fakeAgentAuthenticator{agent: agentIdentity},
		owners:   &fakeTriggerOwnerStore{ownerUserID: "usr_00000000000001"},
		triggers: triggerService,
	}

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

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/schedules/sch_1/runs", 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, "sch_1", triggerService.runsID)
}

func TestTriggerHandlerDeleteSuccess(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	handler := &TriggerHandler{
		auth:     fakeAgentAuthenticator{agent: agentIdentity},
		owners:   &fakeTriggerOwnerStore{ownerUserID: "usr_00000000000001"},
		triggers: &fakeTriggerControlService{},
	}

	router := gin.New()
	router.DELETE("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/triggers/:triggerId", handler.Delete)

	request := httptest.NewRequest(http.MethodDelete, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/triggers/trg_1", nil)
	request.Header.Set("Authorization", "Bearer test-token")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Contains(t, response.Body.String(), `"deleted":true`)
}

func TestTriggerHandlerListRunsClampsLimit(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	triggerService := &fakeTriggerControlService{
		runViews: []trigger.RunView{{ID: "run_1", TriggerID: "trg_1"}},
	}
	handler := &TriggerHandler{
		auth:     fakeAgentAuthenticator{agent: agentIdentity},
		owners:   &fakeTriggerOwnerStore{ownerUserID: "usr_00000000000001"},
		triggers: triggerService,
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/triggers/:triggerId/runs", handler.ListRuns)

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/triggers/trg_1/runs?limit=1000", 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, trigger.MaxListLimit, triggerService.runLimit)
}

func TestTriggerHandlerListRunsRejectsInvalidLimit(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	handler := &TriggerHandler{
		auth:     fakeAgentAuthenticator{agent: agentIdentity},
		owners:   &fakeTriggerOwnerStore{ownerUserID: "usr_00000000000001"},
		triggers: &fakeTriggerControlService{},
	}

	router := gin.New()
	router.GET("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/triggers/:triggerId/runs", handler.ListRuns)

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/triggers/trg_1/runs?limit=0", nil)
	request.Header.Set("Authorization", "Bearer test-token")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusBadRequest, response.Code)
	require.Contains(t, response.Body.String(), "invalid limit")
}

func TestTriggerHandlerListPropagatesOwnerError(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	handler := &TriggerHandler{
		auth:     fakeAgentAuthenticator{agent: agentIdentity},
		owners:   &fakeTriggerOwnerStore{ownerErr: storeerr.ErrNotFound},
		triggers: &fakeTriggerControlService{},
	}

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

	request := httptest.NewRequest(http.MethodGet, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/triggers", nil)
	request.Header.Set("Authorization", "Bearer test-token")
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusNotFound, response.Code)
}

func TestTriggerHandlerCreateRejectsInvalidBody(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	handler := &TriggerHandler{
		auth:     fakeAgentAuthenticator{agent: agentIdentity},
		owners:   &fakeTriggerOwnerStore{ownerUserID: "usr_00000000000001"},
		triggers: &fakeTriggerControlService{},
	}

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

	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/triggers", bytes.NewReader([]byte("not json")))
	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.StatusBadRequest, response.Code)
}

func TestTriggerHandlerCreatePassesHumanNotificationModeNone(t *testing.T) {
	gin.SetMode(gin.TestMode)
	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test"}
	triggerService := &fakeTriggerControlService{}
	handler := &TriggerHandler{
		auth: fakeAgentAuthenticator{agent: agentIdentity},
		owners: &fakeTriggerOwnerStore{
			ownerUserID:   "usr_00000000000001",
			targetScopeID: "scp_1",
		},
		triggers: triggerService,
	}

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

	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/triggers", bytes.NewReader([]byte(`{
		"name":"GitHub watch",
		"source":{"sourceKind":"event"},
		"target":"#all",
		"messageTemplate":"{{event_summary}} @helper",
		"mentions":[{"type":" agent ","id":" agt_helper ","token":" @helper "}],
		"humanNotificationMode":"none"
	}`)))
	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, trigger.HumanNotificationModeNone, triggerService.createInput.HumanNotificationMode)
	require.True(t, triggerService.createInput.MentionHintsSet)
	require.Len(t, triggerService.createInput.MentionHints, 1)
	require.Equal(t, "agent", triggerService.createInput.MentionHints[0].Type)
	require.Equal(t, "agt_helper", triggerService.createInput.MentionHints[0].ID)
	require.Equal(t, "@helper", triggerService.createInput.MentionHints[0].Token)
}
