package events

import (
	"github.com/runatlantis/atlantis/server/events/command"
	"github.com/runatlantis/atlantis/server/metrics"
	tally "github.com/uber-go/tally/v4"
)

type IntrumentedCommandRunner interface {
	Plan(ctx command.ProjectContext) command.ProjectResult
	PolicyCheck(ctx command.ProjectContext) command.ProjectResult
	Apply(ctx command.ProjectContext) command.ProjectResult
	ApprovePolicies(ctx command.ProjectContext) command.ProjectResult
	Import(ctx command.ProjectContext) command.ProjectResult
	StateRm(ctx command.ProjectContext) command.ProjectResult
}

type InstrumentedProjectCommandRunner struct {
	projectCommandRunner ProjectCommandRunner
	scope                tally.Scope
}

func NewInstrumentedProjectCommandRunner(scope tally.Scope, projectCommandRunner ProjectCommandRunner) *InstrumentedProjectCommandRunner {
	projectTags := command.ProjectScopeTags{}
	scope = scope.SubScope("project").Tagged(projectTags.Loadtags())

	for _, m := range []string{metrics.ExecutionSuccessMetric, metrics.ExecutionErrorMetric, metrics.ExecutionFailureMetric} {
		metrics.InitCounter(scope, m)
	}

	return &InstrumentedProjectCommandRunner{
		projectCommandRunner: projectCommandRunner,
		scope:                scope,
	}
}

func (p *InstrumentedProjectCommandRunner) Plan(ctx command.ProjectContext) command.ProjectResult {
	return RunAndEmitStats(ctx, p.projectCommandRunner.Plan, p.scope)
}

func (p *InstrumentedProjectCommandRunner) PolicyCheck(ctx command.ProjectContext) command.ProjectResult {
	return RunAndEmitStats(ctx, p.projectCommandRunner.PolicyCheck, p.scope)
}

func (p *InstrumentedProjectCommandRunner) Apply(ctx command.ProjectContext) command.ProjectResult {
	return RunAndEmitStats(ctx, p.projectCommandRunner.Apply, p.scope)
}

func (p *InstrumentedProjectCommandRunner) ApprovePolicies(ctx command.ProjectContext) command.ProjectResult {
	return RunAndEmitStats(ctx, p.projectCommandRunner.ApprovePolicies, p.scope)
}

func (p *InstrumentedProjectCommandRunner) Import(ctx command.ProjectContext) command.ProjectResult {
	return RunAndEmitStats(ctx, p.projectCommandRunner.Import, p.scope)
}

func (p *InstrumentedProjectCommandRunner) StateRm(ctx command.ProjectContext) command.ProjectResult {
	return RunAndEmitStats(ctx, p.projectCommandRunner.StateRm, p.scope)
}

func RunAndEmitStats(ctx command.ProjectContext, execute func(ctx command.ProjectContext) command.ProjectResult, scope tally.Scope) command.ProjectResult {
	commandName := ctx.CommandName.String()
	// ensures we are differentiating between project level command and overall command
	scope = ctx.SetProjectScopeTags(scope).SubScope(commandName)
	logger := ctx.Log

	executionTime := scope.Timer(metrics.ExecutionTimeMetric).Start()
	defer executionTime.Stop()

	executionSuccess := scope.Counter(metrics.ExecutionSuccessMetric)
	executionError := scope.Counter(metrics.ExecutionErrorMetric)
	executionFailure := scope.Counter(metrics.ExecutionFailureMetric)

	result := execute(ctx)

	if result.Error != nil {
		executionError.Inc(1)
		logger.Err("Error running %s operation: %s", commandName, result.Error.Error())
		return result
	}

	if result.Failure != "" {
		executionFailure.Inc(1)
		logger.Err("Failure running %s operation: %s", commandName, result.Failure)
		return result
	}

	logger.Info("%s success. output available at: %s", commandName, ctx.Pull.URL)

	executionSuccess.Inc(1)
	return result

}
