package quotation_service_v2

import (
	"context"
	"sort"
	"strings"
	"time"

	"github.com/tech/sendico/payments/quotation/internal/service/quotation/batch_quote_processor_v2"
	"github.com/tech/sendico/payments/quotation/internal/service/quotation/quote_computation_service"
	"github.com/tech/sendico/payments/quotation/internal/service/quotation/quote_executability_classifier"
	"github.com/tech/sendico/payments/quotation/internal/service/quotation/quote_response_mapper_v2"
	"github.com/tech/sendico/payments/quotation/internal/service/quotation/transfer_intent_hydrator"
	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.uber.org/zap"
)

type itemProcessDetail struct {
	Index     int
	Intent    model.PaymentIntent
	Quote     *quote_computation_service.ComputedQuote
	ExpiresAt time.Time
	Status    quote_response_mapper_v2.QuoteStatus
}

type itemCollector struct {
	items map[int]*itemProcessDetail
}

func newItemCollector() *itemCollector {
	return &itemCollector{items: make(map[int]*itemProcessDetail)}
}

func (c *itemCollector) Add(detail *itemProcessDetail) {
	if c == nil || detail == nil {
		return
	}
	c.items[detail.Index] = detail
}

func (c *itemCollector) Get(index int) (*itemProcessDetail, bool) {
	if c == nil {
		return nil, false
	}
	detail, ok := c.items[index]
	return detail, ok
}

func (c *itemCollector) Ordered(count int) []*itemProcessDetail {
	if c == nil || count <= 0 {
		return nil
	}
	ordered := make([]*itemProcessDetail, 0, count)
	indices := make([]int, 0, len(c.items))
	for idx := range c.items {
		indices = append(indices, idx)
	}
	sort.Ints(indices)
	for _, idx := range indices {
		ordered = append(ordered, c.items[idx])
	}
	return ordered
}

type singleIntentProcessorV2 struct {
	computation *quote_computation_service.QuoteComputationService
	classifier  *quote_executability_classifier.QuoteExecutabilityClassifier
	mapper      *quote_response_mapper_v2.QuoteResponseMapperV2

	quoteRef  string
	pricedAt  time.Time
	collector *itemCollector
	logger    mlogger.Logger
}

func newSingleIntentProcessorV2(
	computation *quote_computation_service.QuoteComputationService,
	classifier *quote_executability_classifier.QuoteExecutabilityClassifier,
	mapper *quote_response_mapper_v2.QuoteResponseMapperV2,
	quoteRef string,
	pricedAt time.Time,
	collector *itemCollector,
	logger mlogger.Logger,
) *singleIntentProcessorV2 {
	return &singleIntentProcessorV2{
		computation: computation,
		classifier:  classifier,
		mapper:      mapper,
		quoteRef:    quoteRef,
		pricedAt:    pricedAt,
		collector:   collector,
		logger:      logger.Named("single"),
	}
}

