package job_test

import (
	"context"
	"strconv"
	"testing"

	batchapi "k8s.io/api/batch/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/juju/errors"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/auto-deploy/internal/constants"
	"github.com/tidbcloud/auto-deploy/internal/job"
)

func TestGetJobLogs(t *testing.T) {
	config, err := clientcmd.BuildConfigFromFlags("", "/workspaces/auto-deploy/kubeconfig")
	if err != nil {
		panic(err)
	}
	clientset, _ := kubernetes.NewForConfig(config)
	job.GetJobLogs(context.Background(), clientset, "iam")
}

func TestGetDependMap(t *testing.T) {
	jobs := map[string]*job.Job{
		"depend-0-1": makeJob("depend-0-1", ""),
		"depend-0-2": makeJob("depend-0-2", ""),
		"depend-0-3": makeJob("depend-0-3", ""),
		"depend-1-1": makeJob("depend-1-1", "depend-0-1"),
		"depend-1-2": makeJob("depend-1-2", "depend-0-2"),
		"depend-2-1": makeJob("depend-2-1", "depend-0-1, depend-0-2"),
		"depend-2-2": makeJob("depend-2-2", "depend-0-1, depend-0-3"),
		"depend-3-1": makeJob("depend-3-1", "depend-0-1, depend-0-2, depend-0-3"),
		"depend-3-2": makeJob("depend-3-2", "depend-1-1, depend-0-3"),
		"depend-3-3": makeJob("depend-3-3", "depend-1-2, depend-0-3"),
		"depend-4-1": makeJob("depend-4-1", "depend-1-1, depend-1-2"),
	}
	err := job.FlatJobDepend(jobs)
	assert.NoError(t, err)
	for jobName, job := range jobs {
		c, _ := strconv.Atoi(jobName[7:8])
		assert.Equal(t, c, len(job.Depends))
	}

	jobs = map[string]*job.Job{
		"depend-1-1": makeJob("depend-1-1", "depend-1-1"),
	}
	err = job.FlatJobDepend(jobs)
	assert.True(t, errors.Is(err, errors.NotValid))
	assert.Equal(t, "Job depend-1-1 depends itself not valid", err.Error())

	jobs = map[string]*job.Job{
		"depend-1-1": makeJob("depend-1-1", "depend-1-2"),
		"depend-1-2": makeJob("depend-1-2", "depend-1-1"),
	}
	err = job.FlatJobDepend(jobs)
	assert.True(t, errors.Is(err, errors.NotValid))
	assert.Contains(t, err.Error(), "depends itself circle")

	jobs = map[string]*job.Job{
		"depend-1-1": makeJob("depend-1-1", "depend-1-2"),
		"depend-1-2": makeJob("depend-1-2", "depend-1-3"),
		"depend-1-3": makeJob("depend-1-3", "depend-1-4"),
		"depend-1-4": makeJob("depend-1-4", "depend-1-5"),
		"depend-1-5": makeJob("depend-1-5", ""),
	}
	err = job.FlatJobDepend(jobs)
	assert.NoError(t, err)
	assert.Equal(t, 4, len(jobs["depend-1-1"].Depends))
}

func makeJob(name, dependList string) *job.Job {
	batchJob := &batchapi.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
			Annotations: map[string]string{
				constants.JOB_DEPENDS_ON_ANNOTATION: dependList,
			},
		},
	}
	return &job.Job{
		BatchJob: batchJob,
		Depends:  job.GetJobDepends(batchJob),
	}
}

