package schedule

import (
	"strconv"

	"github.com/pingcap/test-infra/services/tps/pkg/common"

	wf "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1"
	"github.com/pkg/errors"

	. "github.com/pingcap/test-infra/common/model"
)

type CompileContext interface {
	GenTemplateName(prefix string) string
	AddTemplate(tmpl *wf.Template)
	ListTemplates() []wf.Template

	RootPlanExecID() ID
	NextCaseExecID(step *PipelineStep) (ID, error)
	CaseExecIDs() []ID
	CaseSpec(id ID) (*TestCase, error)
	PlanSpec(id ID) (*TestPlan, error)
	NextUniqueInt() int
}

func NewContext(tcms common.TCMS, plans []TestPlan, root ID) CompileContext {
	cc := NewMockContext(nil, plans, root).(*baseContext)
	return &tcmsContext{cc, tcms}
}

func NewMockContext(cases []TestCase, plans []TestPlan, root ID) CompileContext {
	cc := &baseContext{
		root:  root,
		cases: make(map[ID]*TestCase, len(cases)),
		plans: make(map[ID]*TestPlan, len(plans)),
	}
	for i := range cases {
		cc.cases[cases[i].ID] = &cases[i]
	}
	for i := range plans {
		cc.plans[plans[i].ID] = &plans[i]
	}
	return cc
}

type tcmsContext struct {
	*baseContext
	tcms common.TCMS
}

func (cc *tcmsContext) NextCaseExecID(step *PipelineStep) (ID, error) {
	exec := TestCaseExecution{
		CaseID:         step.CaseID,
		RootPlanExecID: cc.root,
		Status:         ExecutionStatusPending,
		StepName:       step.Name,
		StepSeq:        step.Seq,
	}
	err := cc.tcms.PutCaseExecution(&exec)
	if err != nil {
		return 0, err
	}
	cc.execs = append(cc.execs, exec.ID)
	return exec.ID, nil
}

func (cc *tcmsContext) CaseSpec(id ID) (*TestCase, error) {
	if tc, ok := cc.cases[id]; ok {
		return tc, nil
	}
	tc, err := cc.tcms.GetCase(id)
	if err != nil {
		return nil, err
	}
	cc.cases[id] = tc
	return tc, err
}

func (cc *tcmsContext) PlanSpec(id ID) (*TestPlan, error) {
	if tp, ok := cc.plans[id]; ok {
		return tp, nil
	}
	tp, err := cc.tcms.GetPlanByPlanExecution(cc.RootPlanExecID())
	if err != nil {
		return nil, err
	}
	cc.plans[id] = tp
	return tp, err
}

type baseContext struct {
	seq      uint64
	root     ID
	tmpls    []*wf.Template
	cases    map[ID]*TestCase
	plans    map[ID]*TestPlan
	execs    []ID
	sequence int

	endpoints map[common.EndpointType]string
}

func (cc *baseContext) GenTemplateName(prefix string) string {
	cc.seq += 1
	return prefix + strconv.FormatUint(cc.seq, 10)
}

func (cc *baseContext) AddTemplate(tmpl *wf.Template) {
	cc.tmpls = append(cc.tmpls, tmpl)
}

func (cc *baseContext) ListTemplates() []wf.Template {
	lst := make([]wf.Template, len(cc.tmpls))
	for i, tmpl := range cc.tmpls {
		lst[i] = *tmpl
	}
	return lst
}

func (cc *baseContext) RootPlanExecID() ID {
	return cc.root
}

func (cc *baseContext) NextCaseExecID(step *PipelineStep) (ID, error) {
	cc.seq += 1
	cc.execs = append(cc.execs, ID(cc.seq))
	return ID(cc.seq), nil
}

func (cc *baseContext) NextUniqueInt() int {
	cc.sequence++
	return cc.sequence
}

func (cc *baseContext) CaseExecIDs() []ID {
	return cc.execs
}

func (cc *baseContext) CaseSpec(id ID) (*TestCase, error) {
	if cc.cases == nil || cc.cases[id] == nil {
		return nil, errors.New("no such case")
	}
	return cc.cases[id], nil
}

func (cc *baseContext) PlanSpec(id ID) (*TestPlan, error) {
	if cc.plans == nil || cc.plans[id] == nil {
		return nil, errors.New("no such plan")
	}
	return cc.plans[id], nil
}
