package serviceactor

import (
	"context"
	"database/sql"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

func TestDeleteRuntimeRowsBeforeUsesIndexedCandidateQueryAndBatchedDelete(t *testing.T) {
	t.Parallel()

	cutoff := time.Date(2026, 6, 1, 4, 5, 6, 0, time.UTC)
	db := &runtimeRetentionCapture{
		rows: &runtimeRetentionRows{ids: []string{"srts_old_1", "srts_old_2"}},
	}

	deleted, err := deleteRuntimeRowsBefore(context.Background(), db, runtimeRetentionDeleteSpec{
		Table:       "service_runtime_sessions",
		TimeColumn:  "updated_at",
		Status:      RuntimeSessionStatusOffline,
		Cutoff:      cutoff,
		Limit:       runtimeRetentionDeleteBatchMax + 100,
		Description: "runtime sessions",
	})

	require.NoError(t, err)
	require.Equal(t, int64(2), deleted)
	require.Len(t, db.queries, 1)
	query := strings.Join(strings.Fields(db.queries[0]), " ")
	require.Contains(t, query, "FROM service_runtime_sessions")
	require.Contains(t, query, "status = $1")
	require.Contains(t, query, "updated_at < $2")
	require.Contains(t, query, "ORDER BY updated_at ASC, id ASC")
	require.Contains(t, query, "LIMIT $3")
	require.Equal(t, []any{RuntimeSessionStatusOffline, cutoff, runtimeRetentionDeleteBatchMax}, db.queryArgs[0])
	require.Len(t, db.execs, 1)
	deleteQuery := strings.Join(strings.Fields(db.execs[0]), " ")
	require.Equal(t, "DELETE FROM service_runtime_sessions WHERE id IN ($1, $2)", deleteQuery)
	require.Equal(t, []any{"srts_old_1", "srts_old_2"}, db.execArgs[0])
}

func TestDeleteRuntimeRowsBeforeSkipsDeleteWhenNoCandidates(t *testing.T) {
	t.Parallel()

	cutoff := time.Date(2026, 6, 1, 4, 5, 6, 0, time.UTC)
	db := &runtimeRetentionCapture{rows: &runtimeRetentionRows{}}

	deleted, err := deleteRuntimeRowsBefore(context.Background(), db, runtimeRetentionDeleteSpec{
		Table:       "service_event_delivery_effects",
		TimeColumn:  "updated_at",
		Status:      DeliveryEffectStatusFailed,
		Cutoff:      cutoff,
		Limit:       10,
		Description: "delivery effects",
	})

	require.NoError(t, err)
	require.Zero(t, deleted)
	require.Len(t, db.queries, 1)
	require.Empty(t, db.execs)
}

type runtimeRetentionCapture struct {
	queries   []string
	queryArgs [][]any
	execs     []string
	execArgs  [][]any
	rows      coredb.Rows
}

func (db *runtimeRetentionCapture) Query(_ context.Context, query string, args ...any) (coredb.Rows, error) {
	db.queries = append(db.queries, query)
	db.queryArgs = append(db.queryArgs, append([]any(nil), args...))
	return db.rows, nil
}

func (db *runtimeRetentionCapture) QueryRow(context.Context, string, ...any) coredb.Row {
	return runtimeRetentionRow{err: sql.ErrNoRows}
}

func (db *runtimeRetentionCapture) Exec(_ context.Context, query string, args ...any) (coredb.Result, error) {
	db.execs = append(db.execs, query)
	db.execArgs = append(db.execArgs, append([]any(nil), args...))
	return runtimeRetentionResult{rowsAffected: int64(len(args))}, nil
}

type runtimeRetentionRows struct {
	ids []string
	idx int
}

func (rows *runtimeRetentionRows) Close() {}

func (rows *runtimeRetentionRows) Err() error { return nil }

func (rows *runtimeRetentionRows) Next() bool {
	return rows.idx < len(rows.ids)
}

func (rows *runtimeRetentionRows) Scan(dest ...any) error {
	if rows.idx >= len(rows.ids) || len(dest) != 1 {
		return sql.ErrNoRows
	}
	target, ok := dest[0].(*string)
	if !ok {
		return sql.ErrNoRows
	}
	*target = rows.ids[rows.idx]
	rows.idx++
	return nil
}

type runtimeRetentionRow struct {
	err error
}

func (row runtimeRetentionRow) Scan(...any) error {
	if row.err != nil {
		return row.err
	}
	return nil
}

type runtimeRetentionResult struct {
	rowsAffected int64
}

func (result runtimeRetentionResult) RowsAffected() int64 {
	return result.rowsAffected
}
