// Package indexes declares the MongoDB indexes more than one Vantage service
// depends on.
package indexes

import (
	"context"
	"errors"
	"fmt"

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

// legacyUserEmailIndex is the global unique index on users.email that this
// package used to declare. It is dropped on sight.
const legacyUserEmailIndex = "email_1"

// indexNotFound is MongoDB's IndexNotFound error code. Two services booting at
// once can both decide to drop the legacy index; the loser must not treat that
// as a failure.
const indexNotFound = 27

// EnsureCoreIndexes declares the unique indexes on users and instances.
//
// users is unique on (instance_id, email), NOT on email alone. One address is
// one user WITHIN an instance; the same address may hold a user in several
// instances, because an account's people are projected into each instance they
// are granted access to.
//
// This is a security property, not an optimisation, and it is only sufficient
// because every lookup by email is scoped by instance. There is deliberately no
// unscoped lookup by email anywhere in the codebase: an unscoped FindOne would
// return an arbitrary one of several matching users, which on the login path
// means signing someone into a tenant that is not theirs. If you are about to
// add one, you are about to reintroduce that bug.
//
// Creating an index that already exists with the same specification is a no-op,
// so this is safe to call at every boot from every service.
func EnsureCoreIndexes(ctx context.Context, db *mongo.Database) error {
	// Create the replacement BEFORE dropping the legacy index. A failure here
	// leaves the old constraint in place, which is safe; a failure after the
	// drop would leave the collection unconstrained, which is not.
	if _, err := db.Collection("users").Indexes().CreateOne(ctx, mongo.IndexModel{
		Keys:    bson.D{{Key: "instance_id", Value: 1}, {Key: "email", Value: 1}},
		Options: options.Index().SetUnique(true).SetName("instance_email_unique"),
	}); err != nil {
		return fmt.Errorf("users.instance_id+email index: %w", err)
	}

	if err := dropIndexIfExists(ctx, db.Collection("users"), legacyUserEmailIndex); err != nil {
		return fmt.Errorf("drop users.%s: %w", legacyUserEmailIndex, err)
	}

	if _, err := db.Collection("instances").Indexes().CreateOne(ctx, mongo.IndexModel{
		Keys:    bson.D{{Key: "slug", Value: 1}},
		Options: options.Index().SetUnique(true),
	}); err != nil {
		return fmt.Errorf("instances.slug index: %w", err)
	}

	return nil
}

// dropIndexIfExists drops name, treating "it was not there" as success whether
// that is discovered by listing or by racing another service to the drop.
func dropIndexIfExists(ctx context.Context, col *mongo.Collection, name string) error {
	cur, err := col.Indexes().List(ctx)
	if err != nil {
		return err
	}
	var existing []struct {
		Name string `bson:"name"`
	}
	if err := cur.All(ctx, &existing); err != nil {
		return err
	}

	found := false
	for _, i := range existing {
		if i.Name == name {
			found = true
			break
		}
	}
	if !found {
		return nil
	}

	err = col.Indexes().DropOne(ctx, name)
	if err == nil {
		return nil
	}
	var srvErr mongo.ServerError
	if errors.As(err, &srvErr) && srvErr.HasErrorCode(indexNotFound) {
		return nil
	}
	return err
}