func (p *singleIntentProcessorV2) Process(
	ctx context.Context,
	in batch_quote_processor_v2.SingleProcessInput,
) (*batch_quote_processor_v2.SingleProcessOutput, error) {
	if p == nil {
		return nil, merrors.InvalidArgument("single processor is required")
	}
	logger := p.logger.With(
		zap.Int("intent_index", in.Item.Index),
		zap.Int("intent_count", in.Item.Count),
	)
	logger.Debug("Single intent processing started")

	if p.computation == nil {
		logger.Warn("Single intent processing failed: missing computation service")
		return nil, merrors.InvalidArgument("quote computation service is required")
	}
	if p.classifier == nil {
		logger.Warn("Single intent processing failed: missing classifier")
		return nil, merrors.InvalidArgument("quote executability classifier is required")
	}
	if p.mapper == nil {
		logger.Warn("Single intent processing failed: missing mapper")
		return nil, merrors.InvalidArgument("quote response mapper is required")
	}
	if in.Item.Intent == nil {
		logger.Debug("Single intent processing failed: empty intent")
		return nil, merrors.InvalidArgument("intent is required")
	}

	computed, err := p.computation.Compute(ctx, quote_computation_service.ComputeInput{
		OrganizationRef:    in.Context.OrganizationRef,
		OrganizationID:     in.Context.OrganizationID,
		BaseIdempotencyKey: in.Item.IdempotencyKey,
		PreviewOnly:        in.Context.PreviewOnly,
		Intents:            []*transfer_intent_hydrator.QuoteIntent{in.Item.Intent},
	})
	if err != nil {
		logger.Warn("Single intent computation failed", zap.Error(err))
		return nil, err
	}
	if computed == nil || computed.Plan == nil || len(computed.Results) != 1 || len(computed.Plan.Items) != 1 {
		logger.Warn("Single intent computation returned invalid shape")
		return nil, merrors.InvalidArgument("invalid computation output for single item")
	}

	result := computed.Results[0]
	planItem := computed.Plan.Items[0]
	if result == nil || planItem == nil || result.Quote == nil || planItem.Intent.Amount == nil {
		logger.Warn("Single intent computation returned incomplete payload")
		return nil, merrors.InvalidArgument("incomplete computation output")
	}
	logger.Debug("Single intent computation completed",
		zap.Int("steps_count", len(planItem.Steps)),
		zap.String("intent_ref", strings.TrimSpace(planItem.Intent.Ref)),
	)

	state := p.classifier.BuildState(in.Context.PreviewOnly, result.BlockReason)
	status := quote_response_mapper_v2.QuoteStatus{
		State:       state.State(),
		BlockReason: state.BlockReason(),
	}

	canonical := quote_response_mapper_v2.CanonicalQuote{
		QuoteRef:                p.quoteRef,
		TransferPrincipalAmount: cloneProtoMoney(result.Quote.DebitAmount),
		DestinationAmount:       cloneProtoMoney(result.Quote.CreditAmount),
		PayerTotalDebitAmount:   cloneProtoMoney(result.Quote.TotalCost),
		FeeLines:                result.Quote.FeeLines,
		FeeRules:                result.Quote.FeeRules,
		FXQuote:                 result.Quote.FXQuote,
		Route:                   result.Quote.Route,
		Conditions:              result.Quote.ExecutionConditions,
		ResolvedSettlementMode:  result.Quote.ResolvedSettlementMode,
		ResolvedFeeTreatment:    result.Quote.ResolvedFeeTreatment,
		ExpiresAt:               result.ExpiresAt,
		PricedAt:                p.pricedAt,
	}
	if canonical.ResolvedSettlementMode == 0 {
		canonical.ResolvedSettlementMode = resolvedSettlementModeFromModel(planItem.Intent.SettlementMode)
	}
	if canonical.ResolvedFeeTreatment == 0 {
		canonical.ResolvedFeeTreatment = defaultResolvedFeeTreatment()
	}

	mapped, mapErr := p.mapper.Map(quote_response_mapper_v2.MapInput{
		Quote:  canonical,
		Status: status,
	})
	if mapErr != nil {
		logger.Warn("Single intent response mapping failed", zap.Error(mapErr))
		return nil, mapErr
	}
	if mapped == nil || mapped.Quote == nil {
		logger.Warn("Single intent response mapping returned empty quote")
		return nil, merrors.InvalidArgument("mapped quote is required")
	}
	mapped.Quote.IntentRef = firstNonEmpty(
		strings.TrimSpace(planItem.Intent.Ref),
		strings.TrimSpace(in.Item.Intent.Ref),
	)
	expiresAt := result.ExpiresAt
	if quoteExpiresAt := mapped.Quote.GetExpiresAt(); quoteExpiresAt != nil {
		expiresAt = quoteExpiresAt.AsTime().UTC()
	}

	p.collector.Add(&itemProcessDetail{
		Index:     in.Item.Index,
		Intent:    planItem.Intent,
		Quote:     result.Quote,
		ExpiresAt: expiresAt,
		Status:    status,
	})
	mappedQuote := mapped.Quote
	logger.Debug("Single intent processing completed",
		zap.String("quote_ref", strings.TrimSpace(mappedQuote.GetQuoteRef())),
		zap.String("intent_ref", strings.TrimSpace(mappedQuote.GetIntentRef())),
		zap.String("quote_state", strings.TrimSpace(mappedQuote.GetState().String())),
		zap.String("block_reason", strings.TrimSpace(mappedQuote.GetBlockReason().String())),
	)

	return &batch_quote_processor_v2.SingleProcessOutput{
		Quote: mapped.Quote,
	}, nil
}
