package cassandra

import (
	"errors"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestWithLockTTL(t *testing.T) {
	tests := []struct {
		name       string
		duration   time.Duration
		wantTTL    int
		description string
	}{
		{
			name:        "30 seconds TTL",
			duration:    30 * time.Second,
			wantTTL:     30,
			description: "should set TTL to 30 seconds",
		},
		{
			name:        "1 minute TTL",
			duration:    1 * time.Minute,
			wantTTL:     60,
			description: "should set TTL to 60 seconds",
		},
		{
			name:        "5 minutes TTL",
			duration:    5 * time.Minute,
			wantTTL:     300,
			description: "should set TTL to 300 seconds",
		},
		{
			name:        "1 hour TTL",
			duration:    1 * time.Hour,
			wantTTL:     3600,
			description: "should set TTL to 3600 seconds",
		},
		{
			name:        "zero duration",
			duration:    0,
			wantTTL:     0,
			description: "should set TTL to 0",
		},
		{
			name:        "negative duration",
			duration:    -10 * time.Second,
			wantTTL:     -10,
			description: "should set TTL to negative value",
		},
		{
			name:        "fractional seconds",
			duration:    1500 * time.Millisecond,
			wantTTL:     1,
			description: "should round down fractional seconds",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			opt := WithLockTTL(tt.duration)
			options := &lockOptions{}
			opt(options)
			assert.Equal(t, tt.wantTTL, options.ttlSeconds, tt.description)
		})
	}
}

func TestWithNoLockExpire(t *testing.T) {
	t.Run("should set TTL to 0", func(t *testing.T) {
		opt := WithNoLockExpire()
		options := &lockOptions{ttlSeconds: 30} // 先設置一個值
		opt(options)
		assert.Equal(t, 0, options.ttlSeconds)
	})

	t.Run("should override existing TTL", func(t *testing.T) {
		opt := WithNoLockExpire()
		options := &lockOptions{ttlSeconds: 100}
		opt(options)
		assert.Equal(t, 0, options.ttlSeconds)
	})
}

func TestLockOptions_Combination(t *testing.T) {
	tests := []struct {
		name     string
		opts     []LockOption
		wantTTL  int
	}{
		{
			name:    "WithLockTTL then WithNoLockExpire",
			opts:    []LockOption{WithLockTTL(60 * time.Second), WithNoLockExpire()},
			wantTTL: 0, // WithNoLockExpire should override
		},
		{
			name:    "WithNoLockExpire then WithLockTTL",
			opts:    []LockOption{WithNoLockExpire(), WithLockTTL(60 * time.Second)},
			wantTTL: 60, // WithLockTTL should override
		},
		{
			name:    "multiple WithLockTTL calls",
			opts:    []LockOption{WithLockTTL(30 * time.Second), WithLockTTL(60 * time.Second)},
			wantTTL: 60, // Last one wins
		},
		{
			name:    "multiple WithNoLockExpire calls",
			opts:    []LockOption{WithNoLockExpire(), WithNoLockExpire()},
			wantTTL: 0,
		},
		{
			name:    "empty options should use default",
			opts:    []LockOption{},
			wantTTL: defaultLockTTLSec,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			options := &lockOptions{ttlSeconds: defaultLockTTLSec}
			for _, opt := range tt.opts {
				opt(options)
			}
			assert.Equal(t, tt.wantTTL, options.ttlSeconds)
		})
	}
}

func TestIsLockFailed(t *testing.T) {
	tests := []struct {
		name string
		err  error
		want bool
	}{
		{
			name: "Error with CONFLICT code and correct message",
			err:  NewError(ErrCodeConflict, "acquire lock failed"),
			want: true,
		},
		{
			name: "Error with CONFLICT code and correct message with table",
			err:  NewError(ErrCodeConflict, "acquire lock failed").WithTable("locks"),
			want: true,
		},
		{
			name: "Error with CONFLICT code but wrong message",
			err:  NewError(ErrCodeConflict, "different message"),
			want: false,
		},
		{
			name: "Error with NOT_FOUND code and correct message",
			err:  NewError(ErrCodeNotFound, "acquire lock failed"),
			want: false,
		},
		{
			name: "Error with INVALID_INPUT code",
			err:  ErrInvalidInput,
			want: false,
		},
		{
			name: "wrapped Error with CONFLICT code and correct message",
			err: NewError(ErrCodeConflict, "acquire lock failed").
				WithError(errors.New("underlying error")),
			want: true,
		},
		{
			name: "standard error",
			err:  errors.New("standard error"),
			want: false,
		},
		{
			name: "nil error",
			err:  nil,
			want: false,
		},
		{
			name: "Error with CONFLICT code but empty message",
			err:  NewError(ErrCodeConflict, ""),
			want: false,
		},
		{
			name: "Error with CONFLICT code and similar but different message",
			err:  NewError(ErrCodeConflict, "acquire lock failed!"),
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := IsLockFailed(tt.err)
			assert.Equal(t, tt.want, result)
		})
	}
}

