package sexec

import (
	"context"
	"strings"
	"time"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/xerr"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/xplan"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.uber.org/zap"
)

type svc struct {
	logger mlogger.Logger
	deps   Dependencies
}

func (s *svc) Execute(ctx context.Context, in ExecuteInput) (out *ExecuteOutput, err error) {
	logger := s.logger
	logger.Debug("Starting Execute",
		zap.String("step_ref", strings.TrimSpace(in.Step.StepRef)),
		zap.String("step_code", strings.TrimSpace(in.Step.StepCode)),
		zap.String("action", strings.TrimSpace(string(in.Step.Action))),
		zap.String("rail", strings.TrimSpace(string(in.Step.Rail))),
	)
	defer func(start time.Time) {
		fields := []zap.Field{zap.Int64("duration_ms", time.Since(start).Milliseconds())}
		if out != nil {
			fields = append(fields,
				zap.String("result_state", string(out.StepExecution.State)),
				zap.Uint32("result_attempt", out.StepExecution.Attempt),
				zap.Bool("async", out.Async),
			)
		}
		if err != nil {
			logger.Warn("Failed to execute", append(fields, zap.Error(err))...)
			return
		}
		logger.Debug("Completed Execute", fields...)
	}(time.Now())

	req, err := validateInput(in)
	if err != nil {
		return nil, err
	}

	switch classifyRoute(req.Step) {
	case routeLedger:
		if s.deps.Ledger == nil {
			return nil, missingExecutorError("ledger")
		}
		out, err = s.deps.Ledger.ExecuteLedger(ctx, req)
		return out, err
	case routeCrypto:
		if s.deps.Crypto == nil {
			return nil, missingExecutorError("crypto")
		}
		out, err = s.deps.Crypto.ExecuteCrypto(ctx, req)
		return out, err
	case routeProviderSettlement:
		if s.deps.ProviderSettlement == nil {
			return nil, missingExecutorError("settlement")
		}
		out, err = s.deps.ProviderSettlement.ExecuteProviderSettlement(ctx, req)
		return out, err
	case routeCardPayout:
		if s.deps.CardPayout == nil {
			return nil, missingExecutorError("card")
		}
		out, err = s.deps.CardPayout.ExecuteCardPayout(ctx, req)
		return out, err
	case routeObserveConfirm:
		if s.deps.ObserveConfirm == nil {
			return nil, missingExecutorError("observe_confirm")
		}
		out, err = s.deps.ObserveConfirm.ExecuteObserveConfirm(ctx, req)
		return out, err
	case routeGuard:
		if s.deps.Guard == nil {
			return nil, missingExecutorError("guard")
		}
		out, err = s.deps.Guard.ExecuteGuard(ctx, req)
		return out, err
	default:
		return nil, unsupportedStepError(req.Step)
	}
}

func validateInput(in ExecuteInput) (StepRequest, error) {
	if in.Payment == nil {
		return StepRequest{}, merrors.InvalidArgument("payment is required")
	}

	step, err := normalizeStep(in.Step)
	if err != nil {
		return StepRequest{}, err
	}
	exec, err := normalizeStepExecution(in.StepExecution, step)
	if err != nil {
		return StepRequest{}, err
	}

	return StepRequest{
		Payment:       in.Payment,
		Step:          step,
		StepExecution: exec,
	}, nil
}

func normalizeStep(step xplan.Step) (xplan.Step, error) {
	step.StepRef = strings.TrimSpace(step.StepRef)
	step.StepCode = strings.TrimSpace(step.StepCode)
	step.Comment = strings.TrimSpace(step.Comment)
	if step.StepRef == "" {
		return xplan.Step{}, merrors.InvalidArgument("step.step_ref is required")
	}
	if step.StepCode == "" {
		step.StepCode = step.StepRef
	}
	return step, nil
}

func normalizeStepExecution(exec agg.StepExecution, step xplan.Step) (agg.StepExecution, error) {
	exec.StepRef = strings.TrimSpace(exec.StepRef)
	exec.StepCode = strings.TrimSpace(exec.StepCode)
	exec.Comment = strings.TrimSpace(exec.Comment)
	if exec.StepRef == "" {
		exec.StepRef = step.StepRef
	}
	if exec.StepRef != step.StepRef {
		return agg.StepExecution{}, merrors.InvalidArgument("step_execution.step_ref must match step.step_ref")
	}
	if exec.StepCode == "" {
		exec.StepCode = step.StepCode
	}
	if exec.Attempt == 0 {
		exec.Attempt = 1
	}
	return exec, nil
}

func missingExecutorError(kind string) error {
	return xerr.Wrapf(ErrMissingExecutor, "%s", strings.TrimSpace(kind))
}

func unsupportedStepError(step xplan.Step) error {
	msg := strings.Join([]string{
		"step_ref=" + stepField(step.StepRef),
		"step_code=" + stepField(step.StepCode),
		"step_kind=" + stepField(string(step.Kind)),
		"action=" + stepField(string(step.Action)),
		"rail=" + stepField(string(step.Rail)),
	}, " ")
	return xerr.Wrapf(ErrUnsupportedStep, "%s", msg)
}

func stepField(value string) string {
	clean := strings.TrimSpace(value)
	if clean == "" {
		return "UNSPECIFIED"
	}
	return clean
}
