package svctaskrunhandler

import (
	"context"
	"fmt"

	"github.com/jmoiron/sqlx"
	taskM "github.com/pingcap/test-infra/common/model/tcms/taskruns"
)

type SvcRunHandler interface {
	OnCreate(ctx context.Context, obj *taskM.SvcTaskRun) (*taskM.SvcTaskRun, error)
	OnSync(ctx context.Context, obj *taskM.SvcTaskRun) (*taskM.SvcTaskRun, error)
}

type SvcTaskRefService interface {
	GetByName(ctx context.Context, name string, opts taskM.GetSvcTaskOpts) (*taskM.SvcTask, error)
}

type mux struct {
	client      SvcTaskRefService
	planHandler SvcRunHandler
	mockHandler SvcRunHandler
}

func MarkError(obj *taskM.SvcTaskRun, err error) {
	obj.Status.Completed = true
	obj.Status.Message = err.Error()
}

func (m mux) OnCreate(ctx context.Context, obj *taskM.SvcTaskRun) (*taskM.SvcTaskRun, error) {
	h, err := m.GetHandler(ctx, *obj)
	if err != nil {
		return nil, err
	}
	if h == nil {
		MarkError(obj, fmt.Errorf("unknown task type"))
	} else {
		ret, err := h.OnCreate(ctx, obj)
		if err != nil {
			if IsUnrecoverable(err) {
				MarkError(obj, err)
			}
			return nil, nil
		}
		return ret, nil
	}
	return obj, nil
}

func (m mux) OnSync(ctx context.Context, obj *taskM.SvcTaskRun) (*taskM.SvcTaskRun, error) {
	if obj.Status.Completed == true {
		return obj, nil
	}
	h, err := m.GetHandler(ctx, *obj)
	if err != nil {
		return nil, err
	}
	if h == nil {
		MarkError(obj, fmt.Errorf("unknown task type"))
	} else {
		obj, err := h.OnSync(ctx, obj)
		if err != nil {
			if err != nil {
				if IsUnrecoverable(err) {
					MarkError(obj, err)
				} else {
					return nil, err
				}
			}
		}
	}
	return obj, nil
}

func (m mux) GetHandler(ctx context.Context, obj taskM.SvcTaskRun) (SvcRunHandler, error) {
	svcType := obj.Status.Implementation.GetType()
	if svcType == taskM.TaskTypeUnknown {
		task, err := m.client.GetByName(ctx, obj.Spec.SvcTaskName, taskM.GetSvcTaskOpts{})
		if err != nil {
			return nil, Unrecoverable(fmt.Errorf("unknown task name"))
		}
		svcType = task.Implementation.GetType()
	}
	switch svcType {
	case taskM.TaskTypePlan:
		return m.planHandler, nil
	case taskM.TaskTypeMock:
		return m.mockHandler, nil
	default:
		return nil, Unrecoverable(fmt.Errorf("unknown service type"))
	}
}

func NewMuxHandler(taskClient SvcTaskRefService, planExecRepo PlanExecSvr, baseURL string) SvcRunHandler {
	return mux{client: taskClient,
		mockHandler: mockHandler{taskClient: taskClient},
		planHandler: NewPlanHandler(taskClient, planExecRepo, baseURL),
	}
}

const sqlxTxKey = "*sqlx.Tx"

func WrapSqlTx(ctx context.Context, tx *sqlx.Tx) context.Context {
	return context.WithValue(ctx, sqlxTxKey, tx)
}

func GetSqlxTx(ctx context.Context) *sqlx.Tx {
	v := ctx.Value(sqlxTxKey)
	if v == nil {
		return nil
	}
	return v.(*sqlx.Tx)
}
