package manage

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

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

	_, err := normalizeTriggerListFilter(ListFilter{
		EventKind: "future.event",
	})
	require.EqualError(t, err, "event_kind is not enabled for generic Trigger management")
}

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

	normalized, err := normalizeTriggerListFilter(ListFilter{})
	require.NoError(t, err)
	require.Equal(t, 20, normalized.Limit)

	normalized, err = normalizeTriggerListFilter(ListFilter{Limit: 25})
	require.NoError(t, err)
	require.Equal(t, 25, normalized.Limit)

	normalized, err = normalizeTriggerListFilter(ListFilter{Limit: 1000})
	require.NoError(t, err)
	require.Equal(t, 100, normalized.Limit)
}

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

	require.Equal(t, DefaultListLimit, normalizeTriggerRunListLimit(0))
	require.Equal(t, DefaultListLimit, normalizeTriggerRunListLimit(-1))
	require.Equal(t, 25, normalizeTriggerRunListLimit(25))
	require.Equal(t, MaxListLimit, normalizeTriggerRunListLimit(1000))
}

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

	_, err := normalizeTriggerListFilter(ListFilter{
		SourceKind: SourceKindEvent,
	})
	require.EqualError(t, err, "source_kind_not_enabled")
}

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

	ctx := context.Background()
	db := startTriggerRollbackDB(t, ctx)
	now := time.Date(2026, 5, 13, 12, 0, 0, 0, time.UTC)
	fixture := seedOwnedChannelFixture(t, db, now, "trigger_list_limit")
	seedScheduleDefinitionForTriggerList(t, db, fixture, "sch_limit_1", now, now, "one")
	seedScheduleDefinitionForTriggerList(t, db, fixture, "sch_limit_2", now.Add(time.Second), now.Add(time.Second), "two")
	seedScheduleDefinitionForTriggerList(t, db, fixture, "sch_limit_3", now.Add(2*time.Second), now.Add(2*time.Second), "three")

	recordingDB := &triggerQueryRecordingDB{DB: db}
	service := New(nil, recordingDB, nil, ServiceOptions{})
	triggers, err := service.ListForOwnerWithFilter(ctx, fixture.User.ID, fixture.WorkspaceID, ListFilter{Limit: 2})
	require.NoError(t, err)

	require.Len(t, triggers, 2)
	definitionQueries := recordingDB.QueriesContaining("FROM schedule_definitions")
	require.NotEmpty(t, definitionQueries)
	require.Contains(t, definitionQueries[0], "LIMIT $3")
}

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

	ctx := context.Background()
	db := startTriggerRollbackDB(t, ctx)
	now := time.Date(2026, 5, 13, 12, 0, 0, 0, time.UTC)
	fixture := seedOwnedChannelFixture(t, db, now, "trigger_summary_list")
	seedScheduleDefinitionForTriggerList(t, db, fixture, "sch_summary_1", now, now, "large body that belongs in trigger_get")

	recordingDB := &triggerQueryRecordingDB{DB: db}
	service := New(nil, recordingDB, nil, ServiceOptions{})
	summaries, err := service.ListSummariesForOwnerWithFilter(ctx, fixture.User.ID, fixture.WorkspaceID, ListFilter{Limit: 2})
	require.NoError(t, err)

	require.Len(t, summaries, 1)
	require.Equal(t, "sch_summary_1", summaries[0].ID)
	definitionQueries := recordingDB.QueriesContaining("FROM schedule_definitions")
	require.NotEmpty(t, definitionQueries)
	require.Contains(t, definitionQueries[0], "LIMIT $3")
	require.NotContains(t, definitionQueries[0], "message_template")
}

func seedScheduleDefinitionForTriggerList(t *testing.T, db coredb.QueryExecer, fixture ownedChannelFixture, scheduleID string, scheduledAt time.Time, createdAt time.Time, messageTemplate string) {
	t.Helper()

	mustExec(t, db, `
INSERT INTO schedule_definitions (
  id, workspace_id, owner_member_id, principal_member_id, name, status, source_kind, source_type,
  scheduled_at, target_kind, target_ref, delivery_scope_id, permission_check_scope_id,
  message_template, notification_mode, next_occurrence_at, last_failure_detail,
  created_by_member_id, created_by_actor_type, created_by_actor_id, created_via,
  created_at, updated_at
) VALUES (
  $1, $2, $3, $3, $4, 'active', 'time', 'one_shot_absolute',
  $5, 'channel', $6, $6, $6,
  $7, 'none', $5, '',
  $3, 'human', $8, 'test',
  $9, $9
)
`, scheduleID, fixture.WorkspaceID, fixture.MemberID, scheduleID+" name", scheduledAt, fixture.ChannelID, messageTemplate, fixture.User.ID, createdAt)
}

func triggerViewIDs(views []TriggerView) []string {
	ids := make([]string, 0, len(views))
	for _, view := range views {
		ids = append(ids, view.ID)
	}
	return ids
}

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

	require.NoError(t, validateResumeStatus(definitionRecord{Status: StatusPaused}))
	require.NoError(t, validateResumeStatus(definitionRecord{Status: StatusBlocked, LastFailureCode: string(FailureCodeTargetNotFound)}))

	err := validateResumeStatus(definitionRecord{
		Status:          StatusBlocked,
		LastFailureCode: string(FailureCodeProviderConnectionDown),
	})
	require.Error(t, err)
	require.Equal(t, "trigger is blocked", err.Error())

	err = validateResumeStatus(definitionRecord{Status: StatusActive})
	require.Error(t, err)
	require.Equal(t, "only paused or blocked triggers can be resumed", err.Error())
}

type triggerQueryRecordingDB struct {
	coredb.DB
	queries []string
}

func (db *triggerQueryRecordingDB) Query(ctx context.Context, query string, args ...any) (coredb.Rows, error) {
	db.record(query)
	return db.DB.Query(ctx, query, args...)
}

func (db *triggerQueryRecordingDB) QueryRow(ctx context.Context, query string, args ...any) coredb.Row {
	db.record(query)
	return db.DB.QueryRow(ctx, query, args...)
}

func (db *triggerQueryRecordingDB) record(query string) {
	db.queries = append(db.queries, query)
}

func (db *triggerQueryRecordingDB) QueriesContaining(fragment string) []string {
	matches := make([]string, 0)
	for _, query := range db.queries {
		if strings.Contains(query, fragment) {
			matches = append(matches, query)
		}
	}
	return matches
}
