package store

import (
	"testing"

	"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
	"github.com/stretchr/testify/require"
)

func TestJsonEquals(t *testing.T) {
	tests := []struct {
		name     string
		dialect  migrator.Dialect
		column   string
		key      string
		value    string
		wantSQL  string
		wantArgs []any
	}{
		{
			name:     "MySQL",
			dialect:  migrator.NewMysqlDialect(),
			column:   "labels",
			key:      "team",
			value:    "alerting",
			wantSQL:  `JSON_UNQUOTE(JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$."', ?, '"'))) = ?`,
			wantArgs: []any{"team", "alerting"},
		},
		{
			name:     "PostgreSQL",
			dialect:  migrator.NewPostgresDialect(),
			column:   "labels",
			key:      "team",
			value:    "alerting",
			wantSQL:  "jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) = ?",
			wantArgs: []any{"team", "alerting"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sql, args := jsonEquals(tt.dialect, tt.column, tt.key, tt.value)
			require.Equal(t, tt.wantSQL, sql)
			require.Equal(t, tt.wantArgs, args)
		})
	}
}

func TestJsonNotEquals(t *testing.T) {
	tests := []struct {
		name     string
		dialect  migrator.Dialect
		column   string
		key      string
		value    string
		wantSQL  string
		wantArgs []any
	}{
		{
			name:     "MySQL",
			dialect:  migrator.NewMysqlDialect(),
			column:   "labels",
			key:      "team",
			value:    "alerting",
			wantSQL:  `(JSON_UNQUOTE(JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$."', ?, '"'))) IS NULL OR JSON_UNQUOTE(JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$."', ?, '"'))) != ?)`,
			wantArgs: []any{"team", "team", "alerting"},
		},
		{
			name:     "PostgreSQL",
			dialect:  migrator.NewPostgresDialect(),
			column:   "labels",
			key:      "team",
			value:    "alerting",
			wantSQL:  "(jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) IS NULL OR jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) != ?)",
			wantArgs: []any{"team", "team", "alerting"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sql, args := jsonNotEquals(tt.dialect, tt.column, tt.key, tt.value)
			require.Equal(t, tt.wantSQL, sql)
			require.Equal(t, tt.wantArgs, args)
		})
	}
}

func TestJsonKeyMissing(t *testing.T) {
	tests := []struct {
		name     string
		dialect  migrator.Dialect
		column   string
		key      string
		wantSQL  string
		wantArgs []any
	}{
		{
			name:     "MySQL",
			dialect:  migrator.NewMysqlDialect(),
			column:   "labels",
			key:      "team",
			wantSQL:  `JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$."', ?, '"')) IS NULL`,
			wantArgs: []any{"team"},
		},
		{
			name:     "PostgreSQL",
			dialect:  migrator.NewPostgresDialect(),
			column:   "labels",
			key:      "team",
			wantSQL:  "jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) IS NULL",
			wantArgs: []any{"team"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sql, args, err := jsonKeyMissing(tt.dialect, tt.column, tt.key)
			require.NoError(t, err)
			require.Equal(t, tt.wantSQL, sql)
			require.Equal(t, tt.wantArgs, args)
		})
	}
}

func TestJsonKeyExists(t *testing.T) {
	tests := []struct {
		name     string
		dialect  migrator.Dialect
		column   string
		key      string
		wantSQL  string
		wantArgs []any
	}{
		{
			name:     "MySQL",
			dialect:  migrator.NewMysqlDialect(),
			column:   "labels",
			key:      "__grafana_origin",
			wantSQL:  `JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$."', ?, '"')) IS NOT NULL`,
			wantArgs: []any{"__grafana_origin"},
		},
		{
			name:     "PostgreSQL",
			dialect:  migrator.NewPostgresDialect(),
			column:   "labels",
			key:      "__grafana_origin",
			wantSQL:  "jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) IS NOT NULL",
			wantArgs: []any{"__grafana_origin"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sql, args, err := jsonKeyExists(tt.dialect, tt.column, tt.key)
			require.NoError(t, err)
			require.Equal(t, tt.wantSQL, sql)
			require.Equal(t, tt.wantArgs, args)
		})
	}
}

func TestGlobEquals(t *testing.T) {
	sql, args, err := globEquals("labels", "team", "alerting")
	require.NoError(t, err)
	require.Equal(t, "labels GLOB ?", sql)
	require.Equal(t, []any{`*"team":"alerting"*`}, args)
}

func TestGlobNotEquals(t *testing.T) {
	sql, args, err := globNotEquals("labels", "team", "alerting")
	require.NoError(t, err)
	require.Equal(t, "labels NOT GLOB ?", sql)
	require.Equal(t, []any{`*"team":"alerting"*`}, args)
}

func TestGlobKeyMissing(t *testing.T) {
	sql, args, err := globKeyMissing("labels", "team")
	require.NoError(t, err)
	require.Equal(t, "labels NOT GLOB ?", sql)
	require.Equal(t, []any{`*"team":*`}, args)
}

func TestBuildGlobPattern(t *testing.T) {
	tests := []struct {
		name     string
		key      string
		value    string
		expected string
	}{
		{
			name:     "simple key-value",
			key:      "team",
			value:    "alerting",
			expected: `"team":"alerting"`,
		},
		{
			name:     "empty value",
			key:      "empty",
			value:    "",
			expected: `"empty":""`,
		},
		{
			name:     "special GLOB chars are escaped",
			key:      "key",
			value:    "*[?]",
			expected: `"key":"[*][[][?]]"`,
		},
		{
			name:     "special chars are escaped",
			key:      "key",
			value:    "line1\nline2\\end\"quote",
			expected: `"key":"line1\nline2\\end\"quote"`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pattern, err := buildGlobPattern(tt.key, tt.value)
			require.NoError(t, err)
			require.Equal(t, tt.expected, pattern)
		})
	}
}
