//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/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/modules/mongodb"
	"github.com/testcontainers/testcontainers-go/wait"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

func TestMongoRepository_Upsert(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")
	repo := repositoryimp.NewMongoRepository(db, "testcollection")

	t.Run("Upsert_InsertsWhenIDIsZero", func(t *testing.T) {
		obj := &TestObject{Name: "inserted-via-upsert"}
		require.True(t, obj.GetID().IsZero())

		err := repo.Upsert(ctx, obj)
		require.NoError(t, err)
		assert.False(t, obj.GetID().IsZero())
		assert.NotZero(t, obj.CreatedAt)
		assert.NotZero(t, obj.UpdatedAt)

		var stored TestObject
		err = repo.Get(ctx, *obj.GetID(), &stored)
		require.NoError(t, err)
		assert.Equal(t, "inserted-via-upsert", stored.Name)
	})

	t.Run("Upsert_UpdatesExistingDocument", func(t *testing.T) {
		obj := &TestObject{Name: "before-upsert"}
		err := repo.Insert(ctx, obj, nil)
		require.NoError(t, err)

		createdAt := obj.CreatedAt
		updatedAt := obj.UpdatedAt
		time.Sleep(10 * time.Millisecond)

		obj.Name = "after-upsert"
		err = repo.Upsert(ctx, obj)
		require.NoError(t, err)

		assert.Equal(t, createdAt, obj.CreatedAt)
		assert.True(t, obj.UpdatedAt.After(updatedAt))

		var stored TestObject
		err = repo.Get(ctx, *obj.GetID(), &stored)
		require.NoError(t, err)
		assert.Equal(t, "after-upsert", stored.Name)
		assert.WithinDuration(t, createdAt, stored.CreatedAt, time.Second)
		assert.True(t, stored.UpdatedAt.After(updatedAt))
	})

	t.Run("Upsert_InsertsWhenIDDoesNotExist", func(t *testing.T) {
		obj := &TestObject{Name: "upsert-with-preassigned-id"}
		obj.SetID(bson.NewObjectID())

		err := repo.Upsert(ctx, obj)
		require.NoError(t, err)

		var stored TestObject
		err = repo.Get(ctx, *obj.GetID(), &stored)
		require.NoError(t, err)
		assert.Equal(t, "upsert-with-preassigned-id", stored.Name)
		assert.Equal(t, *obj.GetID(), stored.ID)
	})
}
