package resource

import (
	"fmt"

	comM "github.com/pingcap/test-infra/common/model"
	reqM "github.com/pingcap/test-infra/common/model/tcms/request"
	respM "github.com/pingcap/test-infra/common/model/tcms/response"

	"github.com/pingcap/test-infra/tools/tcctl/pkg/config"
	"github.com/pingcap/test-infra/tools/tcctl/pkg/model"
	"github.com/pingcap/test-infra/tools/tcctl/pkg/util"
)

type TestPlan struct {
	serverConfig config.ApiServerConfig
	doc          model.RawContent
	Req          reqM.PlanCreateRequest
	id           comM.ID
	synced       bool
}

type PlanCreateFrontEnd struct {
	reqM.PlanCreateRequest
	Steps []comM.PipelineStep `json:"steps"`
}

func NewTestPlan(serverConfig config.ApiServerConfig, doc model.Resource) (*TestPlan, error) {
	t := &TestPlan{serverConfig: serverConfig, doc: doc.RawContent, id: doc.ID}
	err := t.doc.UnMarshal(&t.Req)
	if err != nil {
		return nil, err
	}
	return t, nil
}

func (t *TestPlan) Name() string {
	return t.Req.Name
}

func (t *TestPlan) SyncID() error {
	switch t.Req.Type {
	case "", comM.TestPlanKindGeneral:
		existed, err := PullPlan(t.serverConfig, t.Req.Name)
		if err != nil {
			return err
		}
		if existed != nil {
			t.id = existed.ID
		}
		t.synced = true
	case comM.TestPlanKindGithub, comM.TestPlanKindOneShot:
		// no need to sync, so always set synced
		t.synced = true
		return nil
	default:
		return fmt.Errorf("unknown plan type: %s", t.Req.Type)
	}
	return nil
}

func (t TestPlan) assertSynced() {
	if !t.synced {
		panic("resource not synced")
	}
}

func (t TestPlan) IsExist() (bool, error) {
	t.assertSynced()
	return t.id != 0, nil
}

func (t TestPlan) Create(dryrun bool) (interface{}, error) {
	t.assertSynced()
	return CreatePlan(t.serverConfig, &t.Req, dryrun)
}

func (t TestPlan) Update(dryrun bool) (interface{}, error) {
	t.assertSynced()
	return UpdatePlan(t.serverConfig, t.id, t.Req, dryrun)
}

func (t TestPlan) Delete() error {
	t.assertSynced()
	existed, err := t.IsExist()
	if err != nil {
		return err
	}
	if existed {
		return DeletePlan(t.serverConfig, t.id)
	}
	return nil
}

func PullPlan(config config.ApiServerConfig, name string) (*comM.TestPlan, error) {
	path := "api/v1/plans/autocomplete"
	var rsp = &respM.PlanListResponse{}
	_, err := util.Req().Host(config.TcmsHost).Bearer(config.Token).Path(path).Param(map[string]string{"query": name}).Get(rsp)
	if err != nil {
		return nil, err
	}
	for _, each := range rsp.Data {
		if each.Name == name {
			return each, nil
		}
	}
	return nil, nil
}

func CreatePlan(config config.ApiServerConfig, plan *reqM.PlanCreateRequest, dryrun bool) (*respM.PlanCreateResponse, error) {
	path := "api/v1/plans"
	var rsp = &respM.PlanCreateResponse{}
	_, err := util.Req().Host(config.TcmsHost).Bearer(config.Token).Path(path).DryRun(dryrun).Json(plan).Post(rsp)
	if err != nil {
		return nil, err
	}
	return rsp, nil
}

func CreateBatchPlans(config config.ApiServerConfig, batchPlans reqM.TriggerCreateRequest, dryrun bool) (*comM.TestTrigger, error) {
	path := "api/v1/triggers"
	var rsp = &comM.TestTrigger{}
	_, err := util.Req().Host(config.TcmsHost).Bearer(config.Token).Path(path).DryRun(dryrun).Json(batchPlans).Post(rsp)
	if err != nil {
		return nil, err
	}
	return rsp, nil
}

func UpdatePlan(config config.ApiServerConfig, id comM.ID, plan reqM.PlanCreateRequest, dryrun bool) (*comM.TestPlan, error) {
	path := fmt.Sprintf("api/v1/plans/%d", id)
	var rsp = &comM.TestPlan{}
	_, err := util.Req().Host(config.TcmsHost).Bearer(config.Token).Path(path).DryRun(dryrun).Json(plan).Put(rsp)
	if err != nil {
		return nil, err
	}
	return rsp, nil
}

func DeletePlan(config config.ApiServerConfig, id comM.ID) error {
	path := fmt.Sprintf("api/v1/plans/%d", id)
	rsp := new(map[string]interface{})
	_, err := util.Req().Host(config.TcmsHost).Bearer(config.Token).Path(path).Delete(rsp)
	if err != nil {
		return err
	}
	return nil
}
