package psvc

import (
	"encoding/base64"
	"encoding/json"
	"strings"
	"time"

	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/prepo"
	"github.com/tech/sendico/pkg/merrors"
	orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
	sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
	"go.mongodb.org/mongo-driver/v2/bson"
	"google.golang.org/protobuf/types/known/timestamppb"
)

type listCursorPayload struct {
	CreatedAt string `json:"created_at"`
	ID        string `json:"id"`
}

func parseOrganization(meta *sharedv1.RequestMeta) (string, bson.ObjectID, error) {
	if meta == nil {
		return "", bson.NilObjectID, merrors.InvalidArgument("meta is required")
	}
	orgRef := strings.TrimSpace(meta.GetOrganizationRef())
	if orgRef == "" {
		return "", bson.NilObjectID, merrors.InvalidArgument("meta.organization_ref is required")
	}
	orgID, err := bson.ObjectIDFromHex(orgRef)
	if err != nil {
		return "", bson.NilObjectID, merrors.InvalidArgument("meta.organization_ref must be a valid objectID")
	}
	return orgRef, orgID, nil
}

func parseOptionalOrganization(meta *sharedv1.RequestMeta) (string, bson.ObjectID, bool, error) {
	if meta == nil {
		return "", bson.NilObjectID, false, nil
	}
	orgRef := strings.TrimSpace(meta.GetOrganizationRef())
	if orgRef == "" {
		return "", bson.NilObjectID, false, nil
	}
	orgID, err := bson.ObjectIDFromHex(orgRef)
	if err != nil {
		return "", bson.NilObjectID, false, merrors.InvalidArgument("meta.organization_ref must be a valid objectID")
	}
	return orgRef, orgID, true, nil
}

func parsePaymentRef(value string) (string, error) {
	ref := strings.TrimSpace(value)
	if ref == "" {
		return "", merrors.InvalidArgument("payment_ref is required")
	}
	return ref, nil
}

func parseCursor(value string) (*prepo.ListCursor, error) {
	raw := strings.TrimSpace(value)
	if raw == "" {
		return nil, nil //nolint:nilnil // empty cursor means pagination starts from first page
	}
	data, err := base64.RawURLEncoding.DecodeString(raw)
	if err != nil {
		return nil, merrors.InvalidArgument("page.cursor is invalid")
	}
	var payload listCursorPayload
	if err := json.Unmarshal(data, &payload); err != nil {
		return nil, merrors.InvalidArgument("page.cursor is invalid")
	}
	id, err := bson.ObjectIDFromHex(strings.TrimSpace(payload.ID))
	if err != nil {
		return nil, merrors.InvalidArgument("page.cursor is invalid")
	}
	createdAt, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(payload.CreatedAt))
	if err != nil {
		return nil, merrors.InvalidArgument("page.cursor is invalid")
	}
	return &prepo.ListCursor{
		CreatedAt: createdAt.UTC(),
		ID:        id,
	}, nil
}

func formatCursor(cursor *prepo.ListCursor) (string, error) {
	if cursor == nil || cursor.ID.IsZero() || cursor.CreatedAt.IsZero() {
		return "", nil
	}
	data, err := json.Marshal(listCursorPayload{
		CreatedAt: cursor.CreatedAt.UTC().Format(time.RFC3339Nano),
		ID:        cursor.ID.Hex(),
	})
	if err != nil {
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(data), nil
}

func parseCreated(ts *timestamppb.Timestamp, field string) (*time.Time, error) {
	if ts == nil {
		return nil, nil //nolint:nilnil // nil timestamp means the filter boundary is not provided
	}
	if err := ts.CheckValid(); err != nil {
		return nil, merrors.InvalidArgument(field + " is invalid")
	}
	value := ts.AsTime().UTC()
	return &value, nil
}

func mapStates(states []orchestrationv2.OrchestrationState) ([]agg.State, error) {
	if len(states) == 0 {
		return nil, nil //nolint:nilnil // empty state filter means all states are allowed
	}
	out := make([]agg.State, 0, len(states))
	for i := range states {
		state, ok := mapState(states[i])
		if !ok {
			return nil, merrors.InvalidArgument("states contains invalid value")
		}
		out = append(out, state)
	}
	return out, nil
}

func mapState(state orchestrationv2.OrchestrationState) (agg.State, bool) {
	switch state {
	case orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_CREATED:
		return agg.StateCreated, true
	case orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_EXECUTING:
		return agg.StateExecuting, true
	case orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_NEEDS_ATTENTION:
		return agg.StateNeedsAttention, true
	case orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_SETTLED:
		return agg.StateSettled, true
	case orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_FAILED:
		return agg.StateFailed, true
	default:
		return agg.StateUnspecified, false
	}
}
