package planmanage

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

const cursorVersion = 1

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

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

type versionCursorWire struct {
	Version     int       `json:"v"`
	PublishedAt time.Time `json:"publishedAt"`
	ID          string    `json:"id"`
}

type decodedVersionCursor struct {
	publishedAt time.Time
	id          string
}

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

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

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

func decodeVersionCursor(raw string) (decodedVersionCursor, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return decodedVersionCursor{}, nil
	}
	payload, err := base64.RawURLEncoding.DecodeString(raw)
	if err != nil {
		return decodedVersionCursor{}, invalidRequest("version cursor is invalid")
	}
	var wire versionCursorWire
	if err := json.Unmarshal(payload, &wire); err != nil || wire.Version != cursorVersion || wire.PublishedAt.IsZero() || strings.TrimSpace(wire.ID) == "" {
		return decodedVersionCursor{}, invalidRequest("version cursor is invalid")
	}
	return decodedVersionCursor{publishedAt: wire.PublishedAt.UTC(), id: strings.TrimSpace(wire.ID)}, nil
}
