package prmap

import (
	"strings"
	"time"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"
	"github.com/tech/sendico/pkg/merrors"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
)

func mapStepExecutions(src []agg.StepExecution) ([]*orchestrationv2.StepExecution, error) {
	if len(src) == 0 {
		return nil, nil
	}
	out := make([]*orchestrationv2.StepExecution, 0, len(src))
	for i := range src {
		mapped, err := mapStepExecution(src[i], i)
		if err != nil {
			return nil, err
		}
		out = append(out, mapped)
	}
	return out, nil
}

func mapStepExecution(step agg.StepExecution, index int) (*orchestrationv2.StepExecution, error) {
	state, ok := normalizeStepState(step.State)
	if !ok {
		return nil, merrors.InvalidArgument("payment.stepExecutions[" + itoa(index) + "].state is invalid")
	}

	attempt := step.Attempt
	if attempt == 0 {
		attempt = 1
	}

	plannedAmount := moneyToProto(step.PlannedMoney)
	plannedConverted := moneyToProto(step.PlannedConvertedMoney)
	executedAmount := moneyToProto(step.ExecutedMoney)
	executedConverted := moneyToProto(step.ConvertedMoney)
	moneyEnvelope := mapStepMoneyEnvelope(plannedAmount, plannedConverted, executedAmount, executedConverted)

	return &orchestrationv2.StepExecution{
		StepRef:          strings.TrimSpace(step.StepRef),
		StepCode:         strings.TrimSpace(step.StepCode),
		State:            mapStepState(state),
		Attempt:          attempt,
		StartedAt:        tsOrNil(derefTime(step.StartedAt)),
		CompletedAt:      tsOrNil(derefTime(step.CompletedAt)),
		Failure:          mapStepFailure(step, state),
		Refs:             mapExternalRefs(step.StepCode, step.ExternalRefs),
		ReportVisibility: mapReportVisibility(step.ReportVisibility),
		UserLabel:        strings.TrimSpace(step.UserLabel),
		Money:            moneyEnvelope,
	}, nil
}

func mapStepMoneyEnvelope(
	plannedAmount *moneyv1.Money,
	plannedConverted *moneyv1.Money,
	executedAmount *moneyv1.Money,
	executedConverted *moneyv1.Money,
) *orchestrationv2.StepExecutionMoney {
	planned := mapStepMoneySnapshot(plannedAmount, plannedConverted)
	executed := mapStepMoneySnapshot(executedAmount, executedConverted)
	if planned == nil && executed == nil {
		return nil
	}
	return &orchestrationv2.StepExecutionMoney{
		Planned:  planned,
		Executed: executed,
	}
}

func mapStepMoneySnapshot(amount *moneyv1.Money, converted *moneyv1.Money) *orchestrationv2.StepExecutionMoneySnapshot {
	if amount == nil && converted == nil {
		return nil
	}
	return &orchestrationv2.StepExecutionMoneySnapshot{
		Amount:          amount,
		ConvertedAmount: converted,
	}
}

func mapStepFailure(step agg.StepExecution, state agg.StepState) *orchestrationv2.Failure {
	if state != agg.StepStateFailed && state != agg.StepStateNeedsAttention {
		return nil
	}
	code := strings.TrimSpace(step.FailureCode)
	msg := strings.TrimSpace(step.FailureMsg)
	if code == "" && msg == "" {
		return nil
	}
	return &orchestrationv2.Failure{
		Category: inferFailureCategory(code),
		Code:     code,
		Message:  msg,
	}
}

func mapExternalRefs(stepCode string, refs []agg.ExternalRef) []*orchestrationv2.ExternalReference {
	if len(refs) == 0 {
		return nil
	}
	out := make([]*orchestrationv2.ExternalReference, 0, len(refs))
	for i := range refs {
		ref := refs[i]
		kind := strings.TrimSpace(ref.Kind)
		value := strings.TrimSpace(ref.Ref)
		gatewayInstanceID := strings.TrimSpace(ref.GatewayInstanceID)
		if kind == "" && value == "" && gatewayInstanceID == "" {
			continue
		}
		out = append(out, &orchestrationv2.ExternalReference{
			Rail:              inferRail(kind, stepCode),
			GatewayInstanceId: gatewayInstanceID,
			Kind:              kind,
			Ref:               value,
		})
	}
	return out
}

func derefTime(value *time.Time) time.Time {
	if value == nil {
		return time.Time{}
	}
	return value.UTC()
}
