package quotation_service_v2

import (
	"context"
	"errors"
	"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_idempotency_service"
	"github.com/tech/sendico/payments/quotation/internal/service/quotation/quote_persistence_service"
	"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"
	quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
	"go.uber.org/zap"
)

func (s *QuotationServiceV2) ProcessQuotePayments(
	ctx context.Context,
	req *quotationv2.QuotePaymentsRequest,
) (*QuotePaymentsResult, error) {
	logger := s.quotePaymentsLogger(req).Named("processor").With(zap.String("mode", "batch"))
	startedAt := time.Now()
	logger.Info("QuotePayments request received")
	summaries, totalIntents, truncatedIntents := summarizeQuoteIntentList(req.GetIntents(), 10)
	logger.Debug("QuotePayments request payload",
		zap.Int("intent_count", totalIntents),
		zap.Int("intent_count_truncated", truncatedIntents),
		zap.Any("intents", summaries),
	)
	logger.Debug("ProcessQuotePayments started")

	if err := s.validateDependencies(); err != nil {
		logger.Warn("ProcessQuotePayments failed on dependency validation", zap.Error(err))
		return nil, err
	}
	logger.Debug("ProcessQuotePayments dependencies validated")

	requestCtx, err := s.deps.Validator.ValidateQuotePayments(req)
	if err != nil {
		logger.Debug("ProcessQuotePayments failed on request validation", zap.Error(err))
		return nil, err
	}
	logger.Debug("ProcessQuotePayments request validated",
		zap.String("organization_ref", requestCtx.OrganizationRef),
		zap.Bool("preview_only", requestCtx.PreviewOnly),
		zap.Int("intent_count", requestCtx.IntentCount),
	)

	fingerprint := ""
	if !requestCtx.PreviewOnly {
		fingerprint = s.deps.Idempotency.FingerprintQuotePayments(req)
		logger.Debug("ProcessQuotePayments checking idempotency reuse")
		reusedRecord, reused, reuseErr := s.deps.Idempotency.TryReuse(ctx, s.deps.QuotesStore, quote_idempotency_service.ReuseInput{
			OrganizationID: requestCtx.OrganizationID,
			IdempotencyKey: requestCtx.IdempotencyKey,
			Fingerprint:    fingerprint,
			Shape:          quote_idempotency_service.QuoteShapeBatch,
		})
		if reuseErr != nil {
			logger.Warn("ProcessQuotePayments idempotency reuse check failed", zap.Error(reuseErr))
			return nil, reuseErr
		}
		if reused {
			logger.Info("ProcessQuotePayments served from idempotency reuse",
				zap.String("quote_ref", strings.TrimSpace(reusedRecord.QuoteRef)),
				zap.Duration("elapsed", time.Since(startedAt)),
			)
			reusedResult, mapErr := s.batchResultFromRecord(reusedRecord)
			if mapErr != nil {
				logger.Warn("ProcessQuotePayments failed to map reused record", zap.Error(mapErr))
				return nil, mapErr
			}
			reusedResponse := reusedResult.Response
			logger.Info("QuotePayments response ready",
				zap.Duration("elapsed", time.Since(startedAt)),
				zap.String("quote_ref", strings.TrimSpace(reusedResponse.GetQuoteRef())),
				zap.String("idempotency_key", strings.TrimSpace(reusedResponse.GetIdempotencyKey())),
				zap.Int("quotes_count", len(reusedResponse.GetQuotes())),
			)
			return reusedResult, nil
		}
		logger.Debug("ProcessQuotePayments idempotency reuse miss")
	}

	hydrated, err := s.deps.Hydrator.HydrateMany(ctx, transfer_intent_hydrator.HydrateManyInput{
		OrganizationRef: requestCtx.OrganizationRef,
		InitiatorRef:    requestCtx.InitiatorRef,
		Intents:         req.GetIntents(),
	})
	if err != nil {
		logger.Debug("ProcessQuotePayments failed during intent hydration", zap.Error(err))
		return nil, err
	}
	logger.Debug("ProcessQuotePayments intents hydrated", zap.Int("intent_count", len(hydrated)))

	quoteRef := ""
	if !requestCtx.PreviewOnly {
		quoteRef = normalizeQuoteRef(s.deps.NewRef())
		if quoteRef == "" {
			logger.Warn("ProcessQuotePayments generated empty quote_ref")
			return nil, merrors.InvalidArgument("quote_ref is required")
		}
	}

	collector := newItemCollector()
	single := newSingleIntentProcessorV2(
		s.deps.Computation,
		s.deps.Classifier,
		s.deps.ResponseMapper,
		quoteRef,
		s.deps.Now().UTC(),
		collector,
		logger,
	)
	batch := batch_quote_processor_v2.New(single)

	batchOut, err := batch.Process(ctx, batch_quote_processor_v2.ProcessInput{
		Context: newBatchContext(requestCtx),
		Intents: hydrated,
	})
	if err != nil {
		logger.Warn("ProcessQuotePayments failed during computation pipeline", zap.Error(err))
		return nil, err
	}
	if batchOut == nil || len(batchOut.Items) != len(hydrated) {
		logger.Warn("ProcessQuotePayments returned invalid batch output")
		return nil, merrors.InvalidArgument("batch quote output is invalid")
	}

	quotes := make([]*quotationv2.PaymentQuote, 0, len(batchOut.Items))
	for _, item := range batchOut.Items {
		if item == nil || item.Quote == nil {
			logger.Warn("ProcessQuotePayments contains empty quote item")
			return nil, merrors.InvalidArgument("batch item quote is required")
		}
		quotes = append(quotes, item.Quote)
	}
	logger.Debug("ProcessQuotePayments computation completed", zap.Int("quotes_count", len(quotes)))

	details := collector.Ordered(len(batchOut.Items))
	if len(details) != len(batchOut.Items) {
		logger.Warn("ProcessQuotePayments missing item details", zap.Int("details_count", len(details)), zap.Int("items_count", len(batchOut.Items)))
		return nil, merrors.InvalidArgument("batch processing details are incomplete")
	}

	response := &quotationv2.QuotePaymentsResponse{
		QuoteRef:       quoteRef,
		Quotes:         quotes,
		IdempotencyKey: strings.TrimSpace(requestCtx.IdempotencyKey),
	}
	result := &QuotePaymentsResult{
		Response: response,
	}
	if requestCtx.PreviewOnly {
		logger.Info("ProcessQuotePayments preview completed",
			zap.Duration("elapsed", time.Since(startedAt)),
			zap.String("quote_ref", strings.TrimSpace(response.GetQuoteRef())),
			zap.Int("quotes_count", len(response.GetQuotes())),
		)
		logger.Info("QuotePayments response ready",
			zap.Duration("elapsed", time.Since(startedAt)),
			zap.String("quote_ref", strings.TrimSpace(response.GetQuoteRef())),
			zap.String("idempotency_key", strings.TrimSpace(response.GetIdempotencyKey())),
			zap.Int("quotes_count", len(response.GetQuotes())),
		)
		return result, nil
	}

	expires := make([]time.Time, 0, len(details))
	items := make([]quote_persistence_service.PersistItemInput, 0, len(details))
	for _, detail := range details {
		if detail == nil || detail.Intent.Amount == nil || detail.Quote == nil {
			logger.Warn("ProcessQuotePayments contains incomplete detail")
			return nil, merrors.InvalidArgument("batch processing detail is incomplete")
		}
		expires = append(expires, detail.ExpiresAt)
		items = append(items, quote_persistence_service.PersistItemInput{
			Intent: pointerTo(detail.Intent),
			Quote:  quoteSnapshotFromComputed(detail.Quote),
			Status: statusInputFromStatus(detail.Status),
		})
	}

	expiresAt, ok := minExpiry(expires)
	if !ok {
		logger.Warn("ProcessQuotePayments produced empty expires_at")
		return nil, merrors.InvalidArgument("expires_at is required")
	}

	record, err := s.deps.Persistence.BuildRecord(quote_persistence_service.PersistInput{
		OrganizationID: requestCtx.OrganizationID,
		QuoteRef:       quoteRef,
		IdempotencyKey: requestCtx.IdempotencyKey,
		Hash:           fingerprint,
		ExpiresAt:      expiresAt,
		RequestShape:   model.QuoteRequestShapeBatch,
		Items:          items,
	})
	if err != nil {
		logger.Warn("ProcessQuotePayments failed to build persistence record", zap.Error(err))
		return nil, err
	}

	stored, reused, err := s.deps.Idempotency.CreateOrReuse(ctx, s.deps.QuotesStore, quote_idempotency_service.CreateInput{
		Record: record,
		Reuse: quote_idempotency_service.ReuseInput{
			OrganizationID: requestCtx.OrganizationID,
			IdempotencyKey: requestCtx.IdempotencyKey,
			Fingerprint:    fingerprint,
			Shape:          quote_idempotency_service.QuoteShapeBatch,
		},
	})
	if err != nil {
		logger.Warn("ProcessQuotePayments failed while storing idempotent record", zap.Error(err))
		if errors.Is(err, quote_idempotency_service.ErrIdempotencyParamMismatch) ||
			errors.Is(err, quote_idempotency_service.ErrIdempotencyShapeMismatch) {
			return nil, merrors.InvalidArgument(err.Error())
		}
		return nil, err
	}
	if reused {
		logger.Info("ProcessQuotePayments reused concurrent idempotent record",
			zap.String("quote_ref", strings.TrimSpace(stored.QuoteRef)),
			zap.Duration("elapsed", time.Since(startedAt)),
		)
		reusedResult, mapErr := s.batchResultFromRecord(stored)
		if mapErr != nil {
			logger.Warn("ProcessQuotePayments failed to map reused concurrent record", zap.Error(mapErr))
			return nil, mapErr
		}
		reusedResponse := reusedResult.Response
		logger.Info("QuotePayments response ready",
			zap.Duration("elapsed", time.Since(startedAt)),
			zap.String("quote_ref", strings.TrimSpace(reusedResponse.GetQuoteRef())),
			zap.String("idempotency_key", strings.TrimSpace(reusedResponse.GetIdempotencyKey())),
			zap.Int("quotes_count", len(reusedResponse.GetQuotes())),
		)
		return reusedResult, nil
	}

	result.Record = stored
	logger.Info("ProcessQuotePayments persisted quote batch",
		zap.String("quote_ref", strings.TrimSpace(stored.QuoteRef)),
		zap.Int("quotes_count", len(stored.Items)),
		zap.Time("expires_at", stored.ExpiresAt),
		zap.Duration("elapsed", time.Since(startedAt)),
	)
	logger.Info("QuotePayments response ready",
		zap.Duration("elapsed", time.Since(startedAt)),
		zap.String("quote_ref", strings.TrimSpace(response.GetQuoteRef())),
		zap.String("idempotency_key", strings.TrimSpace(response.GetIdempotencyKey())),
		zap.Int("quotes_count", len(response.GetQuotes())),
	)
	return result, nil
}
