package store

import (
	"context"
	"errors"
	"fmt"
	"time"

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

// Optimistic-concurrency errors. HTTP handlers translate ErrVersionConflict
// into a 409 with a "conflict" envelope (and the QoL reload toast).
var (
	ErrNotFound        = errors.New("document not found")
	ErrVersionConflict = errors.New("version conflict")
)

// UpdateVersioned applies update to the document only if its version still
// matches, bumping version and updatedAt atomically. update uses normal
// operators ($set/$push/...); the helper merges its bookkeeping into them.
func UpdateVersioned(ctx context.Context, coll *mongo.Collection, id string, version int64, update bson.M) error {
	merged := bson.M{}
	for op, fields := range update {
		merged[op] = fields
	}
	set, _ := merged["$set"].(bson.M)
	if set == nil {
		set = bson.M{}
	}
	set["updatedAt"] = time.Now().UTC()
	merged["$set"] = set
	inc, _ := merged["$inc"].(bson.M)
	if inc == nil {
		inc = bson.M{}
	}
	if _, clash := inc["version"]; clash {
		return fmt.Errorf("update must not touch version itself")
	}
	inc["version"] = int64(1)
	merged["$inc"] = inc

	res, err := coll.UpdateOne(ctx, bson.M{"_id": id, "version": version}, merged)
	if err != nil {
		return fmt.Errorf("versioned update %s/%s: %w", coll.Name(), id, err)
	}
	if res.MatchedCount == 1 {
		return nil
	}

	// Distinguish "gone" from "someone got there first".
	n, err := coll.CountDocuments(ctx, bson.M{"_id": id})
	if err != nil {
		return fmt.Errorf("versioned update %s/%s: existence check: %w", coll.Name(), id, err)
	}
	if n == 0 {
		return fmt.Errorf("%s/%s: %w", coll.Name(), id, ErrNotFound)
	}
	return fmt.Errorf("%s/%s at version %d: %w", coll.Name(), id, version, ErrVersionConflict)
}

// InsertOne inserts a document map, stamping createdAt/updatedAt/version so
// every collection follows the §4 conventions.
func InsertOne(ctx context.Context, coll *mongo.Collection, id string, doc bson.M) error {
	now := time.Now().UTC()
	doc["_id"] = id
	doc["createdAt"] = now
	doc["updatedAt"] = now
	doc["version"] = int64(1)
	if _, err := coll.InsertOne(ctx, doc); err != nil {
		return fmt.Errorf("insert %s/%s: %w", coll.Name(), id, err)
	}
	return nil
}
