package executor

import (
	"encoding/json"
	"testing"

	"github.com/everything-on-mysql/cassandra-on-mysql/pkg/cql/parser"
)

func TestListAppendPrepend(t *testing.T) {
	cases := []struct {
		name     string
		cql      string
		existing []interface{}
		expected []interface{}
	}{
		{
			name:     "append",
			cql:      "UPDATE ks.tbl SET tags = tags + ['new'] WHERE id = 1",
			existing: []interface{}{"old"},
			expected: []interface{}{"old", "new"},
		},
		{
			name:     "prepend",
			cql:      "UPDATE ks.tbl SET tags = ['new'] + tags WHERE id = 1",
			existing: []interface{}{"old"},
			expected: []interface{}{"new", "old"},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			updateStmt := mustParseUpdate(t, tc.cql)
			columnTypes := map[string]string{"tags": "list<text>"}
			assignments, hasMutation, err := collectAssignmentInfo(updateStmt.Assigments, columnTypes, nil)
			if err != nil {
				t.Fatalf("collectAssignmentInfo failed: %v", err)
			}
			if !hasMutation {
				t.Fatalf("expected mutation detection")
			}
			markerIndex := buildBindMarkerIndex(updateStmt)
			updates, err := applyCollectionAssignments(assignments, map[string]interface{}{"tags": tc.existing}, nil, markerIndex)
			if err != nil {
				t.Fatalf("applyCollectionAssignments failed: %v", err)
			}
			decoded := decodeJSONValueOrPanic(t, updates["tags"])
			values, ok := decoded.([]interface{})
			if !ok {
				t.Fatalf("expected list value, got %T", decoded)
			}
			if len(values) != len(tc.expected) {
				t.Fatalf("expected %d values, got %d", len(tc.expected), len(values))
			}
			for i, exp := range tc.expected {
				if values[i] != exp {
					t.Fatalf("unexpected list[%d]: %#v", i, values[i])
				}
			}
		})
	}
}

func TestListIndexUpdateWithBindMarkers(t *testing.T) {
	updateStmt := mustParseUpdate(t, "UPDATE ks.tbl SET tags[?] = ? WHERE id = ?")
	columnTypes := map[string]string{"tags": "list<text>"}
	assignments, hasMutation, err := collectAssignmentInfo(updateStmt.Assigments, columnTypes, nil)
	if err != nil {
		t.Fatalf("collectAssignmentInfo failed: %v", err)
	}
	if !hasMutation {
		t.Fatalf("expected mutation detection")
	}
	markerIndex := buildBindMarkerIndex(updateStmt)
	params := []interface{}{1, "x", 42}
	updates, err := applyCollectionAssignments(assignments, map[string]interface{}{"tags": []interface{}{"a", "b", "c"}}, params, markerIndex)
	if err != nil {
		t.Fatalf("applyCollectionAssignments failed: %v", err)
	}
	decoded := decodeJSONValueOrPanic(t, updates["tags"])
	values, ok := decoded.([]interface{})
	if !ok {
		t.Fatalf("expected list value, got %T", decoded)
	}
	expected := []interface{}{"a", "x", "c"}
	if len(values) != len(expected) {
		t.Fatalf("expected %d values, got %d", len(expected), len(values))
	}
	for i, exp := range expected {
		if values[i] != exp {
			t.Fatalf("unexpected list[%d]: %#v", i, values[i])
		}
	}
}

