package sqls

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

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/observability/o11ydiagnosis/pkg/storage"
)

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_ListTables(t *testing.T) {
	mockStore := &mockStorage{}
	query := NewSQLsQuery(mockStore).(*sqlsQuery)

	params := &ListTableParams{
		Env:        "prod",
		Provider:   "aws",
		Region:     "us-west-2",
		TenantID:   "test-tenant",
		ProjectID:  "test-project",
		ClusterID:  "test-cluster",
		SharedTier: false,
		NextGen:    false,
		Byoc:       false,
		Type:       "statements",
	}

	tables, err := query.ListTables(context.Background(), params)
	require.NoError(t, err)
	require.Nil(t, tables)
}

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

	params := &ListTableFieldsParams{
		Env:        "prod",
		Provider:   "aws",
		Region:     "us-west-2",
		TenantID:   "test-tenant",
		ProjectID:  "test-project",
		ClusterID:  "test-cluster",
		SharedTier: false,
		NextGen:    false,
		Byoc:       false,
		TableName:  "test_table",
	}

	fields, err := query.ListTableFields(context.Background(), params)
	require.NoError(t, err)
	require.Nil(t, fields)
}

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

	params := &ListTableDataParams{
		Env:        "prod",
		Provider:   "aws",
		Region:     "us-west-2",
		TenantID:   "test-tenant",
		ProjectID:  "test-project",
		ClusterID:  "test-cluster",
		SharedTier: false,
		NextGen:    false,
		Byoc:       false,
		TableName:  "test_table",
		StartTime:  "2024-01-01T00:00:00Z",
		EndTime:    "2024-01-02T00:00:00Z",
		Fields:     []string{"field1", "field2"},
		Limit:      10,
		Offset:     0,
	}

	data, err := query.ListTableData(context.Background(), params)
	require.NoError(t, err)
	require.Nil(t, data)
}

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

	params := &GetTableDataByIDParams{
		Env:        "prod",
		Provider:   "aws",
		Region:     "us-west-2",
		TenantID:   "test-tenant",
		ProjectID:  "test-project",
		ClusterID:  "test-cluster",
		SharedTier: false,
		NextGen:    false,
		Byoc:       false,
		TableName:  "test_table",
		ID:         123,
	}

	data, err := query.GetTableDataByID(context.Background(), params)
	require.NoError(t, err)
	require.Nil(t, data)
}

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

	tests := []struct {
		name       string
		provider   string
		env        string
		region     string
		sharedTier bool
		nextGen    bool
		expected   string
	}{
		{
			name:       "AWS provider",
			provider:   "aws",
			env:        "prod",
			region:     "us-west-2",
			sharedTier: false,
			nextGen:    false,
			expected:   "s3://o11y-prod-shared-us-west-2/deltalake/test-tenant/test-cluster/slowlogs",
		},
		{
			name:       "GCP provider",
			provider:   "gcp",
			env:        "prod",
			region:     "us-central1",
			sharedTier: false,
			nextGen:    false,
			expected:   "gs://o11y-prod-shared-us-central1/deltalake/test-tenant/test-cluster/slowlogs",
		},
		{
			name:       "Azure provider",
			provider:   "azure",
			env:        "prod",
			region:     "eastus",
			sharedTier: false,
			nextGen:    false,
			expected:   "az://o11y-prod-shared-eastus/deltalake/test-tenant/test-cluster/slowlogs",
		},
		{
			name:       "Alicloud provider",
			provider:   "alicloud",
			env:        "prod",
			region:     "cn-hangzhou",
			sharedTier: false,
			nextGen:    false,
			expected:   "s3://o11y-prod-shared-cn-hangzhou/deltalake/test-tenant/test-cluster/slowlogs",
		},
		{
			name:       "Local provider",
			provider:   "local",
			env:        "dev",
			region:     "local",
			sharedTier: false,
			nextGen:    false,
			expected:   "./o11y-dev-shared-local/deltalake/test-tenant/test-cluster/slowlogs",
		},
		{
			name:       "Unsupported provider",
			provider:   "unknown",
			env:        "prod",
			region:     "us-west-2",
			sharedTier: false,
			nextGen:    false,
			expected:   "",
		},
		{
			name:       "Shared tier",
			provider:   "aws",
			env:        "prod",
			region:     "us-west-2",
			sharedTier: true,
			nextGen:    false,
			expected:   "s3://o11y-prod-shared-us-west-2/deltalake/test-tenant/test-cluster/slowlogs",
		},
		{
			name:       "NextGen tier",
			provider:   "aws",
			env:        "prod",
			region:     "us-west-2",
			sharedTier: false,
			nextGen:    true,
			expected:   "s3://o11y-prod-shared-us-west-2/deltalake/test-tenant/test-cluster/slowlogs",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			url := query.getSQLsBaseURL("test-tenant", "test-cluster", tt.env, tt.provider, tt.region, tt.sharedTier, tt.nextGen)
			require.Equal(t, tt.expected, url)
		})
	}
}

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)
				}
			}
		})
	}
}

func stringPtr(s string) *string {
	return &s
}

// mockStorage is a mock implementation of storage.Storage for testing
type mockStorage struct{}

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) {
	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
}
