package store

import (
	"context"
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo/options"

	"bountyboard/internal/ulid"
)

// AuditEntry records one privileged mutation (§4.8).
type AuditEntry struct {
	ID       string         `bson:"_id" json:"id"`
	At       time.Time      `bson:"at" json:"at"`
	ActorID  string         `bson:"actorId" json:"actorId"`
	ActorIP  string         `bson:"actorIp" json:"actorIp"`
	Action   string         `bson:"action" json:"action"`
	Entity   string         `bson:"entity" json:"entity"`
	EntityID string         `bson:"entityId" json:"entityId"`
	Diff     map[string]any `bson:"diff,omitempty" json:"diff,omitempty"`
}

// Audit appends to the audit log; failures are returned for logging but must
// never abort the mutation they describe.
func (s *Store) Audit(ctx context.Context, e AuditEntry) error {
	e.ID = ulid.New()
	e.At = time.Now().UTC()
	if _, err := s.DB.Collection("auditLog").InsertOne(ctx, e); err != nil {
		return fmt.Errorf("insert audit entry: %w", err)
	}
	return nil
}

// ListAudit returns newest-first entries with optional filters and ULID
// cursor pagination.
func (s *Store) ListAudit(ctx context.Context, actorID, entityID, cursor string, limit int) ([]AuditEntry, error) {
	if limit <= 0 || limit > 200 {
		limit = 50
	}
	filter := bson.M{}
	if actorID != "" {
		filter["actorId"] = actorID
	}
	if entityID != "" {
		filter["entityId"] = entityID
	}
	if cursor != "" {
		filter["_id"] = bson.M{"$lt": cursor}
	}
	cur, err := s.DB.Collection("auditLog").Find(ctx, filter,
		options.Find().SetSort(bson.D{{Key: "_id", Value: -1}}).SetLimit(int64(limit)))
	if err != nil {
		return nil, fmt.Errorf("list audit: %w", err)
	}
	out := []AuditEntry{}
	if err := cur.All(ctx, &out); err != nil {
		return nil, fmt.Errorf("decode audit: %w", err)
	}
	return out, nil
}
