// Copyright 2019 Sorint.lab
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
// See the License for the specific language governing permissions and
// limitations under the License.

package command

import (
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/sorintlab/agola/internal/services/runservice/types"
	"github.com/sorintlab/agola/internal/util"
)

func TestRecreateRun(t *testing.T) {

	inuuid := func(s string) string {
		u := &util.TestPrefixUUIDGenerator{Prefix: "in"}
		return u.New(s).String()
	}

	outuuid := func(s string) string {
		u := &util.TestPrefixUUIDGenerator{Prefix: "out"}
		return u.New(s).String()
	}

	// a global run config for all tests
	rc := &types.RunConfig{
		ID: inuuid("old"),
		Tasks: map[string]*types.RunConfigTask{
			inuuid("task01"): &types.RunConfigTask{
				ID:      inuuid("task01"),
				Name:    "task01",
				Depends: map[string]*types.RunConfigTaskDepend{},
				Runtime: &types.Runtime{Type: types.RuntimeType("pod"),
					Containers: []*types.Container{{Image: "image01"}},
				},
				Environment: map[string]string{},
				Steps:       []interface{}{},
				Skip:        false,
			},
			inuuid("task02"): &types.RunConfigTask{
				ID:   inuuid("task02"),
				Name: "task02",
				Depends: map[string]*types.RunConfigTaskDepend{
					inuuid("task01"): &types.RunConfigTaskDepend{TaskID: inuuid("task01"), Conditions: []types.RunConfigTaskDependCondition{types.RunConfigTaskDependConditionOnSuccess}},
				},
				Runtime: &types.Runtime{Type: types.RuntimeType("pod"),
					Containers: []*types.Container{{Image: "image01"}},
				},
				Environment: map[string]string{},
				Steps:       []interface{}{},
				Skip:        false,
			},
			inuuid("task03"): &types.RunConfigTask{
				ID:      inuuid("task03"),
				Name:    "task03",
				Depends: map[string]*types.RunConfigTaskDepend{},
				Runtime: &types.Runtime{Type: types.RuntimeType("pod"),
					Containers: []*types.Container{{Image: "image01"}},
				},
				Environment: map[string]string{},
				Steps:       []interface{}{},
				Skip:        false,
			},
			inuuid("task04"): &types.RunConfigTask{
				ID:   inuuid("task04"),
				Name: "task04",
				Runtime: &types.Runtime{Type: types.RuntimeType("pod"),
					Containers: []*types.Container{{Image: "image01"}},
				},
				Environment: map[string]string{},
				Steps:       []interface{}{},
				Skip:        false,
			},
			inuuid("task05"): &types.RunConfigTask{
				ID:   inuuid("task05"),
				Name: "task05",
				Depends: map[string]*types.RunConfigTaskDepend{
					inuuid("task03"): &types.RunConfigTaskDepend{TaskID: inuuid("task03"), Conditions: []types.RunConfigTaskDependCondition{types.RunConfigTaskDependConditionOnSuccess}},
					inuuid("task04"): &types.RunConfigTaskDepend{TaskID: inuuid("task04"), Conditions: []types.RunConfigTaskDependCondition{types.RunConfigTaskDependConditionOnSuccess}},
				},
				Runtime: &types.Runtime{Type: types.RuntimeType("pod"),
					Containers: []*types.Container{{Image: "image01"}},
				},
				Environment: map[string]string{},
				Steps:       []interface{}{},
				Skip:        false,
			},
		},
	}

	outrc := &types.RunConfig{
		ID: outuuid("new"),
		Tasks: map[string]*types.RunConfigTask{
			outuuid("task01"): &types.RunConfigTask{
				ID:      outuuid("task01"),
				Name:    "task01",
				Depends: map[string]*types.RunConfigTaskDepend{},
				Runtime: &types.Runtime{Type: types.RuntimeType("pod"),
					Containers: []*types.Container{{Image: "image01"}},
				},
				Environment: map[string]string{},
				Steps:       []interface{}{},
				Skip:        false,
			},
			outuuid("task02"): &types.RunConfigTask{
				ID:   outuuid("task02"),
				Name: "task02",
				Depends: map[string]*types.RunConfigTaskDepend{
					outuuid("task01"): &types.RunConfigTaskDepend{TaskID: outuuid("task01"), Conditions: []types.RunConfigTaskDependCondition{types.RunConfigTaskDependConditionOnSuccess}},
				},
				Runtime: &types.Runtime{Type: types.RuntimeType("pod"),
					Containers: []*types.Container{{Image: "image01"}},
				},
				Environment: map[string]string{},
				Steps:       []interface{}{},
				Skip:        false,
			},
			outuuid("task03"): &types.RunConfigTask{
				ID:      outuuid("task03"),
				Name:    "task03",
				Depends: map[string]*types.RunConfigTaskDepend{},
				Runtime: &types.Runtime{Type: types.RuntimeType("pod"),
					Containers: []*types.Container{{Image: "image01"}},
				},
				Environment: map[string]string{},
				Steps:       []interface{}{},
				Skip:        false,
			},
			outuuid("task04"): &types.RunConfigTask{
				ID:   outuuid("task04"),
				Name: "task04",
				Runtime: &types.Runtime{Type: types.RuntimeType("pod"),
					Containers: []*types.Container{{Image: "image01"}},
				},
				Environment: map[string]string{},
				Steps:       []interface{}{},
				Skip:        false,
			},
			outuuid("task05"): &types.RunConfigTask{
				ID:   outuuid("task05"),
				Name: "task05",
				Depends: map[string]*types.RunConfigTaskDepend{
					outuuid("task03"): &types.RunConfigTaskDepend{TaskID: outuuid("task03"), Conditions: []types.RunConfigTaskDependCondition{types.RunConfigTaskDependConditionOnSuccess}},
					outuuid("task04"): &types.RunConfigTaskDepend{TaskID: outuuid("task04"), Conditions: []types.RunConfigTaskDependCondition{types.RunConfigTaskDependConditionOnSuccess}},
				},
				Runtime: &types.Runtime{Type: types.RuntimeType("pod"),
					Containers: []*types.Container{{Image: "image01"}},
				},
				Environment: map[string]string{},
				Steps:       []interface{}{},
				Skip:        false,
			},
		},
	}

	// initial run that matched the runconfig, all tasks are not started or skipped
	// (if the runconfig task as Skip == true). This must match the status
	// generated by command.genRun()
	run := genRun(rc)
	outrun := genRun(outrc)

	tests := []struct {
		name  string
		rc    *types.RunConfig
		r     *types.Run
		req   *RunCreateRequest
		outrc *types.RunConfig
		outr  *types.Run
		err   error
	}{
		{
			name:  "test recreate run from start with all not start tasks",
			rc:    rc.DeepCopy(),
			r:     run.DeepCopy(),
			outrc: outrc.DeepCopy(),
			outr:  outrun.DeepCopy(),
			req:   &RunCreateRequest{FromStart: true},
		},
		{
			name:  "test recreate run from failed tasks with all not start tasks",
			rc:    rc.DeepCopy(),
			r:     run.DeepCopy(),
			outrc: outrc.DeepCopy(),
			outr:  outrun.DeepCopy(),
			req:   &RunCreateRequest{FromStart: false},
		},
		{
			name: "test recreate run from start tasks with task01 failed and child task02 successful (should recreate all tasks)",
			rc:   rc.DeepCopy(),
			r: func() *types.Run {
				run := run.DeepCopy()
				run.Tasks[inuuid("task01")].Status = types.RunTaskStatusFailed
				run.Tasks[inuuid("task02")].Status = types.RunTaskStatusSuccess
				run.Tasks[inuuid("task03")].Status = types.RunTaskStatusSuccess
				run.Tasks[inuuid("task04")].Status = types.RunTaskStatusSuccess
				run.Tasks[inuuid("task05")].Status = types.RunTaskStatusSuccess
				return run
			}(),
			outrc: outrc.DeepCopy(),
			outr:  outrun.DeepCopy(),
			req:   &RunCreateRequest{FromStart: true},
		},
		{
			name: "test recreate run from failed tasks with task01 failed and child task02 successful (should recreate task01 and task02)",
			rc:   rc.DeepCopy(),
			r: func() *types.Run {
				run := run.DeepCopy()
				run.Tasks[inuuid("task01")].Status = types.RunTaskStatusFailed
				run.Tasks[inuuid("task02")].Status = types.RunTaskStatusSuccess
				run.Tasks[inuuid("task03")].Status = types.RunTaskStatusSuccess
				run.Tasks[inuuid("task04")].Status = types.RunTaskStatusSuccess
				run.Tasks[inuuid("task05")].Status = types.RunTaskStatusSuccess
				return run
			}(),
			// task01 and task02 recreated
			outrc: func() *types.RunConfig {
				rc := rc.DeepCopy()
				outrc := outrc.DeepCopy()

				nrc := rc.DeepCopy()
				nrc.ID = outuuid("new")
				nrc.Tasks = map[string]*types.RunConfigTask{
					outuuid("task01"): outrc.Tasks[outuuid("task01")],
					outuuid("task02"): outrc.Tasks[outuuid("task02")],
					inuuid("task03"):  rc.Tasks[inuuid("task03")],
					inuuid("task04"):  rc.Tasks[inuuid("task04")],
					inuuid("task05"):  rc.Tasks[inuuid("task05")],
				}
				return nrc
			}(),
			// task01 and task02 recreated and status reset to NotStarted
			outr: func() *types.Run {
				run := run.DeepCopy()
				outrun := outrun.DeepCopy()
				nrun := run.DeepCopy()
				nrun.ID = outuuid("new")
				nrun.Tasks = map[string]*types.RunTask{
					outuuid("task01"): outrun.Tasks[outuuid("task01")],
					outuuid("task02"): outrun.Tasks[outuuid("task02")],
					inuuid("task03"):  run.Tasks[inuuid("task03")],
					inuuid("task04"):  run.Tasks[inuuid("task04")],
					inuuid("task05"):  run.Tasks[inuuid("task05")],
				}

				nrun.Tasks[inuuid("task03")].Status = types.RunTaskStatusSuccess
				nrun.Tasks[inuuid("task04")].Status = types.RunTaskStatusSuccess
				nrun.Tasks[inuuid("task05")].Status = types.RunTaskStatusSuccess

				return nrun
			}(),
			req: &RunCreateRequest{FromStart: false},
		},
	}

	u := &util.TestPrefixUUIDGenerator{Prefix: "out"}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			newID := outuuid("new")
			rb := recreateRun(u, tt.r, tt.rc, newID, tt.req)
			if diff := cmp.Diff(tt.outrc, rb.Rc); diff != "" {
				t.Error(diff)
			}
			if diff := cmp.Diff(tt.outr, rb.Run); diff != "" {
				t.Error(diff)
			}
		})
	}
}