// Package mongodb provides MongoDB client functionality for the operator
package mongodb

import (
	"context"
	"fmt"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	ctrl "sigs.k8s.io/controller-runtime"
)

// Common errors
var (
	ErrDeploymentNotFound = fmt.Errorf("deployment not found")
)

const (
	DatabaseName   = "container-mom"
	CollectionName = "deployments"
)

// Client implements the MongoDB interface
type Client struct {
	client *mongo.Client
}

// NewClient creates a new MongoDB client
func NewClient(client *mongo.Client) *Client {
	return &Client{
		client: client,
	}
}

func (c *Client) GetDeployment(ctx context.Context, id string) (*MongoDeployment, error) {
	collection := c.client.Database(DatabaseName).Collection(CollectionName)

	var deployment MongoDeployment
	err := collection.FindOne(ctx, bson.M{"_id": id}).Decode(&deployment)
	if err != nil {
		if err == mongo.ErrNoDocuments {
			return nil, ErrDeploymentNotFound
		}
		return nil, err
	}

	return &deployment, nil
}

func (c *Client) ListDeployments(ctx context.Context) ([]MongoDeployment, error) {
	collection := c.client.Database(DatabaseName).Collection(CollectionName)

	cursor, err := collection.Find(ctx, bson.M{})
	if err != nil {
		return nil, fmt.Errorf("failed to query deployments: %w", err)
	}
	defer func() {
		if err := cursor.Close(ctx); err != nil {
			ctrl.Log.Error(err, "failed to close cursor")
		}
	}()

	var deployments []MongoDeployment
	if err := cursor.All(ctx, &deployments); err != nil {
		return nil, fmt.Errorf("failed to decode deployments: %w", err)
	}

	ctrl.Log.Info("listed deployments from MongoDB",
		"count", len(deployments),
		"collection", collection.Name(),
	)

	return deployments, nil
}

func (c *Client) CreateDeployment(ctx context.Context, deployment *MongoDeployment) error {
	collection := c.client.Database(DatabaseName).Collection(CollectionName)

	_, err := collection.InsertOne(ctx, deployment)
	return err
}

func (c *Client) UpdateDeployment(ctx context.Context, deployment *MongoDeployment) error {
	collection := c.client.Database(DatabaseName).Collection(CollectionName)

	result, err := collection.ReplaceOne(ctx, bson.M{"_id": deployment.ID}, deployment)
	if err != nil {
		return err
	}
	if result.MatchedCount == 0 {
		return ErrDeploymentNotFound
	}
	return nil
}

func (c *Client) DeleteDeployment(ctx context.Context, id string) error {
	collection := c.client.Database(DatabaseName).Collection(CollectionName)

	result, err := collection.DeleteOne(ctx, bson.M{"_id": id})
	if err != nil {
		return err
	}
	if result.DeletedCount == 0 {
		return ErrDeploymentNotFound
	}
	return nil
}

func (c *Client) DeleteAllDeployments(ctx context.Context) error {
	collection := c.client.Database(DatabaseName).Collection(CollectionName)
	_, err := collection.DeleteMany(ctx, bson.M{})
	return err
}

func (c *Client) GetClient() *mongo.Client {
	return c.client
}

func (c *Client) Disconnect(ctx context.Context) error {
	return c.client.Disconnect(ctx)
}

func (c *Client) Database(name string) *mongo.Database {
	return c.client.Database(name)
}
