package resolvespec

import (
	"strings"
	"testing"

	"github.com/bitechdev/ResolveSpec/pkg/common"
)

func TestGetCursorFilter_Forward(t *testing.T) {
	options := common.RequestOptions{
		Sort: []common.SortOption{
			{Column: "created_at", Direction: "DESC"},
			{Column: "id", Direction: "ASC"},
		},
		CursorForward: "123",
	}

	tableName := "posts"
	pkName := "id"
	modelColumns := []string{"id", "title", "created_at", "user_id"}

	filter, err := GetCursorFilter(tableName, pkName, modelColumns, options, nil)
	if err != nil {
		t.Fatalf("GetCursorFilter failed: %v", err)
	}

	if filter == "" {
		t.Fatal("Expected non-empty cursor filter")
	}

	// Verify filter contains EXISTS subquery
	if !strings.Contains(filter, "EXISTS") {
		t.Errorf("Filter should contain EXISTS subquery, got: %s", filter)
	}

	// Verify filter references the cursor ID
	if !strings.Contains(filter, "123") {
		t.Errorf("Filter should reference cursor ID 123, got: %s", filter)
	}

	// Verify filter contains the table name
	if !strings.Contains(filter, tableName) {
		t.Errorf("Filter should reference table name %s, got: %s", tableName, filter)
	}

	// Verify filter contains primary key
	if !strings.Contains(filter, pkName) {
		t.Errorf("Filter should reference primary key %s, got: %s", pkName, filter)
	}

	t.Logf("Generated cursor filter: %s", filter)
}

func TestGetCursorFilter_Backward(t *testing.T) {
	options := common.RequestOptions{
		Sort: []common.SortOption{
			{Column: "created_at", Direction: "DESC"},
			{Column: "id", Direction: "ASC"},
		},
		CursorBackward: "456",
	}

	tableName := "posts"
	pkName := "id"
	modelColumns := []string{"id", "title", "created_at", "user_id"}

	filter, err := GetCursorFilter(tableName, pkName, modelColumns, options, nil)
	if err != nil {
		t.Fatalf("GetCursorFilter failed: %v", err)
	}

	if filter == "" {
		t.Fatal("Expected non-empty cursor filter")
	}

	// Verify filter contains cursor ID
	if !strings.Contains(filter, "456") {
		t.Errorf("Filter should reference cursor ID 456, got: %s", filter)
	}

	// For backward cursor, sort direction should be reversed
	// This is handled internally by the GetCursorFilter function
	t.Logf("Generated backward cursor filter: %s", filter)
}

func TestGetCursorFilter_NoCursor(t *testing.T) {
	options := common.RequestOptions{
		Sort: []common.SortOption{
			{Column: "created_at", Direction: "DESC"},
		},
		// No cursor set
	}

	tableName := "posts"
	pkName := "id"
	modelColumns := []string{"id", "title", "created_at"}

	_, err := GetCursorFilter(tableName, pkName, modelColumns, options, nil)
	if err == nil {
		t.Error("Expected error when no cursor is provided")
	}

	if !strings.Contains(err.Error(), "no cursor provided") {
		t.Errorf("Expected 'no cursor provided' error, got: %v", err)
	}
}

func TestGetCursorFilter_NoSort(t *testing.T) {
	options := common.RequestOptions{
		Sort:          []common.SortOption{},
		CursorForward: "123",
	}

	tableName := "posts"
	pkName := "id"
	modelColumns := []string{"id", "title"}

	_, err := GetCursorFilter(tableName, pkName, modelColumns, options, nil)
	if err == nil {
		t.Error("Expected error when no sort columns are defined")
	}

	if !strings.Contains(err.Error(), "no sort columns") {
		t.Errorf("Expected 'no sort columns' error, got: %v", err)
	}
}

