package bindingmanage

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

const cursorVersion = 1

type bindingCursorWire struct {
	Version   int       `json:"v"`
	UpdatedAt time.Time `json:"updatedAt"`
	ID        string    `json:"id"`
}

type decodedBindingCursor struct {
	updatedAt *time.Time
	id        string
}

type revisionCursorWire struct {
	Version        int   `json:"v"`
	BeforeRevision int64 `json:"beforeRevision"`
}

func encodeBindingCursor(updatedAt time.Time, id string) (string, error) {
	id = strings.TrimSpace(id)
	if updatedAt.IsZero() || id == "" {
		return "", invalidRequest("cannot encode an empty Binding cursor")
	}
	payload, err := json.Marshal(bindingCursorWire{Version: cursorVersion, UpdatedAt: updatedAt.UTC(), ID: id})
	if err != nil {
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(payload), nil
}

func decodeBindingCursor(raw string) (decodedBindingCursor, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return decodedBindingCursor{}, nil
	}
	payload, err := base64.RawURLEncoding.DecodeString(raw)
	if err != nil {
		return decodedBindingCursor{}, invalidRequest("cursor is invalid")
	}
	var wire bindingCursorWire
	if err := json.Unmarshal(payload, &wire); err != nil || wire.Version != cursorVersion || wire.UpdatedAt.IsZero() || strings.TrimSpace(wire.ID) == "" {
		return decodedBindingCursor{}, invalidRequest("cursor is invalid")
	}
	timestamp := wire.UpdatedAt.UTC()
	return decodedBindingCursor{updatedAt: &timestamp, id: strings.TrimSpace(wire.ID)}, nil
}

func encodeRevisionCursor(beforeRevision int64) (string, error) {
	if beforeRevision < 1 {
		return "", invalidRequest("cannot encode an empty Binding revision cursor")
	}
	payload, err := json.Marshal(revisionCursorWire{Version: cursorVersion, BeforeRevision: beforeRevision})
	if err != nil {
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(payload), nil
}

func decodeRevisionCursor(raw string) (int64, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return 0, nil
	}
	payload, err := base64.RawURLEncoding.DecodeString(raw)
	if err != nil {
		return 0, invalidRequest("revision cursor is invalid")
	}
	var wire revisionCursorWire
	if err := json.Unmarshal(payload, &wire); err != nil || wire.Version != cursorVersion || wire.BeforeRevision < 1 {
		return 0, invalidRequest("revision cursor is invalid")
	}
	return wire.BeforeRevision, nil
}
