package prepo

import (
	"strings"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/oshared"
	svcshared "github.com/tech/sendico/payments/orchestrator/internal/service/shared"
	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/db/storable"
	pm "github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	"go.mongodb.org/mongo-driver/v2/bson"
)

type paymentDocument struct {
	storable.Base            `bson:",inline"`
	pm.OrganizationBoundBase `bson:",inline"`
	PaymentRef               string                      `bson:"paymentRef"`
	IdempotencyKey           string                      `bson:"idempotencyKey"`
	QuotationRef             string                      `bson:"quotationRef"`
	ClientPaymentRef         string                      `bson:"clientPaymentRef,omitempty"`
	IntentSnapshot           model.PaymentIntent         `bson:"intentSnapshot"`
	QuoteSnapshot            *model.PaymentQuoteSnapshot `bson:"quoteSnapshot"`
	State                    agg.State                   `bson:"state"`
	Version                  uint64                      `bson:"version"`
	StepExecutions           []agg.StepExecution         `bson:"stepExecutions,omitempty"`
}

func (*paymentDocument) Collection() string {
	return mservice.Payments
}

func toDocument(payment *agg.Payment) (*paymentDocument, error) {
	if payment == nil {
		return nil, nil //nolint:nilnil // nil aggregate means no document to persist
	}
	doc := &paymentDocument{
		Base:                  payment.Base,
		OrganizationBoundBase: payment.OrganizationBoundBase,
		PaymentRef:            strings.TrimSpace(payment.PaymentRef),
		IdempotencyKey:        strings.TrimSpace(payment.IdempotencyKey),
		QuotationRef:          strings.TrimSpace(payment.QuotationRef),
		ClientPaymentRef:      strings.TrimSpace(payment.ClientPaymentRef),
		IntentSnapshot:        payment.IntentSnapshot,
		QuoteSnapshot:         payment.QuoteSnapshot,
		State:                 payment.State,
		Version:               payment.Version,
		StepExecutions:        cloneStepExecutions(payment.StepExecutions),
	}
	return cloneDocument(doc)
}

func fromDocument(doc *paymentDocument) (*agg.Payment, error) {
	if doc == nil {
		return nil, nil //nolint:nilnil // nil document means no aggregate to hydrate
	}
	cloned, err := cloneDocument(doc)
	if err != nil {
		return nil, err
	}
	return &agg.Payment{
		Base:                  cloned.Base,
		OrganizationBoundBase: cloned.OrganizationBoundBase,
		PaymentRef:            cloned.PaymentRef,
		IdempotencyKey:        cloned.IdempotencyKey,
		QuotationRef:          cloned.QuotationRef,
		ClientPaymentRef:      cloned.ClientPaymentRef,
		IntentSnapshot:        cloned.IntentSnapshot,
		QuoteSnapshot:         cloned.QuoteSnapshot,
		State:                 cloned.State,
		Version:               cloned.Version,
		StepExecutions:        cloneStepExecutions(cloned.StepExecutions),
	}, nil
}

func cloneDocument(doc *paymentDocument) (*paymentDocument, error) {
	if doc == nil {
		return nil, nil //nolint:nilnil // nil document means no clone result
	}
	data, err := bson.Marshal(doc)
	if err != nil {
		return nil, err
	}
	out := &paymentDocument{}
	if err := bson.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func cloneStepExecutions(src []agg.StepExecution) []agg.StepExecution {
	if len(src) == 0 {
		return nil
	}
	out := make([]agg.StepExecution, 0, len(src))
	for i := range src {
		step := src[i]
		step.StepRef = strings.TrimSpace(step.StepRef)
		step.StepCode = strings.TrimSpace(step.StepCode)
		step.ReportVisibility = model.NormalizeReportVisibility(step.ReportVisibility)
		if !model.IsValidReportVisibility(step.ReportVisibility) {
			step.ReportVisibility = model.ReportVisibilityUnspecified
		}
		step.UserLabel = strings.TrimSpace(step.UserLabel)
		step.Comment = strings.TrimSpace(step.Comment)
		step.FailureCode = strings.TrimSpace(step.FailureCode)
		step.FailureMsg = strings.TrimSpace(step.FailureMsg)
		if step.Attempt == 0 {
			step.Attempt = 1
		}
		step.ExternalRefs = oshared.CloneExternalRefs(step.ExternalRefs)
		step.ExecutedMoney = svcshared.CloneMoneyTrimNonEmpty(step.ExecutedMoney)
		step.ConvertedMoney = svcshared.CloneMoneyTrimNonEmpty(step.ConvertedMoney)
		step.PlannedMoney = svcshared.CloneMoneyTrimNonEmpty(step.PlannedMoney)
		step.PlannedConvertedMoney = svcshared.CloneMoneyTrimNonEmpty(step.PlannedConvertedMoney)
		step.StartedAt = oshared.CloneTimeUTC(step.StartedAt)
		step.CompletedAt = oshared.CloneTimeUTC(step.CompletedAt)
		out = append(out, step)
	}
	return out
}
