package rpc

import (
	"context"
	"time"

	"gitlink.org.cn/cloudream/common/consts/errorcode"
	"gitlink.org.cn/cloudream/common/pkgs/future"
	"gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec"
	"gitlink.org.cn/cloudream/common/pkgs/logger"
	"gitlink.org.cn/cloudream/common/utils/io2"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/rpc"
	hubrpc "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/rpc/hub"
)

func (s *Service) ExecuteIOPlan(ctx context.Context, req *hubrpc.ExecuteIOPlan) (*hubrpc.ExecuteIOPlanResp, *rpc.CodeError) {
	log := logger.WithField("PlanID", req.Plan.ID)
	log.Infof("begin execute io plan")

	sw := exec.NewExecutor(req.Plan)

	s.swWorker.Add(sw)
	defer s.swWorker.Remove(sw)

	execCtx := exec.NewWithContext(ctx)
	exec.SetValueByType(execCtx, s.stgPool)
	_, err := sw.Run(execCtx)
	if err != nil {
		log.Warnf("running io plan: %v", err)
		return nil, rpc.Failed(errorcode.OperationFailed, "%v", err)
	}

	log.Infof("plan finished")
	return &hubrpc.ExecuteIOPlanResp{}, nil
}

func (s *Service) SendIOStream(ctx context.Context, req *hubrpc.SendIOStream) (*hubrpc.SendIOStreamResp, *rpc.CodeError) {
	logger.
		WithField("PlanID", req.PlanID).
		WithField("VarID", req.VarID).
		Debugf("stream input")

	// 同一批Plan中每个节点的Plan的启动时间有先后，但最多不应该超过30秒
	ctx, cancel := context.WithTimeout(ctx, time.Second*30)
	defer cancel()

	sw := s.swWorker.FindByIDContexted(ctx, exec.PlanID(req.PlanID))
	if sw == nil {
		return nil, rpc.Failed(errorcode.DataNotFound, "plan not found")
	}

	fut := future.NewSetVoid()

	varID := exec.VarID(req.VarID)
	sw.PutVar(varID, &exec.StreamValue{Stream: io2.DelegateReadCloser(req.Stream, func() error {
		fut.SetVoid()
		return nil
	})})

	err := fut.Wait(ctx)
	if err != nil {
		return nil, rpc.Failed(errorcode.OperationFailed, "%v", err)
	}

	return &hubrpc.SendIOStreamResp{}, nil
}

func (s *Service) GetIOStream(ctx context.Context, req *hubrpc.GetIOStream) (*hubrpc.GetIOStreamResp, *rpc.CodeError) {
	logger.
		WithField("PlanID", req.PlanID).
		WithField("VarID", req.VarID).
		Debugf("stream output")

	// 同上
	ctx, cancel := context.WithTimeout(ctx, time.Second*30)
	defer cancel()

	sw := s.swWorker.FindByIDContexted(ctx, exec.PlanID(req.PlanID))
	if sw == nil {
		return nil, rpc.Failed(errorcode.DataNotFound, "plan not found")
	}

	sw.PutVar(req.SignalID, req.Signal)

	strVar, err := exec.BindVar[*exec.StreamValue](sw, ctx, exec.VarID(req.VarID))
	if err != nil {
		return nil, rpc.Failed(errorcode.OperationFailed, "bind var: %v", err)
	}

	return &hubrpc.GetIOStreamResp{
		Stream: strVar.Stream,
	}, nil
}

func (s *Service) SendIOVar(ctx context.Context, req *hubrpc.SendIOVar) (*hubrpc.SendIOVarResp, *rpc.CodeError) {
	ctx, cancel := context.WithTimeout(ctx, time.Second*30)
	defer cancel()

	sw := s.swWorker.FindByIDContexted(ctx, exec.PlanID(req.PlanID))
	if sw == nil {
		return nil, rpc.Failed(errorcode.DataNotFound, "plan not found")
	}

	sw.PutVar(req.VarID, req.Value)
	return &hubrpc.SendIOVarResp{}, nil
}

func (s *Service) GetIOVar(ctx context.Context, req *hubrpc.GetIOVar) (*hubrpc.GetIOVarResp, *rpc.CodeError) {
	ctx2, cancel := context.WithTimeout(ctx, time.Second*30)
	defer cancel()

	sw := s.swWorker.FindByIDContexted(ctx2, exec.PlanID(req.PlanID))
	if sw == nil {
		return nil, rpc.Failed(errorcode.DataNotFound, "plan not found")
	}

	sw.PutVar(req.SignalID, req.Signal)

	v, err := sw.BindVar(ctx, exec.VarID(req.VarID))
	if err != nil {
		return nil, rpc.Failed(errorcode.OperationFailed, "bind var: %v", err)
	}

	return &hubrpc.GetIOVarResp{
		Value: v,
	}, nil
}
