package worker

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

	driversql "github.com/go-sql-driver/mysql"
)

func TestLeaseHeartbeatIntervalLeavesRetryBudget(t *testing.T) {
	t.Parallel()
	if got, want := leaseHeartbeatInterval(2*time.Minute), 20*time.Second; got != want {
		t.Fatalf("2m heartbeat interval = %s, want %s", got, want)
	}
	if got := leaseHeartbeatInterval(time.Second); got != minLeaseHeartbeatInterval {
		t.Fatalf("short lease heartbeat interval = %s, want floor %s", got, minLeaseHeartbeatInterval)
	}
	if got := leaseHeartbeatInterval(time.Hour); got != maxLeaseHeartbeatInterval {
		t.Fatalf("long lease heartbeat interval = %s, want ceiling %s", got, maxLeaseHeartbeatInterval)
	}
}

func TestLeaseRenewalTimeoutIsBounded(t *testing.T) {
	t.Parallel()
	if got, want := leaseRenewalTimeout(2*time.Minute), time.Minute; got != want {
		t.Fatalf("2m renewal timeout = %s, want %s", got, want)
	}
	if got := leaseRenewalTimeout(time.Second); got != minLeaseRenewalTimeout {
		t.Fatalf("short lease renewal timeout = %s, want floor %s", got, minLeaseRenewalTimeout)
	}
	if got := leaseRenewalTimeout(time.Hour); got != maxLeaseRenewalTimeout {
		t.Fatalf("long lease renewal timeout = %s, want ceiling %s", got, maxLeaseRenewalTimeout)
	}
}

func TestLeaseRenewalStopsBeforeExpiry(t *testing.T) {
	t.Parallel()
	now := time.Date(2026, time.September, 3, 10, 0, 0, 0, time.UTC)
	if got := leaseRenewalBudget(now, now.Add(leaseExpirySafety)); got != 0 {
		t.Fatalf("renewal budget at safety boundary = %s, want 0", got)
	}
	if got, want := leaseRenewalBudget(now, now.Add(2*leaseExpirySafety)), leaseExpirySafety; got != want {
		t.Fatalf("renewal budget = %s, want %s", got, want)
	}
}

func TestRetryableLeaseRenewalError(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name string
		err  error
		want bool
	}{
		{name: "deadline", err: context.DeadlineExceeded, want: true},
		{name: "opaque transaction deadline", err: errors.New("context deadline exceeded; sql: transaction has already been committed or rolled back"), want: true},
		{name: "lock wait", err: &driversql.MySQLError{Number: 1205}, want: true},
		{name: "deadlock", err: &driversql.MySQLError{Number: 1213}, want: true},
		{name: "wrapped lock wait", err: errors.Join(errors.New("renew"), &driversql.MySQLError{Number: 1205}), want: true},
		{name: "other mysql", err: &driversql.MySQLError{Number: 1062}, want: false},
		{name: "generic", err: errors.New("network"), want: false},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if got := retryableLeaseRenewalError(tc.err); got != tc.want {
				t.Fatalf("retryableLeaseRenewalError(%v) = %t, want %t", tc.err, got, tc.want)
			}
		})
	}
}
