package batch_quote_processor_v2

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/tech/sendico/payments/quotation/internal/service/quotation/transfer_intent_hydrator"
	"github.com/tech/sendico/pkg/merrors"
)

// SingleIntentProcessor processes one intent in v2 flow.
type SingleIntentProcessor interface {
	Process(ctx context.Context, in SingleProcessInput) (*SingleProcessOutput, error)
}

// BatchQuoteProcessorV2 iterates single-intent processing for batch requests.
type BatchQuoteProcessorV2 struct {
	single SingleIntentProcessor
}

func New(single SingleIntentProcessor) *BatchQuoteProcessorV2 {
	return &BatchQuoteProcessorV2{single: single}
}

func (p *BatchQuoteProcessorV2) Process(ctx context.Context, in ProcessInput) (*ProcessOutput, error) {
	if p == nil || p.single == nil {
		return nil, merrors.InvalidArgument("single processor is required")
	}

	normalizedCtx, err := normalizeContext(in.Context)
	if err != nil {
		return nil, err
	}
	if len(in.Intents) == 0 {
		return nil, merrors.InvalidArgument("intents are required")
	}

	items, err := buildBatchItems(normalizedCtx, in.Intents)
	if err != nil {
		return nil, err
	}

	results := make([]*BatchItemResult, 0, len(items))
	for _, item := range items {
		res, processErr := p.single.Process(ctx, SingleProcessInput{
			Context: normalizedCtx,
			Item:    *item,
		})
		if processErr != nil {
			return nil, wrapIndexedIntentError(item.Index, processErr)
		}
		if res == nil || res.Quote == nil {
			return nil, merrors.InvalidArgument(fmt.Sprintf("intents[%d]: quote is required", item.Index))
		}

		results = append(results, &BatchItemResult{
			Item:  *item,
			Quote: res.Quote,
		})
	}

	return &ProcessOutput{
		Context: normalizedCtx,
		Items:   results,
	}, nil
}

func normalizeContext(ctx BatchContext) (BatchContext, error) {
	ctx.OrganizationRef = strings.TrimSpace(ctx.OrganizationRef)
	ctx.InitiatorRef = strings.TrimSpace(ctx.InitiatorRef)
	ctx.BaseIdempotencyKey = strings.TrimSpace(ctx.BaseIdempotencyKey)

	if ctx.OrganizationRef == "" {
		return BatchContext{}, merrors.InvalidArgument("organization_ref is required")
	}
	if ctx.OrganizationID.IsZero() {
		return BatchContext{}, merrors.InvalidArgument("organization_id is required")
	}
	if ctx.InitiatorRef == "" {
		return BatchContext{}, merrors.InvalidArgument("initiator_ref is required")
	}
	if ctx.PreviewOnly && ctx.BaseIdempotencyKey != "" {
		return BatchContext{}, merrors.InvalidArgument("preview requests must not use idempotency key")
	}
	if !ctx.PreviewOnly && ctx.BaseIdempotencyKey == "" {
		return BatchContext{}, merrors.InvalidArgument("idempotency key is required")
	}

	return ctx, nil
}

func buildBatchItems(ctx BatchContext, intents []*transfer_intent_hydrator.QuoteIntent) ([]*BatchItem, error) {
	items := make([]*BatchItem, 0, len(intents))
	total := len(intents)

	for i, intent := range intents {
		if intent == nil {
			return nil, merrors.InvalidArgument(fmt.Sprintf("intents[%d] is required", i))
		}

		items = append(items, &BatchItem{
			Index:          i,
			Count:          total,
			Intent:         intent,
			IdempotencyKey: deriveItemIdempotencyKey(ctx.BaseIdempotencyKey, ctx.PreviewOnly, i, total),
		})
	}

	return items, nil
}

func wrapIndexedIntentError(index int, err error) error {
	msg := fmt.Sprintf("intents[%d]", index)
	if errors.Is(err, merrors.ErrInvalidArg) {
		return merrors.InvalidArgumentWrap(err, msg)
	}
	return merrors.InternalWrap(err, msg)
}
