package topsqlv2

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

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/observability/o11ydiagnosis/pkg/storage"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

// mockDuckDBFetcher is a mock implementation of duckdb.QueryDuckDBFetcherInterface
type mockDuckDBFetcher struct {
	selectFunc func(ctx context.Context, dest interface{}, tableAlias, table, query string) error
	closeFunc  func(ctx context.Context) error
}

func (m *mockDuckDBFetcher) SelectSlowlogInDL(ctx context.Context, dest interface{}, fullQuery string) error {
	return nil
}

func (m *mockDuckDBFetcher) SelectSlowlogInDLWithoutFilterPushdown(ctx context.Context, dest interface{}, tableAlias, table, query string) error {
	if m.selectFunc != nil {
		return m.selectFunc(ctx, dest, tableAlias, table, query)
	}
	return nil
}

func (m *mockDuckDBFetcher) Close(ctx context.Context) error {
	if m.closeFunc != nil {
		return m.closeFunc(ctx)
	}
	return nil
}

// mockStorageForDuckDB is a mock implementation of storage.Storage
type mockStorageForDuckDB struct{}

func (m *mockStorageForDuckDB) Object(ctx context.Context, bucket, key string) (*storage.Object, error) {
	return nil, nil
}

func (m *mockStorageForDuckDB) Objects(ctx context.Context, bucket, prefix string) ([]storage.ObjectMeta, error) {
	return nil, nil
}

func (m *mockStorageForDuckDB) ObjectsCallback(ctx context.Context, bucket, prefix string, callback func(storage.ObjectMeta) bool) error {
	return nil
}

func (m *mockStorageForDuckDB) ObjectsCallbackWithStartKey(ctx context.Context, bucket, prefix, startKey string, callback func(storage.ObjectMeta) bool) error {
	return nil
}

func (m *mockStorageForDuckDB) ObjectExists(ctx context.Context, bucket, key string) (bool, error) {
	return false, nil
}

func (m *mockStorageForDuckDB) ObjectSize(ctx context.Context, bucket, key string) (int64, error) {
	return 0, nil
}

func (m *mockStorageForDuckDB) ObjectsDelimiter(ctx context.Context, bucket, prefix string) ([]string, error) {
	return nil, nil
}

func (m *mockStorageForDuckDB) SaveObject(ctx context.Context, bucket, key string, body io.Reader) error {
	return nil
}

func (m *mockStorageForDuckDB) DeleteObject(ctx context.Context, bucket, key string) error {
	return nil
}

func (m *mockStorageForDuckDB) DeleteObjects(ctx context.Context, bucket string, keys []string) error {
	return nil
}

func TestTableAlias(t *testing.T) {
	tests := []struct {
		name      string
		clusterID string
		table     string
		expected  string
	}{
		{
			name:      "Simple cluster and table",
			clusterID: "cluster1",
			table:     "table1",
			expected:  "table1_cluster1",
		},
		{
			name:      "Cluster ID with hyphens",
			clusterID: "cluster-1-2",
			table:     "table1",
			expected:  "table1_cluster_1_2",
		},
		{
			name:      "Table with hyphens",
			clusterID: "cluster1",
			table:     "table-1",
			expected:  "table_1_cluster1",
		},
		{
			name:      "Both with hyphens",
			clusterID: "cluster-1",
			table:     "table-1",
			expected:  "table_1_cluster_1",
		},
		{
			name:      "Empty cluster ID",
			clusterID: "",
			table:     "table1",
			expected:  "table1_",
		},
		{
			name:      "Empty table",
			clusterID: "cluster1",
			table:     "",
			expected:  "_cluster1",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tableAlias(tt.clusterID, tt.table)
			require.Equal(t, tt.expected, result)
		})
	}
}

func TestDuckDBAdapter_SelectSlowlogInDLWithoutFilterPushdown(t *testing.T) {
	mockDuckDB := &mockDuckDBFetcher{
		selectFunc: func(ctx context.Context, dest interface{}, tableAlias, table, query string) error {
			return nil
		},
	}
	mockStore := &mockStorageForDuckDB{}
	scheme := fake.NewClientBuilder().Build()

	adapter := &DuckDBAdapter{
		vendor:    "aws",
		region:    "us-west-2",
		duckdb:    mockDuckDB,
		store:     mockStore,
		kubeCli:   scheme,
		mataCache: nil,
	}

	var dest []interface{}
	err := adapter.SelectSlowlogInDLWithoutFilterPushdown(context.Background(), &dest, "alias", "table", "SELECT * FROM table")
	require.NoError(t, err)
}

