package service

import (
	"context"
	"path/filepath"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbworker"
	scalerpb "github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/proto/scaler"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbworker/schema/ttl_task"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type mockScalerServer struct {
	db  *gorm.DB
	cfg *tidbworker.Config
}

func (m *mockScalerServer) GetDB() *gorm.DB {
	return m.db
}

func (m *mockScalerServer) GetConfig() *tidbworker.Config {
	return m.cfg
}

// newTestTTLService creates a ScalerService backed by an isolated sqlite DB,
// so each test can verify TTL task persistence without external dependencies.
func newTestTTLService(t *testing.T) *ScalerService {
	t.Helper()

	dbPath := filepath.Join(t.TempDir(), "ttl-service.db")
	db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.AutoMigrate(&ttl_task.TTLTableInfo{}, &ttl_task.Task{}, &ttl_task.TaskDone{}))

	return &ScalerService{
		Server: &mockScalerServer{
			db:  db,
			cfg: &tidbworker.Config{},
		},
	}
}

// testTTLRegisterRequest returns the minimal request required by
// RegisterTTLTask. Most tests only need to vary the table ID.
func testTTLRegisterRequest(tableID int64) *scalerpb.RegisterTTLTaskRequest {
	return &scalerpb.RegisterTTLTaskRequest{
		Header: &scalerpb.RequestHeader{
			KeyspaceName: "ks-1",
			TidbPool:     "pool-a",
		},
		TableId:      tableID,
		TtlJobEnable: true,
	}
}

func TestRegisterTTLTaskSameSecondKeepsLatestPendingTask(t *testing.T) {
	// Arrange: create a test service backed by an isolated sqlite database.
	svc := newTestTTLService(t)

	// Arrange: freeze the clock to reproduce duplicate registrations in the same second.
	originalNow := ttlTaskNow
	ttlTaskNow = func() time.Time {
		return time.Unix(1710000000, 0)
	}
	t.Cleanup(func() {
		ttlTaskNow = originalNow
	})

	// Arrange: build a TTL registration request for a single table.
	req := testTTLRegisterRequest(100)

	// Act: register the same table twice in immediate succession.
	resp, err := svc.RegisterTTLTask(context.Background(), req)
	require.NoError(t, err)
	require.Nil(t, resp.Error)

	resp, err = svc.RegisterTTLTask(context.Background(), req)
	require.NoError(t, err)
	require.Nil(t, resp.Error)

	// Assert: only one pending task is kept for registrations in the same second.
	var tasks []ttl_task.Task
	err = svc.GetDB().Order("last_ttl_run_time asc").Find(&tasks).Error
	require.NoError(t, err)
	require.Len(t, tasks, 1)

	require.Equal(t, int64(1710000000), tasks[0].LastTTLRunTime)
	require.Equal(t, ttl_task.StatePending, tasks[0].State)
}

func TestRegisterTTLTaskDifferentTablesRemainIndependent(t *testing.T) {
	// Arrange: create a test service backed by an isolated sqlite database.
	svc := newTestTTLService(t)

	// Arrange: freeze the clock so the assertions focus on table-level isolation.
	originalNow := ttlTaskNow
	ttlTaskNow = func() time.Time {
		return time.Unix(1710000000, 0)
	}
	t.Cleanup(func() {
		ttlTaskNow = originalNow
	})

	// Act: register TTL tasks for two different tables.
	resp, err := svc.RegisterTTLTask(context.Background(), testTTLRegisterRequest(100))
	require.NoError(t, err)
	require.Nil(t, resp.Error)

	resp, err = svc.RegisterTTLTask(context.Background(), testTTLRegisterRequest(200))
	require.NoError(t, err)
	require.Nil(t, resp.Error)

	// Assert: each table keeps its own pending task without overwriting the other.
	var tasks []ttl_task.Task
	err = svc.GetDB().Order("table_id asc").Find(&tasks).Error
	require.NoError(t, err)
	require.Len(t, tasks, 2)

	require.Equal(t, int64(100), tasks[0].TableID)
	require.Equal(t, ttl_task.StatePending, tasks[0].State)
	require.Equal(t, int64(200), tasks[1].TableID)
	require.Equal(t, ttl_task.StatePending, tasks[1].State)
}
