//go:build integration
// +build integration

package repositoryimp_test

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tech/sendico/pkg/db/internal/mongo/repositoryimp"
	"github.com/tech/sendico/pkg/db/storable"
	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/modules/mongodb"
	"github.com/testcontainers/testcontainers-go/wait"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

func TestMongoRepository_InsertMany(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()

	mongoContainer, err := mongodb.Run(ctx,
		"mongo:latest",
		mongodb.WithUsername("root"),
		mongodb.WithPassword("password"),
		testcontainers.WithWaitStrategy(wait.ForLog("Waiting for connections")),
	)
	require.NoError(t, err, "failed to start MongoDB container")
	defer terminate(ctx, t, mongoContainer)

	mongoURI, err := mongoContainer.ConnectionString(ctx)
	require.NoError(t, err, "failed to get MongoDB connection string")

	clientOptions := options.Client().ApplyURI(mongoURI)
	client, err := mongo.Connect(ctx, clientOptions)
	require.NoError(t, err, "failed to connect to MongoDB")
	defer disconnect(ctx, t, client)

	db := client.Database("testdb")
	repository := repositoryimp.NewMongoRepository(db, "testcollection")

	t.Run("InsertMany_Success", func(t *testing.T) {
		objects := []storable.Storable{
			&TestObject{Name: "test1"},
			&TestObject{Name: "test2"},
			&TestObject{Name: "test3"},
		}

		err := repository.InsertMany(ctx, objects)
		require.NoError(t, err)

		// Verify all objects were inserted and have IDs
		for _, obj := range objects {
			assert.NotNil(t, obj.GetID())
			assert.False(t, obj.GetID().IsZero())

			// Verify we can retrieve each object
			result := &TestObject{}
			err := repository.Get(ctx, *obj.GetID(), result)
			require.NoError(t, err)
			assert.Equal(t, obj.(*TestObject).Name, result.Name)
		}
	})

	t.Run("InsertMany_EmptySlice", func(t *testing.T) {
		objects := []storable.Storable{}

		err := repository.InsertMany(ctx, objects)
		require.NoError(t, err)
	})

	t.Run("InsertMany_WithExistingIDs", func(t *testing.T) {
		id1 := bson.NewObjectID()
		id2 := bson.NewObjectID()

		objects := []storable.Storable{
			&TestObject{Base: storable.Base{ID: id1}, Name: "preassigned1"},
			&TestObject{Base: storable.Base{ID: id2}, Name: "preassigned2"},
		}

		err := repository.InsertMany(ctx, objects)
		require.NoError(t, err)

		// Verify objects were inserted with pre-assigned IDs
		result1 := &TestObject{}
		err = repository.Get(ctx, id1, result1)
		require.NoError(t, err)
		assert.Equal(t, "preassigned1", result1.Name)

		result2 := &TestObject{}
		err = repository.Get(ctx, id2, result2)
		require.NoError(t, err)
		assert.Equal(t, "preassigned2", result2.Name)
	})

	t.Run("InsertMany_MixedTypes", func(t *testing.T) {
		objects := []storable.Storable{
			&TestObject{Name: "test1"},
			&AnotherObject{Description: "desc1"},
			&TestObject{Name: "test2"},
		}

		err := repository.InsertMany(ctx, objects)
		require.NoError(t, err)

		// Verify all objects were inserted
		for _, obj := range objects {
			assert.NotNil(t, obj.GetID())
			assert.False(t, obj.GetID().IsZero())
		}
	})

	t.Run("InsertMany_DuplicateKey", func(t *testing.T) {
		id := bson.NewObjectID()

		// Insert first object
		obj1 := &TestObject{Base: storable.Base{ID: id}, Name: "original"}
		err := repository.Insert(ctx, obj1, nil)
		require.NoError(t, err)

		// Try to insert multiple objects including one with duplicate ID
		objects := []storable.Storable{
			&TestObject{Name: "test1"},
			&TestObject{Base: storable.Base{ID: id}, Name: "duplicate"},
		}

		err = repository.InsertMany(ctx, objects)
		assert.Error(t, err)
		assert.True(t, mongo.IsDuplicateKeyError(err))
	})

	t.Run("InsertMany_UpdateTimestamps", func(t *testing.T) {
		objects := []storable.Storable{
			&TestObject{Name: "test1"},
			&TestObject{Name: "test2"},
		}

		err := repository.InsertMany(ctx, objects)
		require.NoError(t, err)

		// Verify timestamps were set
		for _, obj := range objects {
			testObj := obj.(*TestObject)
			assert.NotZero(t, testObj.CreatedAt)
			assert.NotZero(t, testObj.UpdatedAt)
		}
	})
}
