// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package nomad

import (
	"fmt"
	"time"

	"github.com/armon/go-metrics"
	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-memdb"

	"github.com/hashicorp/nomad/acl"
	"github.com/hashicorp/nomad/nomad/structs"
)

// Periodic endpoint is used for periodic job interactions
type Periodic struct {
	srv    *Server
	ctx    *RPCContext
	logger hclog.Logger
}

func NewPeriodicEndpoint(srv *Server, ctx *RPCContext) *Periodic {
	return &Periodic{srv: srv, ctx: ctx, logger: srv.logger.Named("periodic")}
}

// Force is used to force a new instance of a periodic job
func (p *Periodic) Force(args *structs.PeriodicForceRequest, reply *structs.PeriodicForceResponse) error {

	authErr := p.srv.Authenticate(p.ctx, args)
	if done, err := p.srv.forward("Periodic.Force", args, args, reply); done {
		return err
	}
	p.srv.MeasureRPCRate("periodic", structs.RateMetricWrite, args)
	if authErr != nil {
		return structs.ErrPermissionDenied
	}
	defer metrics.MeasureSince([]string{"nomad", "periodic", "force"}, time.Now())

	// Check for write-job permissions
	if aclObj, err := p.srv.ResolveACL(args); err != nil {
		return err
	} else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityDispatchJob) && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilitySubmitJob) {
		return structs.ErrPermissionDenied
	}

	// Validate the arguments
	if args.JobID == "" {
		return fmt.Errorf("missing job ID for evaluation")
	}

	// Lookup the job
	snap, err := p.srv.fsm.State().Snapshot()
	if err != nil {
		return err
	}

	ws := memdb.NewWatchSet()
	job, err := snap.JobByID(ws, args.RequestNamespace(), args.JobID)
	if err != nil {
		return err
	}
	if job == nil {
		return fmt.Errorf("job not found")
	}

	if !job.IsPeriodic() {
		return fmt.Errorf("can't force launch non-periodic job")
	}

	// Force run the job.
	eval, err := p.srv.periodicDispatcher.ForceEval(args.RequestNamespace(), job.ID)
	if err != nil {
		return fmt.Errorf("force launch for job %q failed: %v", job.ID, err)
	}

	reply.EvalID = eval.ID
	reply.EvalCreateIndex = eval.CreateIndex
	reply.Index = eval.CreateIndex
	return nil
}