func TestDuckDBAdapter_SelectSlowlogInDLWithoutFilterPushdown_Error(t *testing.T) {
	testErr := errors.New("test error")
	mockDuckDB := &mockDuckDBFetcher{
		selectFunc: func(ctx context.Context, dest interface{}, tableAlias, table, query string) error {
			return testErr
		},
	}
	mockStore := &mockStorageForDuckDB{}
	scheme := fake.NewClientBuilder().Build()

	adapter := &DuckDBAdapter{
		vendor:    "aws",
		region:    "us-west-2",
		duckdb:    mockDuckDB,
		store:     mockStore,
		kubeCli:   scheme,
		mataCache: nil,
	}

	var dest []interface{}
	err := adapter.SelectSlowlogInDLWithoutFilterPushdown(context.Background(), &dest, "alias", "table", "SELECT * FROM table")
	require.Error(t, err)
	require.Equal(t, testErr, err)
}

func TestDuckDBAdapter_Warmup(t *testing.T) {
	mockDuckDB := &mockDuckDBFetcher{}
	mockStore := &mockStorageForDuckDB{}
	scheme := fake.NewClientBuilder().Build()

	adapter := &DuckDBAdapter{
		vendor:    "aws",
		region:    "us-west-2",
		duckdb:    mockDuckDB,
		store:     mockStore,
		kubeCli:   scheme,
		mataCache: nil,
	}

	err := adapter.warmup(context.Background(), mockDuckDB)
	require.NoError(t, err)
}

func TestDuckDBAdapter_RenewAndWarmupDuckDB(t *testing.T) {
	// Test with local vendor (no cloud credentials needed)
	mockStore := &mockStorageForDuckDB{}
	scheme := fake.NewClientBuilder().Build()

	// Create adapter with local vendor
	adapter := NewQueryDuckDBAdapter("local", "local", mockStore, scheme)
	require.NotNil(t, adapter)

	// Test RenewAndWarmupDuckDB
	ctx := context.Background()
	err := adapter.RenewAndWarmupDuckDB(ctx)
	require.NoError(t, err)

	// Verify adapter still works after renewal
	require.NotNil(t, adapter.duckdb)

	// Clean up
	err = adapter.duckdb.Close(ctx)
	require.NoError(t, err)
}

func TestDuckDBAdapter_StartWarmupRefreshLoop(t *testing.T) {
	mockDuckDB := &mockDuckDBFetcher{
		selectFunc: func(ctx context.Context, dest interface{}, tableAlias, table, query string) error {
			return nil
		},
	}
	mockStore := &mockStorageForDuckDB{}
	scheme := fake.NewClientBuilder().Build()

	adapter := &DuckDBAdapter{
		vendor:    "aws",
		region:    "us-west-2",
		duckdb:    mockDuckDB,
		store:     mockStore,
		kubeCli:   scheme,
		mataCache: nil,
	}

	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()

	// Start the refresh loop in a goroutine
	go adapter.startWarmupRefreshLoop(ctx)

	// Wait a bit to let it start
	time.Sleep(50 * time.Millisecond)

	// Cancel context to stop the loop
	cancel()

	// Give it a moment to stop
	time.Sleep(50 * time.Millisecond)
}

// TestNewQueryDuckDBAdapter tests the NewQueryDuckDBAdapter function with local vendor
func TestNewQueryDuckDBAdapter(t *testing.T) {
	// Test with local vendor (no cloud credentials needed)
	mockStore := &mockStorageForDuckDB{}
	scheme := fake.NewClientBuilder().Build()

	adapter := NewQueryDuckDBAdapter("local", "local", mockStore, scheme)

	require.NotNil(t, adapter)
	require.Equal(t, "local", adapter.vendor)
	require.Equal(t, "local", adapter.region)
	require.NotNil(t, adapter.duckdb)
	require.Equal(t, mockStore, adapter.store)
	require.Equal(t, scheme, adapter.kubeCli)
	require.NotNil(t, adapter.mataCache)

	// Test that we can close the adapter
	ctx := context.Background()
	err := adapter.duckdb.Close(ctx)
	require.NoError(t, err)
}