func TestGetCursorFilter_MultiColumnSort(t *testing.T) {
	options := common.RequestOptions{
		Sort: []common.SortOption{
			{Column: "priority", Direction: "DESC"},
			{Column: "created_at", Direction: "DESC"},
			{Column: "id", Direction: "ASC"},
		},
		CursorForward: "789",
	}

	tableName := "tasks"
	pkName := "id"
	modelColumns := []string{"id", "title", "priority", "created_at"}

	filter, err := GetCursorFilter(tableName, pkName, modelColumns, options, nil)
	if err != nil {
		t.Fatalf("GetCursorFilter failed: %v", err)
	}

	// Verify filter contains priority column
	if !strings.Contains(filter, "priority") {
		t.Errorf("Filter should reference priority column, got: %s", filter)
	}

	// Verify filter contains created_at column
	if !strings.Contains(filter, "created_at") {
		t.Errorf("Filter should reference created_at column, got: %s", filter)
	}

	t.Logf("Generated multi-column cursor filter: %s", filter)
}

func TestGetCursorFilter_WithSchemaPrefix(t *testing.T) {
	options := common.RequestOptions{
		Sort: []common.SortOption{
			{Column: "name", Direction: "ASC"},
		},
		CursorForward: "100",
	}

	tableName := "public.users"
	pkName := "id"
	modelColumns := []string{"id", "name", "email"}

	filter, err := GetCursorFilter(tableName, pkName, modelColumns, options, nil)
	if err != nil {
		t.Fatalf("GetCursorFilter failed: %v", err)
	}

	// Should include full schema-qualified name in FROM clause
	if !strings.Contains(filter, "public.users") {
		t.Errorf("Filter FROM clause should use schema-qualified name public.users, got: %s", filter)
	}

	t.Logf("Generated cursor filter with schema: %s", filter)
}

func TestGetCursorFilter_LateralJoin(t *testing.T) {
	lateralJoin := "inner join lateral  (\nselect string_agg(a.name, '.') as sortorder\nfrom tree(account.rid_account) r\ninner join account a on a.id = r.id\n) fn on true"

	options := common.RequestOptions{
		Sort:          []common.SortOption{{Column: "fn.sortorder", Direction: "ASC"}},
		CursorForward: "8975",
	}

	tableName := "core.account"
	pkName := "rid_account"
	modelColumns := []string{"rid_account", "description", "pastelno"}
	expandJoins := map[string]string{"fn": lateralJoin}

	filter, err := GetCursorFilter(tableName, pkName, modelColumns, options, expandJoins)
	if err != nil {
		t.Fatalf("GetCursorFilter failed: %v", err)
	}

	t.Logf("Generated lateral cursor filter: %s", filter)

	if !strings.Contains(filter, "cursor_select_fn") {
		t.Errorf("Filter should reference cursor_select_fn alias, got: %s", filter)
	}
	if !strings.Contains(filter, "sortorder") {
		t.Errorf("Filter should reference sortorder column, got: %s", filter)
	}
	if strings.Contains(filter, " <  ") || strings.Contains(filter, " >  ") {
		t.Errorf("Filter should not contain empty comparison operators, got: %s", filter)
	}
}

func TestGetActiveCursor(t *testing.T) {
	tests := []struct {
		name              string
		options           common.RequestOptions
		expectedID        string
		expectedDirection CursorDirection
	}{
		{
			name: "Forward cursor only",
			options: common.RequestOptions{
				CursorForward: "123",
			},
			expectedID:        "123",
			expectedDirection: CursorForward,
		},
		{
			name: "Backward cursor only",
			options: common.RequestOptions{
				CursorBackward: "456",
			},
			expectedID:        "456",
			expectedDirection: CursorBackward,
		},
		{
			name: "Both cursors - forward takes precedence",
			options: common.RequestOptions{
				CursorForward:  "123",
				CursorBackward: "456",
			},
			expectedID:        "123",
			expectedDirection: CursorForward,
		},
		{
			name:              "No cursors",
			options:           common.RequestOptions{},
			expectedID:        "",
			expectedDirection: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			id, direction := getActiveCursor(tt.options)

			if id != tt.expectedID {
				t.Errorf("Expected cursor ID %q, got %q", tt.expectedID, id)
			}

			if direction != tt.expectedDirection {
				t.Errorf("Expected direction %d, got %d", tt.expectedDirection, direction)
			}
		})
	}
}

