package reqval

import (
	"regexp"
	"strings"
	"time"

	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

const (
	maxIdempotencyKeyLen = 256
	maxQuotationRefLen   = 128
	maxClientRefLen      = 128
)

var refTokenRe = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:/-]*$`)

type svc struct {
	logger mlogger.Logger
}

func (s *svc) Validate(req *Req) (out *Ctx, err error) {
	logger := s.logger
	orgRefIn := ""
	if req != nil && req.Meta != nil {
		orgRefIn = strings.TrimSpace(req.Meta.OrganizationRef)
	}
	logger.Debug("Starting Validate",
		zap.String("organization_ref", orgRefIn),
		zap.String("quotation_ref", strings.TrimSpace(valueOrEmpty(req, func(v *Req) string { return v.QuotationRef }))),
		zap.Bool("has_idempotency_key", strings.TrimSpace(traceKey(req)) != ""),
	)
	defer func(start time.Time) {
		fields := []zap.Field{zap.Int64("duration_ms", time.Since(start).Milliseconds())}
		if out != nil {
			fields = append(fields,
				zap.String("organization_ref", out.OrganizationRef),
				zap.String("quotation_ref", out.QuotationRef),
				zap.Bool("has_client_payment_ref", out.ClientPaymentRef != ""),
			)
		}
		if err != nil {
			logger.Warn("Failed to validate", append(fields, zap.Error(err))...)
			return
		}
		logger.Debug("Completed Validate", fields...)
	}(time.Now())

	if req == nil {
		return nil, merrors.InvalidArgument("request is required")
	}
	if req.Meta == nil {
		return nil, merrors.InvalidArgument("meta is required")
	}

	orgRef := strings.TrimSpace(req.Meta.OrganizationRef)
	if orgRef == "" {
		return nil, merrors.InvalidArgument("meta.organization_ref is required")
	}
	orgID, err := bson.ObjectIDFromHex(orgRef)
	if err != nil {
		return nil, merrors.InvalidArgument("meta.organization_ref must be a valid objectID")
	}

	if req.Meta.Trace == nil {
		return nil, merrors.InvalidArgument("meta.trace is required")
	}

	idempotencyKey, err := validateRefToken("meta.trace.idempotency_key", req.Meta.Trace.IdempotencyKey, maxIdempotencyKeyLen, true)
	if err != nil {
		return nil, err
	}

	quotationRef, err := validateRefToken("quotation_ref", req.QuotationRef, maxQuotationRefLen, true)
	if err != nil {
		return nil, err
	}

	clientPaymentRef, err := validateRefToken("client_payment_ref", req.ClientPaymentRef, maxClientRefLen, false)
	if err != nil {
		return nil, err
	}

	out = &Ctx{
		OrganizationRef:  orgRef,
		OrganizationID:   orgID,
		IdempotencyKey:   idempotencyKey,
		QuotationRef:     quotationRef,
		ClientPaymentRef: clientPaymentRef,
	}
	return out, nil
}

func validateRefToken(field, value string, maxLen int, required bool) (string, error) {
	normalized := strings.TrimSpace(value)
	if normalized == "" {
		if required {
			return "", merrors.InvalidArgument(field + " is required")
		}
		return "", nil
	}
	if maxLen > 0 && len(normalized) > maxLen {
		return "", merrors.InvalidArgument(field + " is too long")
	}
	if !refTokenRe.MatchString(normalized) {
		return "", merrors.InvalidArgument(field + " has invalid format")
	}
	return normalized, nil
}

func valueOrEmpty(req *Req, getter func(*Req) string) string {
	if req == nil || getter == nil {
		return ""
	}
	return getter(req)
}

func traceKey(req *Req) string {
	if req == nil || req.Meta == nil || req.Meta.Trace == nil {
		return ""
	}
	return req.Meta.Trace.IdempotencyKey
}