func TestSetAddRemove(t *testing.T) {
	updateStmt := mustParseUpdate(t, "UPDATE ks.tbl SET roles = roles + {'user', 'admin'} WHERE id = 1")
	columnTypes := map[string]string{"roles": "set<text>"}
	assignments, hasMutation, err := collectAssignmentInfo(updateStmt.Assigments, columnTypes, nil)
	if err != nil {
		t.Fatalf("collectAssignmentInfo failed: %v", err)
	}
	if !hasMutation {
		t.Fatalf("expected mutation detection")
	}
	markerIndex := buildBindMarkerIndex(updateStmt)
	updates, err := applyCollectionAssignments(assignments, map[string]interface{}{"roles": []interface{}{"admin"}}, nil, markerIndex)
	if err != nil {
		t.Fatalf("applyCollectionAssignments failed: %v", err)
	}
	decoded := decodeJSONValueOrPanic(t, updates["roles"])
	values := decoded.([]interface{})
	if len(values) != 2 {
		t.Fatalf("expected 2 values, got %d", len(values))
	}

	removeStmt := mustParseUpdate(t, "UPDATE ks.tbl SET roles = roles - {'guest'} WHERE id = 1")
	assignments, _, err = collectAssignmentInfo(removeStmt.Assigments, columnTypes, nil)
	if err != nil {
		t.Fatalf("collectAssignmentInfo failed: %v", err)
	}
	markerIndex = buildBindMarkerIndex(removeStmt)
	updates, err = applyCollectionAssignments(assignments, map[string]interface{}{"roles": []interface{}{"admin", "guest"}}, nil, markerIndex)
	if err != nil {
		t.Fatalf("applyCollectionAssignments failed: %v", err)
	}
	decoded = decodeJSONValueOrPanic(t, updates["roles"])
	values = decoded.([]interface{})
	if len(values) != 1 || values[0] != "admin" {
		t.Fatalf("unexpected set remove result: %#v", values)
	}
}

func TestMapMutations(t *testing.T) {
	columnTypes := map[string]string{"profile": "map<text, text>"}

	mergeStmt := mustParseUpdate(t, "UPDATE ks.tbl SET profile = profile + {'email': 'a'} WHERE id = 1")
	assignments, hasMutation, err := collectAssignmentInfo(mergeStmt.Assigments, columnTypes, nil)
	if err != nil {
		t.Fatalf("collectAssignmentInfo failed: %v", err)
	}
	if !hasMutation {
		t.Fatalf("expected mutation detection")
	}
	markerIndex := buildBindMarkerIndex(mergeStmt)
	updates, err := applyCollectionAssignments(assignments, map[string]interface{}{"profile": map[string]interface{}{"name": "b"}}, nil, markerIndex)
	if err != nil {
		t.Fatalf("applyCollectionAssignments failed: %v", err)
	}
	decoded := decodeJSONValueOrPanic(t, updates["profile"])
	values := decoded.(map[string]interface{})
	if values["email"] != "a" || values["name"] != "b" {
		t.Fatalf("unexpected map merge result: %#v", values)
	}

	removeStmt := mustParseUpdate(t, "UPDATE ks.tbl SET profile = profile - {'email'} WHERE id = 1")
	assignments, _, err = collectAssignmentInfo(removeStmt.Assigments, columnTypes, nil)
	if err != nil {
		t.Fatalf("collectAssignmentInfo failed: %v", err)
	}
	markerIndex = buildBindMarkerIndex(removeStmt)
	updates, err = applyCollectionAssignments(assignments, map[string]interface{}{"profile": map[string]interface{}{"email": "a", "name": "b"}}, nil, markerIndex)
	if err != nil {
		t.Fatalf("applyCollectionAssignments failed: %v", err)
	}
	decoded = decodeJSONValueOrPanic(t, updates["profile"])
	values = decoded.(map[string]interface{})
	if _, ok := values["email"]; ok {
		t.Fatalf("expected email key removed")
	}

	setStmt := mustParseUpdate(t, "UPDATE ks.tbl SET profile['email'] = 'new' WHERE id = 1")
	assignments, _, err = collectAssignmentInfo(setStmt.Assigments, columnTypes, nil)
	if err != nil {
		t.Fatalf("collectAssignmentInfo failed: %v", err)
	}
	markerIndex = buildBindMarkerIndex(setStmt)
	updates, err = applyCollectionAssignments(assignments, map[string]interface{}{"profile": map[string]interface{}{"email": "a"}}, nil, markerIndex)
	if err != nil {
		t.Fatalf("applyCollectionAssignments failed: %v", err)
	}
	decoded = decodeJSONValueOrPanic(t, updates["profile"])
	values = decoded.(map[string]interface{})
	if values["email"] != "new" {
		t.Fatalf("unexpected map key update: %#v", values)
	}
}

