package paymentapiimp

import (
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/tech/sendico/pkg/api/http/response"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mutil/mzap"
	paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
	orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
	"github.com/tech/sendico/server/interface/api/sresponse"
	mutil "github.com/tech/sendico/server/internal/mutil/param"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/timestamppb"
)

const maxInt32 = int64(1<<31 - 1)

func (a *PaymentAPI) listPayments(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
	orgRef, err := a.oph.GetRef(r)
	if err != nil {
		a.logger.Warn("Failed to parse organization reference for payments list", zap.Error(err), mutil.PLog(a.oph, r))
		return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
	}

	ctx := r.Context()
	allowed, err := a.enf.Enforce(ctx, a.permissionRef, account.ID, orgRef, bson.NilObjectID, model.ActionRead)
	if err != nil {
		a.logger.Warn("Failed to check payments access permissions", zap.Error(err), mutil.PLog(a.oph, r))
		return response.Auto(a.logger, a.Name(), err)
	}
	if !allowed {
		a.logger.Debug("Access denied when listing payments", mutil.PLog(a.oph, r))
		return response.AccessDenied(a.logger, a.Name(), "payments read permission denied")
	}

	req := &orchestrationv2.ListPaymentsRequest{Meta: requestMeta(orgRef.Hex(), "")}

	if page, err := listPaymentsPage(r); err != nil {
		return response.Auto(a.logger, a.Name(), err)
	} else if page != nil {
		req.Page = page
	}

	query := r.URL.Query()
	if quotationRef := firstNonEmpty(query.Get("quotation_ref"), query.Get("quote_ref")); quotationRef != "" {
		req.QuotationRef = quotationRef
	}
	createdFrom, err := parseRFC3339Timestamp(firstNonEmpty(query.Get("created_from"), query.Get("createdFrom")), "created_from")
	if err != nil {
		return response.Auto(a.logger, a.Name(), err)
	}
	if createdFrom != nil {
		req.CreatedFrom = createdFrom
	}
	createdTo, err := parseRFC3339Timestamp(firstNonEmpty(query.Get("created_to"), query.Get("createdTo")), "created_to")
	if err != nil {
		return response.Auto(a.logger, a.Name(), err)
	}
	if createdTo != nil {
		req.CreatedTo = createdTo
	}
	if req.GetCreatedFrom() != nil && req.GetCreatedTo() != nil {
		if !req.GetCreatedTo().AsTime().After(req.GetCreatedFrom().AsTime()) {
			return response.Auto(a.logger, a.Name(), merrors.InvalidArgument("created_to must be after created_from", "created_to"))
		}
	}

	if states, err := parsePaymentStateFilters(r); err != nil {
		return response.Auto(a.logger, a.Name(), err)
	} else if len(states) > 0 {
		req.States = states
	}

	resp, err := a.execution.ListPayments(ctx, req)
	if err != nil {
		a.logger.Warn("Failed to list payments", zap.Error(err), mzap.ObjRef("organization_ref", orgRef))
		return grpcErrorResponse(a.logger, a.Name(), err)
	}

	return sresponse.PaymentsListResponse(a.logger, resp, token)
}

func listPaymentsPage(r *http.Request) (*paginationv1.CursorPageRequest, error) {
	query := r.URL.Query()
	cursor := strings.TrimSpace(query.Get("cursor"))
	limitRaw := strings.TrimSpace(query.Get("limit"))

	var limit int64
	hasLimit := false
	if limitRaw != "" {
		parsed, err := strconv.ParseInt(limitRaw, 10, 32)
		if err != nil {
			return nil, merrors.InvalidArgument("invalid limit", "limit")
		}
		limit = parsed
		hasLimit = true
	}

	if cursor == "" && !hasLimit {
		//nolint:nilnil // Absent pagination params mean no page request should be sent downstream.
		return nil, nil
	}

	page := &paginationv1.CursorPageRequest{
		Cursor: cursor,
	}
	if hasLimit {
		if limit < 0 {
			limit = 0
		} else if limit > maxInt32 {
			limit = maxInt32
		}
		page.Limit = int32(limit)
	}

	return page, nil
}

func parsePaymentStateFilters(r *http.Request) ([]orchestrationv2.OrchestrationState, error) {
	query := r.URL.Query()
	values := append([]string{}, query["state"]...)
	values = append(values, query["states"]...)
	values = append(values, query["filter_states"]...)
	if len(values) == 0 {
		return nil, nil
	}

	states := make([]orchestrationv2.OrchestrationState, 0, len(values))
	for _, raw := range values {
		for _, part := range strings.Split(raw, ",") {
			trimmed := strings.TrimSpace(part)
			if trimmed == "" {
				continue
			}
			state, ok := orchestrationStateFromString(trimmed)
			if !ok {
				return nil, merrors.InvalidArgument("unsupported payment state: "+trimmed, "state")
			}
			states = append(states, state)
		}
	}

	if len(states) == 0 {
		return nil, nil
	}
	return states, nil
}

func orchestrationStateFromString(value string) (orchestrationv2.OrchestrationState, bool) {
	upper := strings.ToUpper(strings.TrimSpace(value))
	if upper == "" {
		return 0, false
	}
	switch upper {
	case "PAYMENT_STATE_ACCEPTED", "ACCEPTED":
		return orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_CREATED, true
	case "PAYMENT_STATE_FUNDS_RESERVED", "FUNDS_RESERVED", "PAYMENT_STATE_SUBMITTED", "SUBMITTED":
		return orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_EXECUTING, true
	case "PAYMENT_STATE_SETTLED", "SETTLED":
		return orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_SETTLED, true
	case "PAYMENT_STATE_FAILED", "FAILED", "PAYMENT_STATE_CANCELLED", "CANCELLED":
		return orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_FAILED, true
	}
	if !strings.HasPrefix(upper, "ORCHESTRATION_STATE_") {
		upper = "ORCHESTRATION_STATE_" + upper
	}
	enumValue, ok := orchestrationv2.OrchestrationState_value[upper]
	if !ok {
		return 0, false
	}
	return orchestrationv2.OrchestrationState(enumValue), true
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed != "" {
			return trimmed
		}
	}
	return ""
}

func parseRFC3339Timestamp(raw string, field string) (*timestamppb.Timestamp, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		//nolint:nilnil // Empty timestamp filter is represented as (nil, nil).
		return nil, nil
	}
	parsed, err := time.Parse(time.RFC3339, trimmed)
	if err != nil {
		return nil, merrors.InvalidArgument("invalid "+field+", expected RFC3339", field)
	}
	return timestamppb.New(parsed), nil
}
