package tenant_test

import (
	"context"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/dustin/go-humanize"
	"github.com/go-chi/chi"
	"github.com/google/go-cmp/cmp"
	"github.com/influxdata/influxdb/v2"
	ihttp "github.com/influxdata/influxdb/v2/http"
	"github.com/influxdata/influxdb/v2/kit/platform"
	"github.com/influxdata/influxdb/v2/kit/platform/errors"
	"github.com/influxdata/influxdb/v2/kv"
	"github.com/influxdata/influxdb/v2/mock"
	"github.com/influxdata/influxdb/v2/tenant"
	itesting "github.com/influxdata/influxdb/v2/testing"
	"go.uber.org/zap/zaptest"
)

func initBucketHttpService(f itesting.BucketFields, t *testing.T) (influxdb.BucketService, string, func()) {
	t.Helper()

	s := itesting.NewTestInmemStore(t)

	store := tenant.NewStore(s)
	if f.IDGenerator != nil {
		store.IDGen = f.IDGenerator
	}

	if f.OrgIDs != nil {
		store.OrgIDGen = f.OrgIDs
	}

	if f.BucketIDs != nil {
		store.BucketIDGen = f.BucketIDs
	}

	ctx := context.Background()

	// go direct to storage for test data
	if err := s.Update(ctx, func(tx kv.Tx) error {
		for _, o := range f.Organizations {
			if err := store.CreateOrg(tx.Context(), tx, o); err != nil {
				return err
			}
		}

		for _, b := range f.Buckets {
			if err := store.CreateBucket(tx.Context(), tx, b); err != nil {
				return err
			}
		}

		return nil
	}); err != nil {
		t.Fatalf("failed to seed data: %s", err)
	}

	handler := tenant.NewHTTPBucketHandler(zaptest.NewLogger(t), tenant.NewService(store), nil, nil, nil)
	r := chi.NewRouter()
	r.Mount(handler.Prefix(), handler)
	server := httptest.NewServer(r)
	httpClient, err := ihttp.NewHTTPClient(server.URL, "", false)
	if err != nil {
		t.Fatal(err)
	}

	client := tenant.BucketClientService{
		Client: httpClient,
	}

	return &client, "http_tenant", server.Close
}

func TestHTTPBucketService(t *testing.T) {
	itesting.BucketService(initBucketHttpService, t)
}

const idOne = platform.ID(iota + 1)

func TestHTTPBucketService_InvalidRetention(t *testing.T) {
	type args struct {
		name          string
		id            platform.ID
		retention     int
		shardDuration int
		description   *string
	}
	type wants struct {
		err    error
		bucket *influxdb.Bucket
	}

	tests := []struct {
		name   string
		fields itesting.BucketFields
		args   args
		wants  wants
	}{
		{
			name: "update with negative retention",
			fields: itesting.BucketFields{
				OrgIDs:        mock.NewIncrementingIDGenerator(idOne),
				BucketIDs:     mock.NewIncrementingIDGenerator(idOne),
				TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2006, 5, 4, 1, 2, 3, 0, time.UTC)},
				Organizations: []*influxdb.Organization{
					{
						// ID(1)
						Name: "theorg",
					},
				},
				Buckets: []*influxdb.Bucket{
					{
						// ID(1)
						OrgID:              idOne,
						Name:               "bucket1",
						RetentionPeriod:    humanize.Day,
						ShardGroupDuration: time.Hour,
					},
					{
						// ID(2)
						OrgID: idOne,
						Name:  "bucket2",
					},
				},
			},
			args: args{
				id:        idOne,
				retention: -1,
			},
			wants: wants{
				err: &errors.Error{
					Code: errors.EUnprocessableEntity,
					Msg:  "expiration seconds cannot be negative",
				},
			},
		},
		{
			name: "update with negative shard-group duration",
			fields: itesting.BucketFields{
				OrgIDs:        mock.NewIncrementingIDGenerator(idOne),
				BucketIDs:     mock.NewIncrementingIDGenerator(idOne),
				TimeGenerator: mock.TimeGenerator{FakeValue: time.Date(2006, 5, 4, 1, 2, 3, 0, time.UTC)},
				Organizations: []*influxdb.Organization{
					{
						// ID(1)
						Name: "theorg",
					},
				},
				Buckets: []*influxdb.Bucket{
					{
						// ID(1)
						OrgID:              idOne,
						Name:               "bucket1",
						RetentionPeriod:    humanize.Day,
						ShardGroupDuration: time.Hour,
					},
					{
						// ID(2)
						OrgID: idOne,
						Name:  "bucket2",
					},
				},
			},
			args: args{
				id:            idOne,
				shardDuration: -1,
			},
			wants: wants{
				err: &errors.Error{
					Code: errors.EUnprocessableEntity,
					Msg:  "shard-group duration seconds cannot be negative",
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s, _, done := initBucketHttpService(tt.fields, t)
			defer done()
			ctx := context.Background()

			upd := influxdb.BucketUpdate{}
			if tt.args.name != "" {
				upd.Name = &tt.args.name
			}
			if tt.args.retention != 0 {
				d := time.Duration(tt.args.retention) * time.Minute
				upd.RetentionPeriod = &d
			}
			if tt.args.shardDuration != 0 {
				d := time.Duration(tt.args.shardDuration) * time.Minute
				upd.ShardGroupDuration = &d
			}

			upd.Description = tt.args.description

			bucket, err := s.UpdateBucket(ctx, tt.args.id, upd)
			itesting.ErrorsEqual(t, err, tt.wants.err)

			if diff := cmp.Diff(bucket, tt.wants.bucket, bucketCmpOptions...); diff != "" {
				t.Errorf("bucket is different -got/+want\ndiff %s", diff)
			}
		})
	}
}
