package topsqlv2

import (
	"context"
	"io"
	"os"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/observability/o11ydiagnosis/pkg/storage"
)

// mockStorage is a mock implementation of storage.Storage for testing
type mockStorage struct {
	objectsDelimiterFunc func(ctx context.Context, bucket, prefix string) ([]string, error)
}

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

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

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

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

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

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

func (m *mockStorage) ObjectsDelimiter(ctx context.Context, bucket, prefix string) ([]string, error) {
	if m.objectsDelimiterFunc != nil {
		return m.objectsDelimiterFunc(ctx, bucket, prefix)
	}
	return nil, nil
}

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

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

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

func TestNewSQLsQuery(t *testing.T) {
	mockStore := &mockStorage{}
	query := NewSQLsQuery(mockStore)
	require.NotNil(t, query)

	sq, ok := query.(*sqlsQuery)
	require.True(t, ok)
	require.Equal(t, mockStore, sq.store)
}

func TestSQLsQuery_SelectSlowlogInDLWithoutFilterPushdown(t *testing.T) {
	mockStore := &mockStorage{}
	query := NewSQLsQuery(mockStore).(*sqlsQuery)

	var dest []interface{}
	err := query.SelectSlowlogInDLWithoutFilterPushdown(context.Background(), &dest, "alias", "table", "SELECT * FROM table")
	require.Error(t, err)
	require.Contains(t, err.Error(), "not implemented")
}

func TestBucketName(t *testing.T) {
	// Save original env value
	originalBucketName := os.Getenv("BUCKET_NAME")
	defer func() {
		if originalBucketName != "" {
			os.Setenv("BUCKET_NAME", originalBucketName)
		} else {
			os.Unsetenv("BUCKET_NAME")
		}
	}()

	tests := []struct {
		name       string
		env        string
		region     string
		sharedTier bool
		nextGen    bool
		envVar     string
		expected   string
	}{
		{
			name:       "With BUCKET_NAME env var",
			env:        "prod",
			region:     "us-west-2",
			sharedTier: false,
			nextGen:    false,
			envVar:     "custom-bucket",
			expected:   "custom-bucket",
		},
		{
			name:       "Prod env",
			env:        "prod",
			region:     "us-west-2",
			sharedTier: false,
			nextGen:    false,
			envVar:     "",
			expected:   "o11y-prod-shared-us-west-2",
		},
		{
			name:       "Dev env",
			env:        "dev",
			region:     "us-west-2",
			sharedTier: false,
			nextGen:    false,
			envVar:     "",
			expected:   "o11y-dev-shared-us-west-2",
		},
		{
			name:       "Test env",
			env:        "test",
			region:     "us-west-2",
			sharedTier: false,
			nextGen:    false,
			envVar:     "",
			expected:   "o11y-test-shared-us-west-2",
		},
		{
			name:       "Shared tier",
			env:        "prod",
			region:     "us-west-2",
			sharedTier: true,
			nextGen:    false,
			envVar:     "",
			expected:   "o11y-prod-shared-us-west-2",
		},
		{
			name:       "NextGen tier",
			env:        "prod",
			region:     "us-west-2",
			sharedTier: false,
			nextGen:    true,
			envVar:     "",
			expected:   "o11y-prod-shared-us-west-2",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.envVar != "" {
				os.Setenv("BUCKET_NAME", tt.envVar)
			} else {
				os.Unsetenv("BUCKET_NAME")
			}

			result := storage.BucketName(tt.env, tt.region, tt.sharedTier, tt.nextGen)
			require.Equal(t, tt.expected, result)
		})
	}
}

