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) ProcessQuotePayment(
	ctx context.Context,
	req *quotationv2.QuotePaymentRequest,
) (*QuotePaymentResult, error) {
	logger := s.quotePaymentLogger(req).Named("processor").With(zap.String("mode", "single"))
	startedAt := time.Now()
	logger.Info("QuotePayment request received")
	logger.Debug("QuotePayment request payload", zap.Any("intent", summarizeQuoteIntent(req.GetIntent())))
	logger.Debug("ProcessQuotePayment started")

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

	requestCtx, err := s.deps.Validator.ValidateQuotePayment(req)
	if err != nil {
		logger.Debug("ProcessQuotePayment failed on request validation", zap.Error(err))
		return nil, err
	}
	logger.Debug("ProcessQuotePayment 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.FingerprintQuotePayment(req)
		logger.Debug("ProcessQuotePayment 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.QuoteShapeSingle,
		})
		if reuseErr != nil {
			logger.Warn("ProcessQuotePayment idempotency reuse check failed", zap.Error(reuseErr))
			return nil, reuseErr
		}
		if reused {
			logger.Info("ProcessQuotePayment served from idempotency reuse",
				zap.String("quote_ref", strings.TrimSpace(reusedRecord.QuoteRef)),
				zap.Duration("elapsed", time.Since(startedAt)),
			)
			reusedResult, mapErr := s.singleResultFromRecord(reusedRecord)
			if mapErr != nil {
				logger.Warn("ProcessQuotePayment failed to map reused record", zap.Error(mapErr))
				return nil, mapErr
			}
			reusedResponse := reusedResult.Response
			reusedQuote := reusedResponse.GetQuote()
			logger.Info("QuotePayment response ready",
				zap.Duration("elapsed", time.Since(startedAt)),
				zap.String("quote_ref", strings.TrimSpace(reusedQuote.GetQuoteRef())),
				zap.String("intent_ref", strings.TrimSpace(reusedQuote.GetIntentRef())),
				zap.String("quote_state", strings.TrimSpace(reusedQuote.GetState().String())),
				zap.String("block_reason", strings.TrimSpace(reusedQuote.GetBlockReason().String())),
				zap.String("idempotency_key", strings.TrimSpace(reusedResponse.GetIdempotencyKey())),
			)
			return reusedResult, nil
		}
		logger.Debug("ProcessQuotePayment idempotency reuse miss")
	}

	hydrated, err := s.deps.Hydrator.HydrateOne(ctx, transfer_intent_hydrator.HydrateOneInput{
		OrganizationRef: requestCtx.OrganizationRef,
		InitiatorRef:    requestCtx.InitiatorRef,
		Intent:          req.GetIntent(),
	})
	if err != nil {
		logger.Debug("ProcessQuotePayment failed during intent hydration", zap.Error(err))
		return nil, err
	}
	logger.Debug("ProcessQuotePayment intent hydrated",
		zap.String("intent_ref", strings.TrimSpace(hydrated.Ref)),
		zap.String("kind", strings.TrimSpace(string(hydrated.Kind))),
	)

	quoteRef := ""
	if !requestCtx.PreviewOnly {
		quoteRef = normalizeQuoteRef(s.deps.NewRef())
		if quoteRef == "" {
			logger.Warn("ProcessQuotePayment 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: []*transfer_intent_hydrator.QuoteIntent{hydrated},
	})
	if err != nil {
		logger.Warn("ProcessQuotePayment failed during computation pipeline", zap.Error(err))
		return nil, err
	}
	if batchOut == nil || len(batchOut.Items) != 1 || batchOut.Items[0] == nil || batchOut.Items[0].Quote == nil {
		logger.Warn("ProcessQuotePayment returned invalid single output")
		return nil, merrors.InvalidArgument("single quote output is invalid")
	}
	computedQuote := batchOut.Items[0].Quote
	logger.Debug("ProcessQuotePayment computation completed",
		zap.String("quote_ref", strings.TrimSpace(computedQuote.GetQuoteRef())),
		zap.String("intent_ref", strings.TrimSpace(computedQuote.GetIntentRef())),
		zap.String("quote_state", strings.TrimSpace(computedQuote.GetState().String())),
		zap.String("block_reason", strings.TrimSpace(computedQuote.GetBlockReason().String())),
	)

	response := &quotationv2.QuotePaymentResponse{
		Quote:          batchOut.Items[0].Quote,
		IdempotencyKey: strings.TrimSpace(requestCtx.IdempotencyKey),
	}

	detail, ok := collector.Get(0)
	if !ok || detail == nil {
		return nil, merrors.InvalidArgument("single processing detail is required")
	}

	result := &QuotePaymentResult{
		Response: response,
	}
	if requestCtx.PreviewOnly {
		previewQuote := response.GetQuote()
		logger.Info("ProcessQuotePayment preview completed",
			zap.Duration("elapsed", time.Since(startedAt)),
			zap.String("quote_ref", strings.TrimSpace(previewQuote.GetQuoteRef())),
			zap.String("intent_ref", strings.TrimSpace(previewQuote.GetIntentRef())),
			zap.String("quote_state", strings.TrimSpace(previewQuote.GetState().String())),
		)
		logger.Info("QuotePayment response ready",
			zap.Duration("elapsed", time.Since(startedAt)),
			zap.String("quote_ref", strings.TrimSpace(previewQuote.GetQuoteRef())),
			zap.String("intent_ref", strings.TrimSpace(previewQuote.GetIntentRef())),
			zap.String("quote_state", strings.TrimSpace(previewQuote.GetState().String())),
			zap.String("block_reason", strings.TrimSpace(previewQuote.GetBlockReason().String())),
			zap.String("idempotency_key", strings.TrimSpace(response.GetIdempotencyKey())),
		)
		return result, nil
	}

	expiresAt := detail.ExpiresAt
	if expiresAt.IsZero() {
		logger.Warn("ProcessQuotePayment 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.QuoteRequestShapeSingle,
		Items: []quote_persistence_service.PersistItemInput{
			{
				Intent: pointerTo(detail.Intent),
				Quote:  quoteSnapshotFromComputed(detail.Quote),
				Status: statusInputFromStatus(detail.Status),
			},
		},
	})
	if err != nil {
		logger.Warn("ProcessQuotePayment 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.QuoteShapeSingle,
		},
	})
	if err != nil {
		logger.Warn("ProcessQuotePayment 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("ProcessQuotePayment reused concurrent idempotent record",
			zap.String("quote_ref", strings.TrimSpace(stored.QuoteRef)),
			zap.Duration("elapsed", time.Since(startedAt)),
		)
		reusedResult, mapErr := s.singleResultFromRecord(stored)
		if mapErr != nil {
			logger.Warn("ProcessQuotePayment failed to map reused concurrent record", zap.Error(mapErr))
			return nil, mapErr
		}
		reusedResponse := reusedResult.Response
		reusedQuote := reusedResponse.GetQuote()
		logger.Info("QuotePayment response ready",
			zap.Duration("elapsed", time.Since(startedAt)),
			zap.String("quote_ref", strings.TrimSpace(reusedQuote.GetQuoteRef())),
			zap.String("intent_ref", strings.TrimSpace(reusedQuote.GetIntentRef())),
			zap.String("quote_state", strings.TrimSpace(reusedQuote.GetState().String())),
			zap.String("block_reason", strings.TrimSpace(reusedQuote.GetBlockReason().String())),
			zap.String("idempotency_key", strings.TrimSpace(reusedResponse.GetIdempotencyKey())),
		)
		return reusedResult, nil
	}
	result.Record = stored
	logger.Info("ProcessQuotePayment persisted quote",
		zap.String("quote_ref", strings.TrimSpace(stored.QuoteRef)),
		zap.Time("expires_at", stored.ExpiresAt),
		zap.Duration("elapsed", time.Since(startedAt)),
	)
	logger.Info("QuotePayment response ready",
		zap.Duration("elapsed", time.Since(startedAt)),
		zap.String("quote_ref", strings.TrimSpace(response.GetQuote().GetQuoteRef())),
		zap.String("intent_ref", strings.TrimSpace(response.GetQuote().GetIntentRef())),
		zap.String("quote_state", strings.TrimSpace(response.GetQuote().GetState().String())),
		zap.String("block_reason", strings.TrimSpace(response.GetQuote().GetBlockReason().String())),
		zap.String("idempotency_key", strings.TrimSpace(response.GetIdempotencyKey())),
	)
	return result, nil
}

func pointerTo(intent model.PaymentIntent) *model.PaymentIntent {
	return &intent
}
