package store

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/jmoiron/sqlx"
	"github.com/stretchr/testify/require"

	"github.com/pingcap/test-infra/common/model"
	"github.com/pingcap/test-infra/common/model/tcms/taskruns"
	"github.com/pingcap/test-infra/services/tcms/utils"
	utilSql "github.com/pingcap/test-infra/services/tcms/utils/sql"
)

func sampleTestbedRun() taskruns.TestbedSetup {
	now := time.Unix(1, 0).UTC()
	planID := model.ID(1)
	return taskruns.TestbedSetup{
		GeneralHeader: taskruns.HeaderTestbedSetup,
		MetaData:      taskruns.GeneralMetaData{ID: 1, CreateAt: now, UpdateAt: now},
		Spec:          taskruns.TestbedSetupSpec{TestbedRef: &taskruns.TestbedRef{PlanID: &planID}},
	}
}

func newMock(t *testing.T) (*utilSql.Database, sqlmock.Sqlmock, error) {
	sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
	require.NoError(t, err)
	return &utilSql.Database{DB: sqlx.NewDb(sqlDB, "mysql")}, mock, nil
}

func TestTestbedSetupService_Get(t *testing.T) {
	db, mock, err := newMock(t)
	if err != nil {
		return
	}
	expected := sampleTestbedRun()
	sql := "SELECT id, body FROM testbed_setups WHERE id = ?"
	qr := mock.ExpectQuery(sql)
	marshal, err := json.Marshal(expected)
	require.NoError(t, err)
	qr.WillReturnRows(sqlmock.NewRows(
		[]string{"id", "body"}).
		AddRow(1, json.RawMessage(marshal)),
	)
	svc := testbedSetupService{db: db}
	ctx := utils.FakeCtx()
	obj, err := svc.Get(ctx, model.ID(1), taskruns.GetTestbedSetupOpts{})
	require.NoError(t, err)
	require.Equal(t, expected, *obj)
}

func TestTestbedService_Create(t *testing.T) {
	db, mock, err := newMock(t)
	if err != nil {
		return
	}
	request := sampleTestbedRun()
	now := time.Unix(2, 0)
	svc := testbedSetupService{db: db, now: func() time.Time {
		return now
	}}

	expected := request
	expected.MetaData.CreateAt = now
	expected.Status.Schedule = &taskruns.Schedule{ScheduleStatus: taskruns.ScheduleStatusPending}

	marshal, err := json.Marshal(expected)
	require.NoError(t, err)
	mock.ExpectBegin()
	mock.ExpectExec("INSERT INTO testbed_setups(id, body) VALUES (?, ?)").
		WithArgs(request.MetaData.ID, marshal).
		WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectQuery("SELECT id, body FROM testbed_setups WHERE id = ?").
		WithArgs(1).
		WillReturnRows(sqlmock.NewRows(
			[]string{"id", "body"}).
			AddRow(1, json.RawMessage(marshal)),
		)
	mock.ExpectCommit()
	ctx := utils.FakeCtx()
	obj, err := svc.Create(ctx, request, taskruns.CreateTestbedSetupOpts{})

	require.NoError(t, err)
	require.Equal(t, expected, *obj)
}

func TestTestbedService_UpdateSchedule(t *testing.T) {
	db, mock, err := newMock(t)
	if err != nil {
		return
	}
	now := time.Unix(2, 0)
	svc := testbedSetupService{db: db, now: func() time.Time {
		return now
	}}

	old := sampleTestbedRun()
	oldMarshal, err := json.Marshal(old)
	require.NoError(t, err)

	patch := taskruns.NewTestbedSetup()
	patch.MetaData.ID = 1
	patch.Status.Schedule = &taskruns.Schedule{ScheduleStatus: taskruns.ScheduleStatusApproved}
	planID := model.ID(200)
	patch.Spec.TestbedRef = &taskruns.TestbedRef{PlanID: &planID}

	expected := old
	expected.MetaData.UpdateAt = now
	expected.Status.Schedule = patch.Status.Schedule
	expectedMarshal, err := json.Marshal(expected)
	require.NoError(t, err)

	mock.ExpectBegin()
	mock.ExpectQuery("SELECT id, body FROM testbed_setups WHERE id = ?").
		WithArgs(1).
		WillReturnRows(sqlmock.NewRows(
			[]string{"id", "body"}).
			AddRow(1, json.RawMessage(oldMarshal)),
		)
	mock.ExpectExec("UPDATE testbed_setups SET body = ? WHERE id = ?").
		WithArgs(expectedMarshal, old.MetaData.ID).
		WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectQuery("SELECT id, body FROM testbed_setups WHERE id = ?").
		WithArgs(1).
		WillReturnRows(sqlmock.NewRows(
			[]string{"id", "body"}).
			AddRow(1, json.RawMessage(expectedMarshal)),
		)
	mock.ExpectCommit()
	ctx := utils.FakeCtx()
	obj, err := svc.UpdateSchedule(ctx, patch, taskruns.GeneralUpdateOpts{})
	require.NoError(t, err)
	require.Equal(t, expected, *obj)
}