func TestLockConstants(t *testing.T) {
	tests := []struct {
		name     string
		constant interface{}
		expected interface{}
	}{
		{
			name:     "defaultLockTTLSec should be 30",
			constant: defaultLockTTLSec,
			expected: 30,
		},
		{
			name:     "defaultLockRetry should be 3",
			constant: defaultLockRetry,
			expected: 3,
		},
		{
			name:     "lockBaseDelay should be 100ms",
			constant: lockBaseDelay,
			expected: 100 * time.Millisecond,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, tt.constant)
		})
	}
}

func TestLockOptions_DefaultValues(t *testing.T) {
	t.Run("default lockOptions should have default TTL", func(t *testing.T) {
		options := &lockOptions{ttlSeconds: defaultLockTTLSec}
		assert.Equal(t, defaultLockTTLSec, options.ttlSeconds)
	})

	t.Run("lockOptions with zero TTL", func(t *testing.T) {
		options := &lockOptions{ttlSeconds: 0}
		assert.Equal(t, 0, options.ttlSeconds)
	})

	t.Run("lockOptions with negative TTL", func(t *testing.T) {
		options := &lockOptions{ttlSeconds: -1}
		assert.Equal(t, -1, options.ttlSeconds)
	})
}

func TestTryLock_ErrorScenarios(t *testing.T) {
	tests := []struct {
		name        string
		description string
		// 注意：實際的 TryLock 測試需要 mock session 或實際的資料庫連接
		// 這裡只是定義測試結構
	}{
		{
			name:        "successful lock acquisition",
			description: "should return nil when lock is successfully acquired",
		},
		{
			name:        "lock already exists",
			description: "should return CONFLICT error when lock already exists",
		},
		{
			name:        "database error",
			description: "should return INVALID_INPUT error with underlying error when database operation fails",
		},
		{
			name:        "context cancellation",
			description: "should respect context cancellation",
		},
		{
			name:        "with custom TTL",
			description: "should use custom TTL when provided",
		},
		{
			name:        "with no expire",
			description: "should not set TTL when WithNoLockExpire is used",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// 注意：這需要 mock session 或實際的資料庫連接
			// 在實際測試中，需要使用 mock 或 testcontainers
			_ = tt
		})
	}
}

func TestUnLock_ErrorScenarios(t *testing.T) {
	tests := []struct {
		name        string
		description string
		// 注意：實際的 UnLock 測試需要 mock session 或實際的資料庫連接
		// 這裡只是定義測試結構
	}{
		{
			name:        "successful unlock",
			description: "should return nil when lock is successfully released",
		},
		{
			name:        "lock not found",
			description: "should retry when lock is not found",
		},
		{
			name:        "database error",
			description: "should retry on database error",
		},
		{
			name:        "max retries exceeded",
			description: "should return error after max retries",
		},
		{
			name:        "context cancellation",
			description: "should respect context cancellation",
		},
		{
			name:        "exponential backoff",
			description: "should use exponential backoff between retries",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// 注意：這需要 mock session 或實際的資料庫連接
			// 在實際測試中，需要使用 mock 或 testcontainers
			_ = tt
		})
	}
}

func TestLockOption_Type(t *testing.T) {
	t.Run("WithLockTTL should return LockOption", func(t *testing.T) {
		opt := WithLockTTL(30 * time.Second)
		assert.NotNil(t, opt)
		// 驗證它是一個函數
		var lockOpt LockOption = opt
		assert.NotNil(t, lockOpt)
	})

	t.Run("WithNoLockExpire should return LockOption", func(t *testing.T) {
		opt := WithNoLockExpire()
		assert.NotNil(t, opt)
		// 驗證它是一個函數
		var lockOpt LockOption = opt
		assert.NotNil(t, lockOpt)
	})
}

func TestLockOptions_ApplyOrder(t *testing.T) {
	t.Run("last option should win", func(t *testing.T) {
		options := &lockOptions{ttlSeconds: defaultLockTTLSec}
		
		WithLockTTL(60 * time.Second)(options)
		assert.Equal(t, 60, options.ttlSeconds)
		
		WithNoLockExpire()(options)
		assert.Equal(t, 0, options.ttlSeconds)
		
		WithLockTTL(120 * time.Second)(options)
		assert.Equal(t, 120, options.ttlSeconds)
	})
}

