package balancer_test

import (
	"context"
	"testing"

	"github.com/featurebasedb/featurebase/v3/dax"
	"github.com/featurebasedb/featurebase/v3/dax/controller/sqldb"
	"github.com/stretchr/testify/require"
)

const (
	orgID     = "orgid"
	dbID      = "blah"
	dbName    = "nameofdb"
	role      = "compute"
	tableName = "tbl"
	tableID   = "tblid"
)

func TestWorkerJobService(t *testing.T) {
	tx, err := SQLTransactor.BeginTx(context.Background(), true)
	require.NoError(t, err, "getting transaction")

	defer func() {
		err := tx.Rollback()
		if err != nil {
			t.Logf("rolling back: %v", err)
		}
	}()

	// must have a database to do workerjob stuff
	schemar := sqldb.NewSchemar(nil)
	err = schemar.CreateDatabase(tx,
		&dax.QualifiedDatabase{
			OrganizationID: orgID,
			Database:       dax.Database{ID: dbID, Name: dbName}})
	require.NoError(t, err)

	wjSvc := sqldb.NewWorkerJobService(nil)
	qdbid := dax.QualifiedDatabaseID{OrganizationID: orgID, DatabaseID: dbID}

	// have to create a free worker before you can create a worker job worker
	fwSvc := sqldb.NewFreeWorkerService(nil)
	err = fwSvc.AddWorkers(tx, role, nodeAddr)
	require.NoError(t, err)

	err = wjSvc.CreateWorker(tx, role, qdbid, nodeAddr)
	require.NoError(t, err)

	// we create a qtid to prefix jobs so that we can then test the
	// "DeleteJobsForTable" method. Is it strange that a table is not
	// explicitly mentioned in the interface on the way in, but is in
	// the Delete method? Why yes, yes it is... thank you for asking. #TODO
	qtid := dax.QualifiedTableID{
		QualifiedDatabaseID: qdbid,
		Name:                tableName,
		ID:                  tableID,
	}
	job1 := dax.Job(qtid.Key() + "job1")
	job2 := dax.Job(qtid.Key() + "job2")
	job3 := dax.Job(qtid.Key() + "job3")

	fjSvc := sqldb.NewFreeJobService(nil)
	err = fjSvc.CreateJobs(tx, role, qdbid, job1, job2, job3)
	require.NoError(t, err)

	err = wjSvc.CreateJobs(tx, role, qdbid, nodeAddr, job1, job2)
	require.NoError(t, err)

	jobs, err := wjSvc.ListJobs(tx, role, qdbid, nodeAddr)
	require.NoError(t, err)
	require.ElementsMatch(t, dax.Jobs{job1, job2}, jobs)

	workerInfos, err := wjSvc.WorkersJobs(tx, role, qdbid)
	require.NoError(t, err)
	require.Equal(t, 1, len(workerInfos))
	require.ElementsMatch(t, []dax.Job{job1, job2}, workerInfos[0].Jobs)

	cnt, err := wjSvc.WorkerCount(tx, role, qdbid)
	require.NoError(t, err)
	require.Equal(t, 1, cnt)

	addrs, err := wjSvc.ListWorkers(tx, role, qdbid)
	require.NoError(t, err)
	require.ElementsMatch(t, dax.Addresses{nodeAddr}, addrs)

	err = wjSvc.CreateJobs(tx, role, qdbid, nodeAddr, job3)
	require.NoError(t, err)

	jcs, err := wjSvc.JobCounts(tx, role, qdbid, nodeAddr)
	require.NoError(t, err)
	require.Equal(t, 3, jcs[nodeAddr])

	err = wjSvc.DeleteJob(tx, role, qdbid, nodeAddr, job3)
	require.NoError(t, err)

	idiffs, err := wjSvc.DeleteJobsForTable(tx, role, qtid)
	require.NoError(t, err)
	workerDiffs := idiffs.Output()
	require.Equal(t, 1, len(workerDiffs))
	require.EqualValues(t, nodeAddr, workerDiffs[0].Address)
	require.Empty(t, workerDiffs[0].AddedJobs)
	require.ElementsMatch(t, []dax.Job{job1, job2}, workerDiffs[0].RemovedJobs)

	jobs, err = wjSvc.ListJobs(tx, role, qdbid, nodeAddr)
	require.NoError(t, err)
	require.Empty(t, jobs)

	dk := wjSvc.DatabaseForWorker(tx, nodeAddr)
	require.EqualValues(t, "db__orgid__blah", dk)

	err = wjSvc.DeleteWorker(tx, role, qdbid, nodeAddr)
	require.NoError(t, err)

	addrs, err = wjSvc.ListWorkers(tx, role, qdbid)
	require.NoError(t, err)
	require.Empty(t, addrs)
}
