package cushion

import (
	"context"
	"errors"
	"net/http"
	"testing"

	"github.com/go-kivik/kivik/v4"
	internal "github.com/go-kivik/kivik/v4/int/errors"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	dsv1alpha1 "edge-infra.dev/pkg/edge/datasync/apis/v1alpha1"
)

func TestCreateReplicationSetDoc(t *testing.T) {
	req := Request{
		TenantID:         "bsl-id",
		DBName:           "my-db",
		Provider:         "",
		EntityID:         "doc-id",
		EntityType:       "json",
		EnterpriseUnitID: "my-enterprise-unit-id",
	}
	expectedReplDoc := dsv1alpha1.ReplicationSet{
		Datasets: []dsv1alpha1.Dataset{
			{
				Name:             "my-db",
				Config:           defaultReplConfig(),
				Stores:           []string{},
				Touchpoints:      []string{},
				EnterpriseUnitID: "my-enterprise-unit-id",
			},
		},
	}

	replDoc := createReplicationSetDoc(createDataset(&req))
	assert.Equal(t, &expectedReplDoc, replDoc)

	// with provider
	req2 := req
	req2.Provider = "provider"

	expectedReplDoc2 := expectedReplDoc
	expectedReplDoc2.Providers = []dsv1alpha1.Provider{{Name: "provider"}}
	expectedReplDoc2.Datasets[0].Provider = &dsv1alpha1.Provider{Name: "provider"}

	rsd2 := createReplicationSetDoc(createDataset(&req2))
	assert.Equal(t, &expectedReplDoc2, rsd2)
}

func TestUpdateReplicationSetDocNone(t *testing.T) {
	req := Request{
		TenantID:   "bsl-id",
		DBName:     "my-db",
		Provider:   "",
		EntityID:   "doc-id",
		EntityType: "json",
	}
	replDoc := dsv1alpha1.ReplicationSet{Datasets: []dsv1alpha1.Dataset{createDataset(&req)}}

	// no update should occur
	updatedReplDoc := replDoc
	updateReplicationSetDoc(&updatedReplDoc, createDataset(&req))

	assert.Equal(t, replDoc, updatedReplDoc)

	// with provider
	req2 := req
	req2.Provider = "provider"
	expectedReplDoc2 := updatedReplDoc
	expectedReplDoc2.Providers = []dsv1alpha1.Provider{{Name: "provider"}}
	expectedReplDoc2.Datasets[0].Provider = &dsv1alpha1.Provider{Name: "provider"}

	rsd2 := createReplicationSetDoc(createDataset(&req2))
	assert.Equal(t, &expectedReplDoc2, rsd2)
}

func TestBuildReplicationDocCache_Error(t *testing.T) {
	ctx := context.Background()
	dbname := "test-db"
	storer := NewCouchDBStorage(&kivik.Client{}, nil)
	dbGetter := func(_ string) (*MessageBuffer, error) {
		return nil, errors.New("database error")
	}

	cache, err := BuildReplicationDocCache(ctx, dbname, storer, dbGetter)

	require.Error(t, err)
	require.Nil(t, cache)
}

func TestBuildReplicationDocCache_NotFound(t *testing.T) {
	ctx := context.Background()
	dbname := "test-db"
	storer := NewCouchDBStorage(&kivik.Client{}, nil)
	dbGetter := func(_ string) (*MessageBuffer, error) {
		return nil, &internal.Error{Status: http.StatusNotFound, Err: errors.New("not found")}
	}

	cache, err := BuildReplicationDocCache(ctx, dbname, storer, dbGetter)

	require.NoError(t, err)
	require.NotNil(t, cache)
	require.Equal(t, dbname, cache.replDB)
	require.NotNil(t, cache.datasets)
	require.Equal(t, storer, cache.storer)
	require.NotNil(t, cache.dbGetter)
}
func TestUpdateReplicationSetDoc(t *testing.T) {
	req := Request{
		TenantID:         "bsl-id",
		DBName:           "my-db",
		Provider:         "",
		EntityID:         "doc-id",
		EntityType:       "json",
		EnterpriseUnitID: "my-enterprise-unit-id",
	}

	replDoc := dsv1alpha1.ReplicationSet{Datasets: []dsv1alpha1.Dataset{createDataset(&req)}}

	// update should occur for different db
	updatedReplDoc := replDoc
	updatedRequest := req
	updatedRequest.DBName = "my-db2"
	updatedRequest.DBName = "my-enterprise-unit-2"
	updateReplicationSetDoc(&updatedReplDoc, createDataset(&updatedRequest))

	assert.NotEqual(t, replDoc, updatedReplDoc)

	assert.Len(t, updatedReplDoc.Providers, 0)
	assert.Len(t, updatedReplDoc.Datasets, 2)

	assert.Equal(t, replDoc.Datasets[0], updatedReplDoc.Datasets[0])
	assert.Equal(t, createDataset(&updatedRequest), updatedReplDoc.Datasets[1])

	// with provider
	req3 := Request{
		TenantID:   "bsl-id",
		DBName:     "my-db3",
		Provider:   "provider3",
		EntityID:   "doc-id",
		EntityType: "json",
	}

	updatedReplDoc2 := updatedReplDoc

	// update should occur for different db
	updateReplicationSetDoc(&updatedReplDoc2, createDataset(&req3))

	assert.NotEqual(t, updatedReplDoc, updatedReplDoc2)

	assert.Len(t, updatedReplDoc2.Providers, 1)
	assert.Len(t, updatedReplDoc2.Datasets, 3)

	assert.Equal(t, updatedReplDoc2.Providers[0].Name, req3.Provider)

	assert.Equal(t, updatedReplDoc.Datasets[0], updatedReplDoc2.Datasets[0])
	assert.Equal(t, updatedReplDoc.Datasets[1], updatedReplDoc2.Datasets[1])
	assert.Equal(t, createDataset(&req3), updatedReplDoc2.Datasets[2])
}

func TestUpdateSameReplicationSetDoc(t *testing.T) {
	req := Request{
		TenantID:         "bsl-id",
		DBName:           "my-db",
		Provider:         "no-change-provider",
		EntityID:         "doc-id",
		EntityType:       "json",
		EnterpriseUnitID: "no-change-euid",
	}
	replDoc := dsv1alpha1.ReplicationSet{Datasets: []dsv1alpha1.Dataset{createDataset(&req)}}

	updatedRequest := req
	updatedRequest.Provider = "change-provider"
	updatedRequest.EnterpriseUnitID = "change-euid"

	// only provider should update for existing db
	updatedReplDoc := replDoc
	updateReplicationSetDoc(&updatedReplDoc, createDataset(&updatedRequest))

	assert.NotEqual(t, replDoc, updatedReplDoc)

	assert.Len(t, updatedReplDoc.Providers, 1)
	assert.Len(t, updatedReplDoc.Datasets, 1)

	// Confirm that update did not happen
	assert.Equal(t, "no-change-provider", updatedReplDoc.Datasets[0].Provider.Name)
	assert.Equal(t, "no-change-euid", updatedReplDoc.Datasets[0].EnterpriseUnitID)
}