package mongo

import (
	"context"
	"testing"
	"time"

	"github.com/shopspring/decimal"
	"github.com/zeromicro/go-zero/core/stores/cache"
	"go.mongodb.org/mongo-driver/v2/bson"
)

func TestDocumentDBWithCache_MustDocumentDBWithCache(t *testing.T) {
	// Test with valid config
	conf := &Conf{
		Host:     "localhost:27017",
		Database: "testdb",
	}

	collection := "testcollection"
	cacheConf := cache.CacheConf{}

	// This will panic if MongoDB is not available, so we need to handle it
	defer func() {
		if r := recover(); r != nil {
			t.Logf("Expected panic in test environment: %v", r)
		}
	}()

	db, err := MustDocumentDBWithCache(conf, collection, cacheConf, nil, nil)
	if err != nil {
		t.Logf("MongoDB connection failed (expected in test environment): %v", err)
		return
	}

	if db == nil {
		t.Error("Expected DocumentDBWithCache to be non-nil")
	}
}

func TestDocumentDBWithCache_CacheOperations(t *testing.T) {
	conf := &Conf{
		Host:     "localhost:27017",
		Database: "testdb",
	}

	collection := "testcollection"
	cacheConf := cache.CacheConf{}

	ctx := context.Background()
	db, err := MustDocumentDBWithCache(conf, collection, cacheConf, nil, nil)

	if err != nil {
		t.Skip("Skipping test - MongoDB not available")
		return
	}

	// Test cache operations
	key := "test-key"
	value := "test-value"

	// Test SetCache
	err = db.SetCache(key, value)
	if err != nil {
		t.Errorf("Failed to set cache: %v", err)
	}

	// Test GetCache
	var cachedValue string
	err = db.GetCache(key, &cachedValue)
	if err != nil {
		t.Errorf("Failed to get cache: %v", err)
	}

	if cachedValue != value {
		t.Errorf("Expected cached value %s, got %s", value, cachedValue)
	}

	// Test DelCache
	err = db.DelCache(ctx, key)
	if err != nil {
		t.Errorf("Failed to delete cache: %v", err)
	}
}

func TestDocumentDBWithCache_CRUDOperations(t *testing.T) {
	conf := &Conf{
		Host:     "localhost:27017",
		Database: "testdb",
	}

	collection := "testcollection"
	cacheConf := cache.CacheConf{}

	ctx := context.Background()
	db, err := MustDocumentDBWithCache(conf, collection, cacheConf, nil, nil)

	if err != nil {
		t.Skip("Skipping test - MongoDB not available")
		return
	}

	// Test data
	testDoc := bson.M{
		"name":  "test",
		"value": 123,
		"price": decimal.NewFromFloat(99.99),
	}

	// Test InsertOne
	result, err := db.InsertOne(ctx, collection, testDoc)
	if err != nil {
		t.Errorf("Failed to insert document: %v", err)
	}

	insertedID := result.InsertedID
	if insertedID == nil {
		t.Error("Expected inserted ID to be non-nil")
	}

	// Test FindOne
	var foundDoc bson.M
	err = db.FindOne(ctx, collection, bson.M{"_id": insertedID}, &foundDoc)
	if err != nil {
		t.Errorf("Failed to find document: %v", err)
	}

	if foundDoc["name"] != "test" {
		t.Errorf("Expected name 'test', got %v", foundDoc["name"])
	}

	// Test UpdateOne
	update := bson.M{"$set": bson.M{"value": 456}}
	updateResult, err := db.UpdateOne(ctx, collection, bson.M{"_id": insertedID}, update)
	if err != nil {
		t.Errorf("Failed to update document: %v", err)
	}

	if updateResult.ModifiedCount != 1 {
		t.Errorf("Expected 1 modified document, got %d", updateResult.ModifiedCount)
	}

	// Test UpdateByID
	updateByID := bson.M{"$set": bson.M{"value": 789}}
	updateByIDResult, err := db.UpdateByID(ctx, collection, insertedID, updateByID)
	if err != nil {
		t.Errorf("Failed to update document by ID: %v", err)
	}

	if updateByIDResult.ModifiedCount != 1 {
		t.Errorf("Expected 1 modified document, got %d", updateByIDResult.ModifiedCount)
	}

	// Test UpdateMany
	updateMany := bson.M{"$set": bson.M{"updated": true}}
	updateManyResult, err := db.UpdateMany(ctx, []string{collection}, bson.M{"_id": insertedID}, updateMany)
	if err != nil {
		t.Errorf("Failed to update many documents: %v", err)
	}

	if updateManyResult.ModifiedCount != 1 {
		t.Errorf("Expected 1 modified document, got %d", updateManyResult.ModifiedCount)
	}

	// Test FindOneAndReplace
	replacement := bson.M{
		"name":  "replaced",
		"value": 999,
		"price": decimal.NewFromFloat(199.99),
	}

	var replacedDoc bson.M
	err = db.FindOneAndReplace(ctx, collection, bson.M{"_id": insertedID}, replacement, &replacedDoc)
	if err != nil {
		t.Errorf("Failed to find and replace document: %v", err)
	}

	// Test FindOneAndDelete
	var deletedDoc bson.M
	err = db.FindOneAndDelete(ctx, collection, bson.M{"_id": insertedID}, &deletedDoc)
	if err != nil {
		t.Errorf("Failed to find and delete document: %v", err)
	}

	// Test DeleteOne
	deleteResult, err := db.DeleteOne(ctx, collection, bson.M{"_id": insertedID})
	if err != nil {
		t.Errorf("Failed to delete document: %v", err)
	}

	if deleteResult != 0 { // Should be 0 since we already deleted it
		t.Errorf("Expected 0 deleted documents, got %d", deleteResult)
	}
}