func TestGetFinalizerJobDepend(t *testing.T) {
	jobs := map[string]*job.Job{}
	finalizers := map[string]*job.Job{}
	makeJobWithFinalizer("depend-0-1", "finalizer-0-1", "", jobs, finalizers)
	makeJobWithFinalizer("depend-0-2", "finalizer-0-2", "", jobs, finalizers)
	jobs["depend-0-3"] = makeJob("depend-0-3", "")
	makeJobWithFinalizer("depend-1-1", "finalizer-1-1", "depend-0-1", jobs, finalizers)
	makeJobWithFinalizer("depend-1-2", "finalizer-1-2", "depend-0-2", jobs, finalizers)
	makeJobWithFinalizer("depend-1-3", "finalizer-1-3", "depend-0-3", jobs, finalizers)
	makeJobWithFinalizer("depend-2-1", "finalizer-2-1", "depend-0-1, depend-0-2", jobs, finalizers)
	makeJobWithFinalizer("depend-2-2", "finalizer-2-2", "depend-0-1, depend-0-3", jobs, finalizers)
	makeJobWithFinalizer("depend-3-1", "finalizer-3-1", "depend-0-1, depend-0-2, depend-0-3", jobs, finalizers)
	makeJobWithFinalizer("depend-3-2", "finalizer-3-2", "depend-1-1, depend-0-3", jobs, finalizers)
	jobs["depend-3-3"] = makeJob("depend-3-3", "depend-1-2, depend-0-3")
	makeJobWithFinalizer("depend-3-4", "finalizer-3-4", "depend-2-2", jobs, finalizers)
	// makeJobWithFinalizer("depend-3-5", "finalizer-3-5", "depend-1-2, depend-0-3", jobs, finalizers)
	makeJobWithFinalizer("depend-4-1", "finalizer-4-1", "depend-1-1, depend-1-2", jobs, finalizers)
	makeJobWithFinalizer("depend-4-2", "finalizer-4-2", "depend-3-3", jobs, finalizers)
	makeJobWithFinalizer("depend-4-3", "finalizer-4-3", "depend-2-1, depend-0-3", jobs, finalizers)
	err := job.FlatJobDepend(jobs)
	assert.NoError(t, err)
	err = job.GetFinalizerJobDepend(jobs, finalizers)
	assert.NoError(t, err)
}

func TestTwoJobHaveSameFinalizer(t *testing.T) {
	jobs := map[string]*job.Job{}
	finalizers := map[string]*job.Job{}
	makeJobWithFinalizer("depend-0-1", "same-finalizer", "", jobs, finalizers)
	makeJobWithFinalizer("depend-0-2", "same-finalizer", "", jobs, finalizers)
	err := job.FlatJobDepend(jobs)
	assert.NoError(t, err)
	err = job.GetFinalizerJobDepend(jobs, finalizers)
	assert.ErrorIs(t, err, errors.NotValid)
	assert.Equal(t, "Two jobs (depend-0-1, depend-0-2) have same finalizer same-finalizer not valid", err.Error())
}

func TestJobHasInvalidFinalizer(t *testing.T) {
	jobs := map[string]*job.Job{}
	finalizers := map[string]*job.Job{}
	makeJobWithFinalizer("depend-0-1", "finalizer-0-1", "", jobs, finalizers)
	jobs["depend-0-1"].Finalizer = "invalid"
	err := job.FlatJobDepend(jobs)
	assert.NoError(t, err)
	err = job.GetFinalizerJobDepend(jobs, finalizers)
	assert.ErrorIs(t, err, errors.NotFound)
	assert.Equal(t, "Job depend-0-1 finalizer invalid not found", err.Error())
}

func TestFinalizerJobHasNoJob(t *testing.T) {
	jobs := map[string]*job.Job{}
	finalizers := map[string]*job.Job{}
	makeJobWithFinalizer("depend-0-1", "finalizer-0-1", "", jobs, finalizers)
	jobs["depend-0-1"].Finalizer = ""
	err := job.FlatJobDepend(jobs)
	assert.NoError(t, err)
	err = job.GetFinalizerJobDepend(jobs, finalizers)
	assert.ErrorIs(t, err, errors.NotFound)
	assert.Equal(t, "Finalizer job finalizer-0-1 associated job not found", err.Error())
}

func makeJobWithFinalizer(name, finalizer, dependList string, jobs, finalizers map[string]*job.Job) {
	batchJob := &batchapi.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
			Annotations: map[string]string{
				constants.JOB_DEPENDS_ON_ANNOTATION: dependList,
				constants.JOB_FINALIZER_ANNOTATION:  finalizer,
			},
		},
	}

	finalizerBatchJob := &batchapi.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name:        finalizer,
			Annotations: map[string]string{},
		},
	}

	jobs[name] = &job.Job{
		BatchJob:  batchJob,
		Finalizer: finalizer,
		Depends:   job.GetJobDepends(batchJob),
	}
	finalizers[finalizer] = &job.Job{
		BatchJob: finalizerBatchJob,
		Depends:  []string{},
	}
}
