// Package executorclient provides a client for interacting with the executor service.
package executorclient

import (
	"context"
	"fmt"

	execv1pb "github.com/tidbcloud/cluster-service-ng/api/executor/v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/executor"
)

type Client struct {
	scope      string
	execClient execv1pb.ExecutorServiceClient
}

func New(
	scope string,
	execClient execv1pb.ExecutorServiceClient,
) (*Client, error) {
	client := new(Client)
	client.scope = scope
	client.execClient = execClient
	return client, nil
}

func (client *Client) Abort(ctx context.Context, execId string) error {
	in := new(execv1pb.AbortRequest)
	in.Scope = client.scope
	in.ExecId = execId
	_, err := client.execClient.Abort(ctx, in)
	if err != nil {
		return err
	}
	return nil
}

func (client *Client) Submit(ctx context.Context, planName string, args actions.ActionArgs, execId string) error {
	in := new(execv1pb.SubmitRequest)
	in.Scope = client.scope
	in.PlanName = planName
	in.ExecId = execId
	argsPB, err := actions.BuildPBFromActionArgs(args)
	if err != nil {
		return err
	}
	in.Args = argsPB
	_, err = client.execClient.Submit(ctx, in)
	if err != nil {
		return err
	}
	return nil
}

func (client *Client) QueryRunnings(ctx context.Context, planName string, matchFilter executor.MatchFilter) (executor.ExecTaskIdStatus, error) {
	in := new(execv1pb.QueryRunningsRequest)
	in.Scope = client.scope
	in.PlanName = planName

	// Pass match filter to server for server-side filtering
	if len(matchFilter) > 0 {
		in.MatchArgs = make(map[string]*execv1pb.MatchArgValues)
		for key, values := range matchFilter {
			in.MatchArgs[key] = &execv1pb.MatchArgValues{Values: values}
		}
	}

	out, err := client.execClient.QueryRunnings(ctx, in)
	if err != nil {
		return nil, err
	}

	matched := make(executor.ExecTaskIdStatus)
	for k, v := range out.GetExecTaskIdStatus() {
		taskStatusEx, err := buildExecTaskStatusExFromPB(v)
		if err != nil {
			return nil, err
		}
		matched[k] = taskStatusEx
	}
	return matched, nil
}

func buildActionArgsFromPB(in map[string]*execv1pb.ActionArg) (actions.ActionArgs, error) {
	out := make(actions.ActionArgs)
	for k, v := range in {
		switch t := v.GetArg().(type) {
		case *execv1pb.ActionArg_ActionArgInt:
			out[k] = actions.ActionArgInt(int(v.GetActionArgInt()))
		case *execv1pb.ActionArg_ActionArgString:
			out[k] = actions.ActionArgString(v.GetActionArgString())
		default:
			return nil, fmt.Errorf("unsupported action args pb type: %T", t)
		}
	}
	return out, nil
}

func buildExecTaskStatusExFromPB(in *execv1pb.ExecTaskStatusEx) (*executor.ExecTaskStatusEx, error) {
	out := new(executor.ExecTaskStatusEx)
	out.State = executor.ExecTaskStateType(in.GetState())
	out.ExecTask = new(executor.ExecutorTask)
	out.ExecTask.ExecId = in.ExecTask.GetExecId()
	out.ExecTask.RootName = in.ExecTask.GetRootName()
	args, err := buildActionArgsFromPB(in.ExecTask.GetArgs())
	if err != nil {
		return nil, err
	}
	out.ExecTask.Args = args
	// NOTE(shenjun): no need to fill LogicPlan and PhysicPlan since caller doesn't care these two fields
	return out, nil
}
