package connector_ingress_receipts

import (
	"testing"
	"time"

	"github.com/pingcap-inc/nexuscrm-connectors/testing/connectorfixture"
)

// Exercise the real SQL/driver boundary: mocked time.Time rows hide TiDB's
// result-type inference when DATETIME columns are combined with parameters.
func TestNextClaimableAtSchedulingBoundariesTiDB(t *testing.T) {
	db := connectorfixture.OpenTiDB(t)
	connectorfixture.CreateSystemTables(t, db, "_System_ConnectorIngressReceipt")
	now := time.Date(2026, 9, 2, 12, 0, 0, 123456000, time.UTC)
	future := now.Truncate(time.Second).Add(7 * time.Second)
	past := now.Truncate(time.Second).Add(-2 * time.Second)
	service := NewService(db, receiptTestCipher(t), nil, func() time.Time { return now })
	assertNext := func(t *testing.T, service *Service, want time.Time) {
		t.Helper()
		got, found, err := service.NextClaimableAt(t.Context())
		if err != nil || found != !want.IsZero() || !got.Equal(want) {
			t.Fatalf("NextClaimableAt = (%v, %v, %v), want (%v, %v, nil)", got, found, err, want, !want.IsZero())
		}
	}
	assertNext(t, service, time.Time{})
	for _, tc := range []struct {
		name, status string
		next, lease  any
		want         time.Time
	}{
		{"pending uses clock microseconds", StatusPending, nil, nil, now},
		{"retry deadline", StatusRetry, future, nil, future},
		{"overdue retry", StatusRetry, past, nil, past},
		{"pending configuration", StatusPendingConfig, future, nil, future},
		{"processing lease", StatusProcessing, nil, future, future},
		{"expired lease", StatusProcessing, nil, past, past},
		{"processing without lease", StatusProcessing, nil, nil, time.Time{}},
		{"enqueued is terminal", StatusEnqueued, past, past, time.Time{}},
		{"dead is terminal", StatusDead, past, past, time.Time{}},
		{"configuration terminal", StatusConfigTerminal, past, past, time.Time{}},
	} {
		t.Run(tc.name, func(t *testing.T) {
			if _, err := db.ExecContext(t.Context(), "DELETE FROM _System_ConnectorIngressReceipt"); err != nil {
				t.Fatal(err)
			}
			if _, _, err := service.CreateVerifiedReceipt(t.Context(), receiptTestInput()); err != nil {
				t.Fatal(err)
			}
			if _, err := db.ExecContext(t.Context(), "UPDATE _System_ConnectorIngressReceipt SET Status=?, NextAttemptAt=?, LeaseExpiresAt=?", tc.status, tc.next, tc.lease); err != nil {
				t.Fatal(err)
			}
			assertNext(t, service, tc.want)
		})
	}
	t.Run("earliest deadline respects selected installations", func(t *testing.T) {
		if _, err := db.ExecContext(t.Context(), "DELETE FROM _System_ConnectorIngressReceipt"); err != nil {
			t.Fatal(err)
		}
		for _, input := range []struct{ id, pack string }{{"one", "selected"}, {"two", "selected"}, {"outside", "excluded"}} {
			receipt := receiptTestInput()
			receipt.ID, receipt.DeliveryIdentity, receipt.InstalledPackID = input.id, input.id, input.pack
			if _, _, err := service.CreateVerifiedReceipt(t.Context(), receipt); err != nil {
				t.Fatal(err)
			}
		}
		if _, err := db.ExecContext(t.Context(), "UPDATE _System_ConnectorIngressReceipt SET Status=?, NextAttemptAt=? WHERE Id='one'", StatusRetry, future); err != nil {
			t.Fatal(err)
		}
		later := future.Add(time.Second)
		if _, err := db.ExecContext(t.Context(), "UPDATE _System_ConnectorIngressReceipt SET Status=?, LeaseExpiresAt=? WHERE Id='two'", StatusProcessing, later); err != nil {
			t.Fatal(err)
		}
		scoped, err := NewScopedClaimService(db, receiptTestCipher(t), []string{"selected"})
		if err != nil {
			t.Fatal(err)
		}
		scoped.now = service.now
		assertNext(t, scoped, future)
		assertNext(t, service, now)
		empty, err := NewScopedClaimService(db, receiptTestCipher(t), nil)
		if err != nil {
			t.Fatal(err)
		}
		assertNext(t, empty, time.Time{})
	})
}