func TestDeleteMapElement(t *testing.T) {
	deleteStmt := mustParseDelete(t, "DELETE profile['email'] FROM ks.tbl WHERE id = 1")
	columnTypes := map[string]string{"profile": "map<text, text>"}
	assignments := deleteAssignments(deleteStmt)
	infos, hasMutation, err := collectAssignmentInfo(assignments, columnTypes, nil)
	if err != nil {
		t.Fatalf("collectAssignmentInfo failed: %v", err)
	}
	if !hasMutation {
		t.Fatalf("expected mutation detection")
	}
	markerIndex := buildBindMarkerIndex(deleteStmt)
	updates, err := applyCollectionAssignments(infos, map[string]interface{}{"profile": map[string]interface{}{"email": "a", "name": "b"}}, nil, markerIndex)
	if err != nil {
		t.Fatalf("applyCollectionAssignments failed: %v", err)
	}
	decoded := decodeJSONValueOrPanic(t, updates["profile"])
	values := decoded.(map[string]interface{})
	if _, ok := values["email"]; ok {
		t.Fatalf("expected email key removed")
	}
}

func TestMutationOnEmptyRow(t *testing.T) {
	updateStmt := mustParseUpdate(t, "UPDATE ks.tbl SET tags = tags + ['new'] WHERE id = 1")
	columnTypes := map[string]string{"tags": "list<text>"}
	assignments, hasMutation, err := collectAssignmentInfo(updateStmt.Assigments, columnTypes, nil)
	if err != nil {
		t.Fatalf("collectAssignmentInfo failed: %v", err)
	}
	if !hasMutation {
		t.Fatalf("expected mutation detection")
	}
	markerIndex := buildBindMarkerIndex(updateStmt)
	updates, err := applyCollectionAssignments(assignments, map[string]interface{}{}, nil, markerIndex)
	if err != nil {
		t.Fatalf("applyCollectionAssignments failed: %v", err)
	}
	decoded := decodeJSONValueOrPanic(t, updates["tags"])
	values := decoded.([]interface{})
	if len(values) != 1 || values[0] != "new" {
		t.Fatalf("unexpected list append on empty: %#v", values)
	}
}

func TestMapRemoveWithBindMarker(t *testing.T) {
	updateStmt := mustParseUpdate(t, "UPDATE ks.tbl SET profile = profile - ? WHERE id = ?")
	columnTypes := map[string]string{"profile": "map<text, text>"}
	assignments, hasMutation, err := collectAssignmentInfo(updateStmt.Assigments, columnTypes, nil)
	if err != nil {
		t.Fatalf("collectAssignmentInfo failed: %v", err)
	}
	if !hasMutation {
		t.Fatalf("expected mutation detection")
	}
	markerIndex := buildBindMarkerIndex(updateStmt)
	keys, _ := json.Marshal([]interface{}{"email"})
	params := []interface{}{keys, "pk"}
	updates, err := applyCollectionAssignments(assignments, map[string]interface{}{"profile": map[string]interface{}{"email": "a", "name": "b"}}, params, markerIndex)
	if err != nil {
		t.Fatalf("applyCollectionAssignments failed: %v", err)
	}
	decoded := decodeJSONValueOrPanic(t, updates["profile"])
	values := decoded.(map[string]interface{})
	if _, ok := values["email"]; ok {
		t.Fatalf("expected email key removed")
	}
}

func mustParseUpdate(t *testing.T, cql string) *parser.UpdateStatement {
	t.Helper()
	stmt, err := parser.NewParser(cql).Parse()
	if err != nil {
		t.Fatalf("parse failed: %v", err)
	}
	updateStmt, ok := stmt.(*parser.UpdateStatement)
	if !ok {
		t.Fatalf("expected update statement, got %T", stmt)
	}
	return updateStmt
}

func mustParseDelete(t *testing.T, cql string) *parser.DeleteStatement {
	t.Helper()
	stmt, err := parser.NewParser(cql).Parse()
	if err != nil {
		t.Fatalf("parse failed: %v", err)
	}
	deleteStmt, ok := stmt.(*parser.DeleteStatement)
	if !ok {
		t.Fatalf("expected delete statement, got %T", stmt)
	}
	return deleteStmt
}

func decodeJSONValueOrPanic(t *testing.T, value interface{}) interface{} {
	t.Helper()
	bytes, ok := value.([]byte)
	if !ok {
		t.Fatalf("expected []byte JSON, got %T", value)
	}
	decoded, err := decodeJSONValue(bytes)
	if err != nil {
		t.Fatalf("decodeJSONValue failed: %v", err)
	}
	return decoded
}
