package app

import (
	"context"
	"errors"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

type channelRealtimeRoomRows struct {
	values  []string
	index   int
	scanErr error
	rowsErr error
	closed  bool
}

func (rows *channelRealtimeRoomRows) Close() {
	rows.closed = true
}

func (rows *channelRealtimeRoomRows) Err() error {
	return rows.rowsErr
}

func (rows *channelRealtimeRoomRows) Next() bool {
	if rows.index >= len(rows.values) {
		return false
	}
	rows.index++
	return true
}

func (rows *channelRealtimeRoomRows) Scan(destinations ...any) error {
	if rows.scanErr != nil {
		return rows.scanErr
	}
	*destinations[0].(*string) = rows.values[rows.index-1]
	return nil
}

func TestCaptureChannelRealtimeRoomIDsInTxLocksCurrentTopologyAndFailsClosed(t *testing.T) {
	ctx := context.Background()

	t.Run("locking current read returns resolved parent and children", func(t *testing.T) {
		rows := &channelRealtimeRoomRows{values: []string{"thr_b", "chn_parent", "thr_a", "thr_a", " "}}
		tx := &stubRuntimeTx{
			query: func(_ context.Context, query string, args ...any) (coredb.Rows, error) {
				normalized := strings.Join(strings.Fields(query), " ")
				require.Contains(t, normalized, "s.workspace_id = $1")
				require.Contains(t, normalized, "s.id = $2")
				require.Contains(t, normalized, "ORDER BY s.id ASC")
				require.Contains(t, normalized, "FOR UPDATE")
				require.Equal(t, []any{"wsp_1", "chn_parent"}, args)
				return rows, nil
			},
		}

		roomIDs, err := captureChannelRealtimeRoomIDsInTx(ctx, tx, " wsp_1 ", " chn_parent ")
		require.NoError(t, err)
		require.Equal(t, []string{"chn_parent", "thr_a", "thr_b"}, roomIDs)
		require.True(t, rows.closed)
	})

	t.Run("query error is fatal", func(t *testing.T) {
		tx := &stubRuntimeTx{
			query: func(context.Context, string, ...any) (coredb.Rows, error) {
				return nil, errors.New("query failed")
			},
		}

		_, err := captureChannelRealtimeRoomIDsInTx(ctx, tx, "wsp_1", "chn_parent")
		require.ErrorContains(t, err, "capture channel realtime rooms")
		require.ErrorContains(t, err, "query failed")
	})

	t.Run("scan error is fatal", func(t *testing.T) {
		rows := &channelRealtimeRoomRows{values: []string{"chn_parent"}, scanErr: errors.New("scan failed")}
		tx := &stubRuntimeTx{
			query: func(context.Context, string, ...any) (coredb.Rows, error) {
				return rows, nil
			},
		}

		_, err := captureChannelRealtimeRoomIDsInTx(ctx, tx, "wsp_1", "chn_parent")
		require.ErrorContains(t, err, "scan channel realtime room")
		require.True(t, rows.closed)
	})

	t.Run("rows error is fatal", func(t *testing.T) {
		rows := &channelRealtimeRoomRows{values: []string{"chn_parent"}, rowsErr: errors.New("iterate failed")}
		tx := &stubRuntimeTx{
			query: func(context.Context, string, ...any) (coredb.Rows, error) {
				return rows, nil
			},
		}

		_, err := captureChannelRealtimeRoomIDsInTx(ctx, tx, "wsp_1", "chn_parent")
		require.ErrorContains(t, err, "iterate channel realtime rooms")
		require.True(t, rows.closed)
	})

	t.Run("missing parent violates the captured-set contract", func(t *testing.T) {
		rows := &channelRealtimeRoomRows{values: []string{"thr_a"}}
		tx := &stubRuntimeTx{
			query: func(context.Context, string, ...any) (coredb.Rows, error) {
				return rows, nil
			},
		}

		_, err := captureChannelRealtimeRoomIDsInTx(ctx, tx, "wsp_1", "chn_parent")
		require.ErrorContains(t, err, "missing parent channel")
	})
}

func TestChannelMemberMutationEffectsNormalizeRealtimeRoomIDs(t *testing.T) {
	effects := &ChannelMemberMutationEffects{
		RealtimeRoomIDs: []string{"thr_b", "", "chn_parent", "thr_b", " thr_a "},
	}

	effects.Normalize()

	require.Equal(t, []string{"chn_parent", "thr_a", "thr_b"}, effects.RealtimeRoomIDs)
}