func TestResolveColumn(t *testing.T) {
	tests := []struct {
		name         string
		field        string
		prefix       string
		tableName    string
		modelColumns []string
		wantCursor   string
		wantTarget   string
		wantErr      bool
	}{
		{
			name:         "Simple column",
			field:        "id",
			prefix:       "",
			tableName:    "users",
			modelColumns: []string{"id", "name", "email"},
			wantCursor:   "cursor_select.id",
			wantTarget:   "users.id",
			wantErr:      false,
		},
		{
			name:         "Column with case insensitive match",
			field:        "NAME",
			prefix:       "",
			tableName:    "users",
			modelColumns: []string{"id", "name", "email"},
			wantCursor:   "cursor_select.NAME",
			wantTarget:   "users.NAME",
			wantErr:      false,
		},
		{
			name:         "Invalid column",
			field:        "invalid_field",
			prefix:       "",
			tableName:    "users",
			modelColumns: []string{"id", "name", "email"},
			wantErr:      true,
		},
		{
			name:         "JSON field",
			field:        "metadata->>'key'",
			prefix:       "",
			tableName:    "posts",
			modelColumns: []string{"id", "metadata"},
			wantCursor:   "cursor_select.metadata->>'key'",
			wantTarget:   "posts.metadata->>'key'",
			wantErr:      false,
		},
		{
			name:         "Joined column (isJoin=true, no error)",
			field:        "name",
			prefix:       "user",
			tableName:    "posts",
			modelColumns: []string{"id", "title"},
			wantErr:      false,
			// cursorCol and targetCol are empty when isJoin=true; handled by caller
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cursor, target, isJoin, err := resolveColumn(tt.field, tt.prefix, tt.tableName, tt.modelColumns)

			if tt.wantErr {
				if err == nil {
					t.Error("Expected error but got none")
				}
				return
			}

			if err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}

			// For join columns, cursor/target are empty and isJoin=true
			if isJoin {
				if cursor != "" || target != "" {
					t.Errorf("Expected empty cursor/target for join column, got %q / %q", cursor, target)
				}
				return
			}

			if cursor != tt.wantCursor {
				t.Errorf("Expected cursor %q, got %q", tt.wantCursor, cursor)
			}

			if target != tt.wantTarget {
				t.Errorf("Expected target %q, got %q", tt.wantTarget, target)
			}
		})
	}
}

func TestBuildPriorityChain(t *testing.T) {
	clauses := []string{
		"cursor_select.priority > tasks.priority",
		"cursor_select.created_at > tasks.created_at",
		"cursor_select.id < tasks.id",
	}

	result := buildPriorityChain(clauses)

	// Should build OR-AND chain for cursor comparison
	if !strings.Contains(result, "OR") {
		t.Error("Priority chain should contain OR operators")
	}

	if !strings.Contains(result, "AND") {
		t.Error("Priority chain should contain AND operators for composite conditions")
	}

	// First clause should appear standalone
	if !strings.Contains(result, clauses[0]) {
		t.Errorf("Priority chain should contain first clause: %s", clauses[0])
	}

	t.Logf("Built priority chain: %s", result)
}

func TestCursorFilter_SQL_Safety(t *testing.T) {
	// Test that cursor filter doesn't allow SQL injection
	options := common.RequestOptions{
		Sort: []common.SortOption{
			{Column: "created_at", Direction: "DESC"},
		},
		CursorForward: "123; DROP TABLE users; --",
	}

	tableName := "posts"
	pkName := "id"
	modelColumns := []string{"id", "created_at"}

	filter, err := GetCursorFilter(tableName, pkName, modelColumns, options, nil)
	if err != nil {
		t.Fatalf("GetCursorFilter failed: %v", err)
	}

	// The cursor ID is inserted directly into the query
	// This should be sanitized by the sanitizeWhereClause function in the handler
	// For now, just verify it generates a filter
	if filter == "" {
		t.Error("Expected non-empty cursor filter even with special characters")
	}

	t.Logf("Generated filter with special chars in cursor: %s", filter)
}