func TestClusterHeader_Validate(t *testing.T) {
	tests := []struct {
		name    string
		header  ClusterHeader
		wantErr bool
		errMsg  string
	}{
		{
			name: "Valid header",
			header: ClusterHeader{
				Provider:   stringPtr("aws"),
				Region:     stringPtr("us-west-2"),
				TenantID:   stringPtr("tenant-1"),
				ProjectID:  stringPtr("project-1"),
				ClusterID:  stringPtr("cluster-1"),
				DeployType: stringPtr("dedicated"),
				Env:        stringPtr("prod"),
				BizType:    stringPtr("other"),
			},
			wantErr: false,
		},
		{
			name: "Missing provider",
			header: ClusterHeader{
				Region:     stringPtr("us-west-2"),
				TenantID:   stringPtr("tenant-1"),
				ProjectID:  stringPtr("project-1"),
				ClusterID:  stringPtr("cluster-1"),
				DeployType: stringPtr("dedicated"),
			},
			wantErr: true,
			errMsg:  "provider not found",
		},
		{
			name: "Missing region",
			header: ClusterHeader{
				Provider:   stringPtr("aws"),
				TenantID:   stringPtr("tenant-1"),
				ProjectID:  stringPtr("project-1"),
				ClusterID:  stringPtr("cluster-1"),
				DeployType: stringPtr("dedicated"),
			},
			wantErr: true,
			errMsg:  "region not found",
		},
		{
			name: "Missing tenant ID",
			header: ClusterHeader{
				Provider:   stringPtr("aws"),
				Region:     stringPtr("us-west-2"),
				ProjectID:  stringPtr("project-1"),
				ClusterID:  stringPtr("cluster-1"),
				DeployType: stringPtr("dedicated"),
			},
			wantErr: true,
			errMsg:  "org id not found",
		},
		{
			name: "Missing cluster ID",
			header: ClusterHeader{
				Provider:   stringPtr("aws"),
				Region:     stringPtr("us-west-2"),
				TenantID:   stringPtr("tenant-1"),
				ProjectID:  stringPtr("project-1"),
				DeployType: stringPtr("dedicated"),
			},
			wantErr: true,
			errMsg:  "cluster id not found",
		},
		{
			name: "Empty cluster ID",
			header: ClusterHeader{
				Provider:   stringPtr("aws"),
				Region:     stringPtr("us-west-2"),
				TenantID:   stringPtr("tenant-1"),
				ProjectID:  stringPtr("project-1"),
				ClusterID:  stringPtr(""),
				DeployType: stringPtr("dedicated"),
			},
			wantErr: true,
			errMsg:  "cluster id not found",
		},
		{
			name: "Missing deploy type",
			header: ClusterHeader{
				Provider:  stringPtr("aws"),
				Region:    stringPtr("us-west-2"),
				TenantID:  stringPtr("tenant-1"),
				ProjectID: stringPtr("project-1"),
				ClusterID: stringPtr("cluster-1"),
			},
			wantErr: true,
			errMsg:  "deploy type not found",
		},
		{
			name: "Missing project ID - should set empty string",
			header: ClusterHeader{
				Provider:   stringPtr("aws"),
				Region:     stringPtr("us-west-2"),
				TenantID:   stringPtr("tenant-1"),
				ClusterID:  stringPtr("cluster-1"),
				DeployType: stringPtr("dedicated"),
			},
			wantErr: false,
		},
		{
			name: "Missing env - should set prod",
			header: ClusterHeader{
				Provider:   stringPtr("aws"),
				Region:     stringPtr("us-west-2"),
				TenantID:   stringPtr("tenant-1"),
				ProjectID:  stringPtr("project-1"),
				ClusterID:  stringPtr("cluster-1"),
				DeployType: stringPtr("dedicated"),
			},
			wantErr: false,
		},
		{
			name: "Missing biz type - should set unknown",
			header: ClusterHeader{
				Provider:   stringPtr("aws"),
				Region:     stringPtr("us-west-2"),
				TenantID:   stringPtr("tenant-1"),
				ProjectID:  stringPtr("project-1"),
				ClusterID:  stringPtr("cluster-1"),
				DeployType: stringPtr("dedicated"),
				Env:        stringPtr("prod"),
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.header.Validate()
			if tt.wantErr {
				require.Error(t, err)
				require.Contains(t, err.Error(), tt.errMsg)
			} else {
				require.NoError(t, err)
				// Check default values
				if tt.header.ProjectID == nil {
					require.NotNil(t, tt.header.ProjectID)
					require.Equal(t, "", *tt.header.ProjectID)
				}
				if tt.header.Env == nil {
					require.NotNil(t, tt.header.Env)
					require.Equal(t, "prod", *tt.header.Env)
				}
				if tt.header.BizType == nil {
					require.NotNil(t, tt.header.BizType)
					require.Equal(t, "unknown", *tt.header.BizType)
				}
			}
		})
	}
}