func TestDocumentDBWithCache_MustModelCache(t *testing.T) {
	conf := &Conf{
		Host:     "localhost:27017",
		Database: "testdb",
	}

	collection := "testcollection"
	cacheConf := cache.CacheConf{}

	db, err := MustDocumentDBWithCache(conf, collection, cacheConf, nil, nil)

	if err != nil {
		t.Skip("Skipping test - MongoDB not available")
		return
	}

	// Test that we got a valid DocumentDBWithCache
	if db == nil {
		t.Error("Expected DocumentDBWithCache to be non-nil")
	}
}

func TestDocumentDBWithCache_ErrorHandling(t *testing.T) {
	// Test with invalid config
	invalidConf := &Conf{
		Host: "invalid-host:99999",
	}

	collection := "testcollection"
	cacheConf := cache.CacheConf{}

	_, err := MustDocumentDBWithCache(invalidConf, collection, cacheConf, nil, nil)

	// This should fail
	if err == nil {
		t.Error("Expected error with invalid host, got nil")
	}
}

func TestDocumentDBWithCache_ContextHandling(t *testing.T) {
	conf := &Conf{
		Host:     "localhost:27017",
		Database: "testdb",
	}

	collection := "testcollection"
	cacheConf := cache.CacheConf{}

	// Test with timeout context
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	db, err := MustDocumentDBWithCache(conf, collection, cacheConf, nil, nil)

	// Use ctx to avoid unused variable warning
	_ = ctx

	if err != nil {
		t.Logf("MongoDB connection failed (expected in test environment): %v", err)
		return
	}

	if db == nil {
		t.Error("Expected DocumentDBWithCache to be non-nil")
	}
}

func TestDocumentDBWithCache_WithDecimalValues(t *testing.T) {
	conf := &Conf{
		Host:     "localhost:27017",
		Database: "testdb",
	}

	collection := "testcollection"
	cacheConf := cache.CacheConf{}

	ctx := context.Background()
	db, err := MustDocumentDBWithCache(conf, collection, cacheConf, nil, nil)

	if err != nil {
		t.Skip("Skipping test - MongoDB not available")
		return
	}

	// Test with decimal values
	testDoc := bson.M{
		"name":   "decimal-test",
		"price":  decimal.NewFromFloat(123.45),
		"amount": decimal.NewFromFloat(999.99),
	}

	// Insert document with decimal values
	result, err := db.InsertOne(ctx, collection, testDoc)
	if err != nil {
		t.Errorf("Failed to insert document with decimal values: %v", err)
	}

	insertedID := result.InsertedID

	// Find document with decimal values
	var foundDoc bson.M
	err = db.FindOne(ctx, collection, bson.M{"_id": insertedID}, &foundDoc)
	if err != nil {
		t.Errorf("Failed to find document with decimal values: %v", err)
	}

	// Verify decimal values
	if foundDoc["name"] != "decimal-test" {
		t.Errorf("Expected name 'decimal-test', got %v", foundDoc["name"])
	}

	// Clean up
	_, err = db.DeleteOne(ctx, collection, bson.M{"_id": insertedID})
	if err != nil {
		t.Errorf("Failed to clean up document: %v", err)
	}
}

func TestDocumentDBWithCache_WithObjectID(t *testing.T) {
	conf := &Conf{
		Host:     "localhost:27017",
		Database: "testdb",
	}

	collection := "testcollection"
	cacheConf := cache.CacheConf{}

	ctx := context.Background()
	db, err := MustDocumentDBWithCache(conf, collection, cacheConf, nil, nil)

	if err != nil {
		t.Skip("Skipping test - MongoDB not available")
		return
	}

	// Test with ObjectID
	objectID := bson.NewObjectID()
	testDoc := bson.M{
		"_id":   objectID,
		"name":  "objectid-test",
		"value": 123,
	}

	// Insert document with ObjectID
	result, err := db.InsertOne(ctx, collection, testDoc)
	if err != nil {
		t.Errorf("Failed to insert document with ObjectID: %v", err)
	}

	insertedID := result.InsertedID

	// Verify ObjectID
	if insertedID != objectID {
		t.Errorf("Expected ObjectID %v, got %v", objectID, insertedID)
	}

	// Find document by ObjectID
	var foundDoc bson.M
	err = db.FindOne(ctx, collection, bson.M{"_id": objectID}, &foundDoc)
	if err != nil {
		t.Errorf("Failed to find document by ObjectID: %v", err)
	}

	// Clean up
	_, err = db.DeleteOne(ctx, collection, bson.M{"_id": objectID})
	if err != nil {
		t.Errorf("Failed to clean up document: %v", err)
	}
}