func TestIsLockFailed_EdgeCases(t *testing.T) {
	tests := []struct {
		name string
		err  error
		want bool
	}{
		{
			name: "Error with CONFLICT code, correct message, and underlying error",
			err: NewError(ErrCodeConflict, "acquire lock failed").
				WithTable("locks").
				WithError(errors.New("database error")),
			want: true,
		},
		{
			name: "Error with CONFLICT code but message with extra spaces",
			err:  NewError(ErrCodeConflict, " acquire lock failed "),
			want: false,
		},
		{
			name: "Error with CONFLICT code but message with different case",
			err:  NewError(ErrCodeConflict, "Acquire Lock Failed"),
			want: false,
		},
		{
			name: "chained errors with CONFLICT",
			err: func() error {
				err1 := NewError(ErrCodeConflict, "acquire lock failed")
				err2 := errors.New("wrapped")
				return errors.Join(err1, err2)
			}(),
			want: true, // errors.Join preserves Error type and errors.As can find it
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := IsLockFailed(tt.err)
			assert.Equal(t, tt.want, result)
		})
	}
}

func TestLockOptions_ZeroValue(t *testing.T) {
	t.Run("zero value lockOptions", func(t *testing.T) {
		var options lockOptions
		assert.Equal(t, 0, options.ttlSeconds)
	})

	t.Run("apply option to zero value", func(t *testing.T) {
		var options lockOptions
		WithLockTTL(30 * time.Second)(&options)
		assert.Equal(t, 30, options.ttlSeconds)
	})
}

func TestLockRetryDelay(t *testing.T) {
	t.Run("verify exponential backoff calculation", func(t *testing.T) {
		// 驗證重試延遲的計算邏輯
		// 100ms → 200ms → 400ms
		expectedDelays := []time.Duration{
			lockBaseDelay * time.Duration(1<<0), // 100ms * 1 = 100ms
			lockBaseDelay * time.Duration(1<<1), // 100ms * 2 = 200ms
			lockBaseDelay * time.Duration(1<<2), // 100ms * 4 = 400ms
		}

		assert.Equal(t, 100*time.Millisecond, expectedDelays[0])
		assert.Equal(t, 200*time.Millisecond, expectedDelays[1])
		assert.Equal(t, 400*time.Millisecond, expectedDelays[2])
	})
}

func TestLockOption_InterfaceCompliance(t *testing.T) {
	t.Run("LockOption should be a function type", func(t *testing.T) {
		// 驗證 LockOption 是一個函數類型
		var fn func(*lockOptions) = WithLockTTL(30 * time.Second)
		assert.NotNil(t, fn)
	})

	t.Run("LockOption can be assigned from WithLockTTL", func(t *testing.T) {
		var opt LockOption = WithLockTTL(30 * time.Second)
		assert.NotNil(t, opt)
	})

	t.Run("LockOption can be assigned from WithNoLockExpire", func(t *testing.T) {
		var opt LockOption = WithNoLockExpire()
		assert.NotNil(t, opt)
	})
}

func TestLockOptions_RealWorldScenarios(t *testing.T) {
	tests := []struct {
		name     string
		scenario func(*lockOptions)
		wantTTL  int
	}{
		{
			name: "short-lived lock (5 seconds)",
			scenario: func(o *lockOptions) {
				WithLockTTL(5 * time.Second)(o)
			},
			wantTTL: 5,
		},
		{
			name: "medium-lived lock (5 minutes)",
			scenario: func(o *lockOptions) {
				WithLockTTL(5 * time.Minute)(o)
			},
			wantTTL: 300,
		},
		{
			name: "long-lived lock (1 hour)",
			scenario: func(o *lockOptions) {
				WithLockTTL(1 * time.Hour)(o)
			},
			wantTTL: 3600,
		},
		{
			name: "permanent lock",
			scenario: func(o *lockOptions) {
				WithNoLockExpire()(o)
			},
			wantTTL: 0,
		},
		{
			name: "default lock",
			scenario: func(o *lockOptions) {
				// 不應用任何選項，使用預設值
			},
			wantTTL: defaultLockTTLSec,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			options := &lockOptions{ttlSeconds: defaultLockTTLSec}
			tt.scenario(options)
			assert.Equal(t, tt.wantTTL, options.ttlSeconds)
		})
	}
}

