//go:build integration
// +build integration

package archivable

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/tech/sendico/pkg/model"
	"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"
	"go.uber.org/zap"
)

// TestArchivableObject represents a test object with archivable functionality
type TestArchivableObject struct {
	storable.Base        `bson:",inline" json:",inline"`
	model.ArchivableBase `bson:",inline" json:",inline"`
	Name                 string `bson:"name" json:"name"`
}

func (t *TestArchivableObject) Collection() string {
	return "testArchivableObject"
}

func (t *TestArchivableObject) GetArchivable() model.Archivable {
	return &t.ArchivableBase
}

func TestArchivableDB(t *testing.T) {
	ctx := context.Background()

	// Start MongoDB container (stable)
	mongoContainer, err := mongodb.Run(ctx,
		"mongo:latest",
		mongodb.WithUsername("test"),
		mongodb.WithPassword("test"),
		testcontainers.WithWaitStrategy(wait.ForListeningPort("27017/tcp").WithStartupTimeout(2*time.Minute)),
	)
	require.NoError(t, err)
	defer func() {
		termCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()
		if err := mongoContainer.Terminate(termCtx); err != nil {
			t.Logf("Failed to terminate container: %v", err)
		}
	}()

	// Get MongoDB connection string
	mongoURI, err := mongoContainer.ConnectionString(ctx)
	require.NoError(t, err)

	// Connect to MongoDB
	client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
	require.NoError(t, err)
	defer func() {
		if err := client.Disconnect(context.Background()); err != nil {
			t.Logf("Failed to disconnect from MongoDB: %v", err)
		}
	}()

	// Ping the database
	err = client.Ping(ctx, nil)
	require.NoError(t, err)

	// Create repository
	repo := repositoryimp.NewMongoRepository(client.Database("test_"+t.Name()), "testArchivableCollection")

	// Create archivable DB
	archivableDB := NewArchivableDB(
		repo,
		zap.NewNop(),
		func() *TestArchivableObject { return &TestArchivableObject{} },
		func(obj *TestArchivableObject) model.Archivable { return obj.GetArchivable() },
	)

	t.Run("SetArchived_Success", func(t *testing.T) {
		obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: false}}
		err := repo.Insert(ctx, obj, nil)
		require.NoError(t, err)

		err = archivableDB.SetArchived(ctx, obj.ID, true)
		require.NoError(t, err)

		var result TestArchivableObject
		err = repo.Get(ctx, obj.ID, &result)
		require.NoError(t, err)
		assert.True(t, result.IsArchived())
	})

	t.Run("SetArchived_NoChange", func(t *testing.T) {
		obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: true}}
		err := repo.Insert(ctx, obj, nil)
		require.NoError(t, err)

		err = archivableDB.SetArchived(ctx, obj.ID, true)
		require.NoError(t, err) // Should not error, just not change anything

		var result TestArchivableObject
		err = repo.Get(ctx, obj.ID, &result)
		require.NoError(t, err)
		assert.True(t, result.IsArchived())
	})

	t.Run("SetArchived_Unarchive", func(t *testing.T) {
		obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: true}}
		err := repo.Insert(ctx, obj, nil)
		require.NoError(t, err)

		err = archivableDB.SetArchived(ctx, obj.ID, false)
		require.NoError(t, err)

		var result TestArchivableObject
		err = repo.Get(ctx, obj.ID, &result)
		require.NoError(t, err)
		assert.False(t, result.IsArchived())
	})

	t.Run("IsArchived_True", func(t *testing.T) {
		obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: true}}
		err := repo.Insert(ctx, obj, nil)
		require.NoError(t, err)

		isArchived, err := archivableDB.IsArchived(ctx, obj.ID)
		require.NoError(t, err)
		assert.True(t, isArchived)
	})

	t.Run("IsArchived_False", func(t *testing.T) {
		obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: false}}
		err := repo.Insert(ctx, obj, nil)
		require.NoError(t, err)

		isArchived, err := archivableDB.IsArchived(ctx, obj.ID)
		require.NoError(t, err)
		assert.False(t, isArchived)
	})

	t.Run("Archive_Success", func(t *testing.T) {
		obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: false}}
		err := repo.Insert(ctx, obj, nil)
		require.NoError(t, err)

		err = archivableDB.Archive(ctx, obj.ID)
		require.NoError(t, err)

		var result TestArchivableObject
		err = repo.Get(ctx, obj.ID, &result)
		require.NoError(t, err)
		assert.True(t, result.IsArchived())
	})

	t.Run("Unarchive_Success", func(t *testing.T) {
		obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: true}}
		err := repo.Insert(ctx, obj, nil)
		require.NoError(t, err)

		err = archivableDB.Unarchive(ctx, obj.ID)
		require.NoError(t, err)

		var result TestArchivableObject
		err = repo.Get(ctx, obj.ID, &result)
		require.NoError(t, err)
		assert.False(t, result.IsArchived())
	})
}
