package runtime

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/store/automation"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func TestAutomationRepoStatusOutboxWorkerDeliversCommitStatus(t *testing.T) {
	store, db := newRepoStatusWorkerStore(t)
	now := time.Date(2026, 6, 14, 10, 0, 0, 0, time.UTC)
	row := automationRepoStatusWorkerRow(now)
	require.NoError(t, db.Create(&row).Error)
	writer := &recordingRepoStatusWriter{}
	worker := NewAutomationRepoStatusOutboxWorker(store, writer, AutomationRepoStatusOutboxWorkerConfig{
		LeaseOwner:    "worker-1",
		LeaseDuration: time.Minute,
		TargetBaseURL: "https://meshx.example.test/base",
		Now:           func() time.Time { return now },
	}, nil)

	result, err := worker.RunOnce(context.Background(), 10)

	require.NoError(t, err)
	require.Equal(t, AutomationRepoStatusOutboxWorkerResult{Leased: 1, Delivered: 1}, result)
	require.Len(t, writer.requests, 1)
	require.Equal(t, AutomationCommitStatusRequest{
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		CommitSHA:       "2222222222222222222222222222222222222222",
		State:           automation.AutomationRepoStatusStateSuccess,
		Context:         "meshx/automation/.agentteam/build.yml",
		Description:     "Automation Build succeeded",
		TargetURL:       "https://meshx.example.test/base/w/ws-1/automations/runs/wfr_repo_status",
	}, writer.requests[0])
	var delivered automation.AutomationRepoStatusOutbox
	require.NoError(t, db.First(&delivered, "id = ?", row.ID).Error)
	require.Equal(t, automation.AutomationRepoStatusOutboxStatusDelivered, delivered.Status)
	require.Empty(t, delivered.LeaseOwner)
	require.Nil(t, delivered.LeaseExpiresAt)
	require.NotNil(t, delivered.DeliveredAt)
}

func TestAutomationRepoStatusOutboxWorkerRetriesFailure(t *testing.T) {
	store, db := newRepoStatusWorkerStore(t)
	now := time.Date(2026, 6, 14, 10, 30, 0, 0, time.UTC)
	row := automationRepoStatusWorkerRow(now)
	require.NoError(t, db.Create(&row).Error)
	worker := NewAutomationRepoStatusOutboxWorker(store, &recordingRepoStatusWriter{err: errors.New("AGS 500")}, AutomationRepoStatusOutboxWorkerConfig{
		LeaseOwner:    "worker-1",
		LeaseDuration: time.Minute,
		Now:           func() time.Time { return now },
	}, nil)

	result, err := worker.RunOnce(context.Background(), 10)

	require.NoError(t, err)
	require.Equal(t, AutomationRepoStatusOutboxWorkerResult{Leased: 1, Retried: 1}, result)
	var retried automation.AutomationRepoStatusOutbox
	require.NoError(t, db.First(&retried, "id = ?", row.ID).Error)
	require.Equal(t, automation.AutomationRepoStatusOutboxStatusPending, retried.Status)
	require.Equal(t, 1, retried.Attempts)
	require.True(t, retried.NextAttemptAt.After(now))
	require.Empty(t, retried.LeaseOwner)
	require.Nil(t, retried.LeaseExpiresAt)
	require.Contains(t, retried.LastError, "AGS 500")
}

func TestAutomationRepoStatusOutboxWorkerMarksFailedAfterMaxAttempts(t *testing.T) {
	store, db := newRepoStatusWorkerStore(t)
	now := time.Date(2026, 6, 14, 11, 0, 0, 0, time.UTC)
	row := automationRepoStatusWorkerRow(now)
	row.Attempts = 1
	row.MaxAttempts = 2
	require.NoError(t, db.Create(&row).Error)
	worker := NewAutomationRepoStatusOutboxWorker(store, &recordingRepoStatusWriter{err: errors.New("permission denied")}, AutomationRepoStatusOutboxWorkerConfig{
		LeaseOwner: "worker-1",
		Now:        func() time.Time { return now },
	}, nil)

	result, err := worker.RunOnce(context.Background(), 10)

	require.NoError(t, err)
	require.Equal(t, AutomationRepoStatusOutboxWorkerResult{Leased: 1, Failed: 1}, result)
	var failed automation.AutomationRepoStatusOutbox
	require.NoError(t, db.First(&failed, "id = ?", row.ID).Error)
	require.Equal(t, automation.AutomationRepoStatusOutboxStatusFailed, failed.Status)
	require.Equal(t, 2, failed.Attempts)
	require.Equal(t, now, failed.NextAttemptAt)
	require.Empty(t, failed.LeaseOwner)
	require.Nil(t, failed.LeaseExpiresAt)
	require.Contains(t, failed.LastError, "permission denied")
}

func newRepoStatusWorkerStore(t *testing.T) (*automation.Store, *gorm.DB) {
	t.Helper()
	db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.AutoMigrate(&automation.AutomationRepoStatusOutbox{}))
	return automation.New(db), db
}

func automationRepoStatusWorkerRow(now time.Time) automation.AutomationRepoStatusOutbox {
	return automation.AutomationRepoStatusOutbox{
		ID:              "wfrs_worker",
		WorkspaceID:     "ws-1",
		RunID:           "wfr_repo_status",
		RunCreatedAt:    now.Add(-time.Minute),
		RepositoryOwner: "pingcap",
		RepositoryName:  "agentteam",
		CommitSHA:       "2222222222222222222222222222222222222222",
		Context:         "meshx/automation/.agentteam/build.yml",
		State:           automation.AutomationRepoStatusStateSuccess,
		Description:     "Automation Build succeeded",
		Sequence:        3,
		Status:          automation.AutomationRepoStatusOutboxStatusPending,
		MaxAttempts:     3,
		NextAttemptAt:   now.Add(-time.Second),
		CreatedAt:       now.Add(-time.Minute),
		UpdatedAt:       now.Add(-time.Minute),
	}
}

type recordingRepoStatusWriter struct {
	requests []AutomationCommitStatusRequest
	err      error
}

func (w *recordingRepoStatusWriter) CreateCommitStatus(_ context.Context, req AutomationCommitStatusRequest) error {
	w.requests = append(w.requests, req)
	return w.err
}
