package cmdpacks_test

import (
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl/cmdpacks"
	"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
)

func TestModRS_New(t *testing.T) {
	svc := newService(t)

	rsProxy, err := svc.CommandController.ExecuteAndWait(t.Context(), `rs:new`)

	assert.NoError(t, err)
	assert.IsType(t, rsProxy, cmdpacks.SimpleProxy[*models.ResultSet]{})
}

func TestModRS_NextPage(t *testing.T) {
	t.Run("multiple pages", func(t *testing.T) {
		svc := newService(t, withDataGenerator(largeTestData), withTable("large-table"), withDefaultLimit(20))

		hasNextPage, err := svc.CommandController.ExecuteAndWait(t.Context(), `@resultset.HasNextPage`)
		assert.NoError(t, err)
		assert.True(t, hasNextPage.(bool))

		// Page 2
		rsProxy, err := svc.CommandController.ExecuteAndWait(t.Context(), `rs:next-page @resultset`)

		assert.NoError(t, err)
		assert.IsType(t, cmdpacks.SimpleProxy[*models.ResultSet]{}, rsProxy)
		assert.Equal(t, 20, len(rsProxy.(cmdpacks.SimpleProxy[*models.ResultSet]).ProxyValue().Items()))

		hasNextPage, err = svc.CommandController.ExecuteAndWait(t.Context(), `(rs:next-page @resultset).HasNextPage`)
		assert.NoError(t, err)
		assert.True(t, hasNextPage.(bool))

		// Page 3
		rsProxy, err = svc.CommandController.ExecuteAndWait(t.Context(), `rs:next-page @resultset | rs:next-page`)

		assert.NoError(t, err)
		assert.IsType(t, cmdpacks.SimpleProxy[*models.ResultSet]{}, rsProxy)
		assert.Equal(t, 10, len(rsProxy.(cmdpacks.SimpleProxy[*models.ResultSet]).ProxyValue().Items()))

		hasNextPage, err = svc.CommandController.ExecuteAndWait(t.Context(), `(rs:next-page @resultset | rs:next-page).HasNextPage`)
		assert.NoError(t, err)
		assert.False(t, hasNextPage.(bool))

		// Last page
		rsProxy, err = svc.CommandController.ExecuteAndWait(t.Context(), `rs:next-page (rs:next-page @resultset | rs:next-page)`)
		assert.NoError(t, err)
		assert.Nil(t, rsProxy)
	})

	t.Run("only one page", func(t *testing.T) {
		svc := newService(t)

		hasNextPage, err := svc.CommandController.ExecuteAndWait(t.Context(), `@resultset.HasNextPage`)
		assert.NoError(t, err)
		assert.False(t, hasNextPage.(bool))

		rsProxy, err := svc.CommandController.ExecuteAndWait(t.Context(), `rs:next-page @resultset`)
		assert.NoError(t, err)
		assert.Nil(t, rsProxy)
	})
}

func TestModRS_Union(t *testing.T) {
	svc := newService(t, withDefaultLimit(2))

	rsProxy, err := svc.CommandController.ExecuteAndWait(t.Context(), `
		mr = rs:union @resultset (rs:next-page @resultset)
		
		assert (eq (len $mr.Items) 3) "expected len == 3"
		assert (eq $mr.Items.(0).pk "abc") "expected 0.pk"
		assert (eq $mr.Items.(0).sk "111") "expected 0.sk"
		assert (eq $mr.Items.(1).pk "abc") "expected 1.pk"
		assert (eq $mr.Items.(1).sk "222") "expected 1.sk"
		assert (eq $mr.Items.(2).pk "bbb") "expected 2.pk"
		assert (eq $mr.Items.(2).sk "131") "expected 2.sk"

		$mr
	`)

	assert.NoError(t, err)
	assert.IsType(t, cmdpacks.SimpleProxy[*models.ResultSet]{}, rsProxy)

	rs := rsProxy.(cmdpacks.SimpleProxy[*models.ResultSet]).ProxyValue()
	assert.Equal(t, 3, len(rs.Items()))
}

func TestModRS_Query(t *testing.T) {
	tests := []struct {
		descr    string
		cmd      string
		wantRows []int
	}{
		{
			descr:    "query with pk 1",
			cmd:      `rs:query 'pk="abc"' -table service-test-data`,
			wantRows: []int{0, 1},
		},
		{
			descr:    "query with pk 2",
			cmd:      `rs:query 'pk="bbb"' -table service-test-data`,
			wantRows: []int{2},
		},
		{
			descr:    "query with sk 1",
			cmd:      `rs:query 'sk="222"' -table service-test-data`,
			wantRows: []int{1},
		},
		{
			descr:    "query with args 1",
			cmd:      `rs:query 'pk=$v' [v:'abc'] -table service-test-data`,
			wantRows: []int{0, 1},
		},
		{
			descr:    "query with args 2",
			cmd:      `rs:query ':k=$v' [k:'pk' v:'abc'] -table service-test-data`,
			wantRows: []int{0, 1},
		},
		{
			descr:    "query with args 3",
			cmd:      `rs:query ':k=$v' [k:'beta' v:1231] -table service-test-data`,
			wantRows: []int{1},
		},
		{
			descr:    "query with args with no table set",
			cmd:      `rs:query ':k=$v' [k:'beta' v:1231]`,
			wantRows: []int{1},
		},
	}
	for _, tt := range tests {
		t.Run(tt.descr, func(t *testing.T) {
			svc := newService(t)

			res, err := svc.CommandController.ExecuteAndWait(t.Context(), tt.cmd)
			assert.NoError(t, err)

			rs := res.(cmdpacks.SimpleProxy[*models.ResultSet]).ProxyValue()
			assert.Len(t, rs.Items(), len(tt.wantRows))

			for i, rowIndex := range tt.wantRows {
				for key, want := range svc.testData[0].Data[rowIndex] {
					have, ok := rs.Items()[i].AttributeValueAsString(key)
					assert.True(t, ok)
					assert.Equal(t, fmt.Sprint(want), have)
				}
			}
		})
	}
}

func TestModRS_First(t *testing.T) {
	tests := []struct {
		descr string
		cmd   string
	}{
		{
			descr: "returns the first item in sorted order",
			cmd: `
				rs = rs:query 'pk="abc"' -table service-test-data
				assert (eq $rs.First.pk "abc") "expected First.pk == abc"
				assert (eq $rs.First.sk "111") "expected First.sk == 111"
			`,
		}, {
			descr: "returns the first item in single item",
			cmd: `
				rs = rs:query 'pk="abc" and sk="222"' -table service-test-data
				assert (eq $rs.First.pk "abc") "expected First.pk == abc"
				assert (eq $rs.First.sk "222") "expected First.sk == 222"
				assert (eq $rs.First.beta 1231) "expected First.beta == 1231"
			`,
		}, {
			descr: "returns the first item in empty result",
			cmd: `
				rs = rs:query 'pk="zzz"' -table service-test-data
				assert (eq $rs.First ()) "expected First to be nil"
			`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.descr, func(t *testing.T) {
			svc := newService(t)

			_, err := svc.CommandController.ExecuteAndWait(t.Context(), tt.cmd)
			assert.NoError(t, err)
		})
	}
}
