package pagination

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

	"gorm.io/gorm"
)

type Cursor struct {
	Timestamp time.Time `json:"timestamp"`
	ID        string    `json:"id"`
}

func Encode(timestamp time.Time, id string) string {
	if timestamp.IsZero() || strings.TrimSpace(id) == "" {
		return ""
	}
	payload, err := json.Marshal(Cursor{
		Timestamp: timestamp.UTC(),
		ID:        id,
	})
	if err != nil {
		return ""
	}
	return base64.RawURLEncoding.EncodeToString(payload)
}

func Decode(raw string) (*Cursor, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, nil
	}
	decoded, err := base64.RawURLEncoding.DecodeString(raw)
	if err != nil {
		return nil, err
	}
	var cursor Cursor
	if err := json.Unmarshal(decoded, &cursor); err != nil {
		return nil, err
	}
	if cursor.Timestamp.IsZero() || strings.TrimSpace(cursor.ID) == "" {
		return nil, errors.New("invalid cursor")
	}
	return &cursor, nil
}

func ComesAfter(timestamp time.Time, id string, cursor *Cursor) bool {
	if cursor == nil {
		return true
	}
	if timestamp.Before(cursor.Timestamp) {
		return true
	}
	return timestamp.Equal(cursor.Timestamp) && id < cursor.ID
}

func SortByKeyDesc[T any](items []T, key func(T) (time.Time, string)) {
	sort.SliceStable(items, func(i, j int) bool {
		leftTime, leftID := key(items[i])
		rightTime, rightID := key(items[j])
		if !leftTime.Equal(rightTime) {
			return leftTime.After(rightTime)
		}
		return leftID > rightID
	})
}

func PageByCursor[T any](items []T, limit int, cursorRaw string, key func(T) (time.Time, string)) ([]T, string, error) {
	cursor, err := Decode(cursorRaw)
	if err != nil {
		return nil, "", err
	}
	filtered := make([]T, 0, len(items))
	for _, item := range items {
		timestamp, id := key(item)
		if ComesAfter(timestamp, id, cursor) {
			filtered = append(filtered, item)
		}
	}
	if len(filtered) <= limit {
		return filtered, "", nil
	}
	page := filtered[:limit]
	lastTimestamp, lastID := key(page[len(page)-1])
	return page, Encode(lastTimestamp, lastID), nil
}

func ApplyCreatedAtIDCursor(db *gorm.DB, cursor *Cursor, createdAtColumn string, idColumn string) *gorm.DB {
	if cursor == nil {
		return db
	}
	return db.Where(
		createdAtColumn+" < ? OR ("+createdAtColumn+" = ? AND "+idColumn+" < ?)",
		cursor.Timestamp,
		cursor.Timestamp,
		cursor.ID,
	)
}
