package eventsourcing

import (
	"context"
	"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
	"testing"

	"github.com/caos/zitadel/internal/api/auth"
	"github.com/caos/zitadel/internal/errors"
	es_models "github.com/caos/zitadel/internal/eventstore/models"
	org_model "github.com/caos/zitadel/internal/org/model"
)

func Test_isReservedValidation(t *testing.T) {
	type res struct {
		isErr              func(error) bool
		agggregateSequence uint64
	}
	type args struct {
		aggregate *es_models.Aggregate
		eventType es_models.EventType
		Events    []*es_models.Event
	}
	tests := []struct {
		name string
		args args
		res  res
	}{
		{
			name: "no events success",
			args: args{
				aggregate: &es_models.Aggregate{},
				eventType: "object.reserved",
				Events:    []*es_models.Event{},
			},
			res: res{
				isErr:              nil,
				agggregateSequence: 0,
			},
		},
		{
			name: "not reseved success",
			args: args{
				aggregate: &es_models.Aggregate{},
				eventType: "object.reserved",
				Events: []*es_models.Event{
					{
						AggregateID:   "asdf",
						AggregateType: "org",
						Sequence:      45,
						Type:          "object.released",
					},
				},
			},
			res: res{
				isErr:              nil,
				agggregateSequence: 45,
			},
		},
		{
			name: "reseved error",
			args: args{
				aggregate: &es_models.Aggregate{},
				eventType: "object.reserved",
				Events: []*es_models.Event{
					{
						AggregateID:   "asdf",
						AggregateType: "org",
						Sequence:      45,
						Type:          "object.reserved",
					},
				},
			},
			res: res{
				isErr:              errors.IsPreconditionFailed,
				agggregateSequence: 0,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			validate := isReservedValidation(tt.args.aggregate, tt.args.eventType)

			err := validate(tt.args.Events...)

			if tt.res.isErr == nil && err != nil {
				t.Errorf("no error expected got: %v", err)
			}
			if tt.res.isErr != nil && !tt.res.isErr(err) {
				t.Errorf("wrong error got: %v", err)
			}
			if err == nil && tt.args.aggregate.PreviousSequence != tt.res.agggregateSequence {
				t.Errorf("expected sequence %d got %d", tt.res.agggregateSequence, tt.args.aggregate.PreviousSequence)
			}
		})
	}
}

func aggregateWithPrecondition() *es_models.Aggregate {
	return nil
}

func Test_uniqueNameAggregate(t *testing.T) {
	type res struct {
		expected *es_models.Aggregate
		isErr    func(error) bool
	}
	type args struct {
		ctx        context.Context
		aggCreator *es_models.AggregateCreator
		orgName    string
	}
	tests := []struct {
		name string
		args args
		res  res
	}{
		{
			name: "no org name error",
			args: args{
				ctx:        auth.NewMockContext("orgID", "userID"),
				aggCreator: es_models.NewAggregateCreator("test"),
				orgName:    "",
			},
			res: res{
				expected: nil,
				isErr:    errors.IsPreconditionFailed,
			},
		},
		{
			name: "aggregate created",
			args: args{
				ctx:        auth.NewMockContext("orgID", "userID"),
				aggCreator: es_models.NewAggregateCreator("test"),
				orgName:    "asdf",
			},
			res: res{
				expected: aggregateWithPrecondition(),
				isErr:    nil,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := uniqueNameAggregate(tt.args.ctx, tt.args.aggCreator, "", tt.args.orgName)
			if tt.res.isErr == nil && err != nil {
				t.Errorf("no error expected got: %v", err)
			}
			if tt.res.isErr != nil && !tt.res.isErr(err) {
				t.Errorf("wrong error got %T: %v", err, err)
			}
			if tt.res.isErr == nil && (got.Precondition == nil || got.Precondition.Query == nil || got.Precondition.Validation == nil) {
				t.Errorf("precondition is not set correctly")
			}
		})
	}
}

func Test_uniqueDomainAggregate(t *testing.T) {
	type res struct {
		expected *es_models.Aggregate
		isErr    func(error) bool
	}
	type args struct {
		ctx        context.Context
		aggCreator *es_models.AggregateCreator
		orgDomain  string
	}
	tests := []struct {
		name string
		args args
		res  res
	}{
		{
			name: "no org domain error",
			args: args{
				ctx:        auth.NewMockContext("orgID", "userID"),
				aggCreator: es_models.NewAggregateCreator("test"),
				orgDomain:  "",
			},
			res: res{
				expected: nil,
				isErr:    errors.IsPreconditionFailed,
			},
		},
		{
			name: "aggregate created",
			args: args{
				ctx:        auth.NewMockContext("orgID", "userID"),
				aggCreator: es_models.NewAggregateCreator("test"),
				orgDomain:  "asdf",
			},
			res: res{
				expected: aggregateWithPrecondition(),
				isErr:    nil,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := uniqueDomainAggregate(tt.args.ctx, tt.args.aggCreator, "", tt.args.orgDomain)
			if tt.res.isErr == nil && err != nil {
				t.Errorf("no error expected got: %v", err)
			}
			if tt.res.isErr != nil && !tt.res.isErr(err) {
				t.Errorf("wrong error got %T: %v", err, err)
			}
			if tt.res.isErr == nil && (got.Precondition == nil || got.Precondition.Query == nil || got.Precondition.Validation == nil) {
				t.Errorf("precondition is not set correctly")
			}
		})
	}
}

func TestOrgReactivateAggregate(t *testing.T) {
	type res struct {
		isErr func(error) bool
	}
	type args struct {
		aggCreator *es_models.AggregateCreator
		org        *model.Org
		ctx        context.Context
	}
	tests := []struct {
		name string
		args args
		res  res
	}{
		{
			name: "correct",
			args: args{
				aggCreator: es_models.NewAggregateCreator("test"),
				ctx:        auth.NewMockContext("org", "user"),
				org: &model.Org{
					ObjectRoot: es_models.ObjectRoot{
						AggregateID: "orgID",
						Sequence:    2,
					},
					State: int32(org_model.ORGSTATE_INACTIVE),
				},
			},
		},
		{
			name: "already active error",
			args: args{
				aggCreator: es_models.NewAggregateCreator("test"),
				ctx:        auth.NewMockContext("org", "user"),
				org: &model.Org{
					ObjectRoot: es_models.ObjectRoot{
						AggregateID: "orgID",
						Sequence:    2,
					},
					State: int32(org_model.ORGSTATE_ACTIVE),
				},
			},
			res: res{
				isErr: errors.IsErrorInvalidArgument,
			},
		},
		{
			name: "org nil error",
			args: args{
				aggCreator: es_models.NewAggregateCreator("test"),
				ctx:        auth.NewMockContext("org", "user"),
				org:        nil,
			},
			res: res{
				isErr: errors.IsPreconditionFailed,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			aggregateCreator := orgReactivateAggregate(tt.args.aggCreator, tt.args.org)
			aggregate, err := aggregateCreator(tt.args.ctx)
			if tt.res.isErr == nil && err != nil {
				t.Errorf("no error expected got: %v", err)
			}
			if tt.res.isErr != nil && !tt.res.isErr(err) {
				t.Errorf("wrong error got %T: %v", err, err)
			}
			if tt.res.isErr == nil && aggregate == nil {
				t.Error("aggregate must not be nil")
			}
		})
	}
}

func TestOrgDeactivateAggregate(t *testing.T) {
	type res struct {
		isErr func(error) bool
	}
	type args struct {
		aggCreator *es_models.AggregateCreator
		org        *model.Org
		ctx        context.Context
	}
	tests := []struct {
		name string
		args args
		res  res
	}{
		{
			name: "correct",
			args: args{
				aggCreator: es_models.NewAggregateCreator("test"),
				ctx:        auth.NewMockContext("org", "user"),
				org: &model.Org{
					ObjectRoot: es_models.ObjectRoot{
						AggregateID: "orgID",
						Sequence:    2,
					},
					State: int32(org_model.ORGSTATE_ACTIVE),
				},
			},
		},
		{
			name: "already inactive error",
			args: args{
				aggCreator: es_models.NewAggregateCreator("test"),
				ctx:        auth.NewMockContext("org", "user"),
				org: &model.Org{
					ObjectRoot: es_models.ObjectRoot{
						AggregateID: "orgID",
						Sequence:    2,
					},
					State: int32(org_model.ORGSTATE_INACTIVE),
				},
			},
			res: res{
				isErr: errors.IsErrorInvalidArgument,
			},
		},
		{
			name: "org nil error",
			args: args{
				aggCreator: es_models.NewAggregateCreator("test"),
				ctx:        auth.NewMockContext("org", "user"),
				org:        nil,
			},
			res: res{
				isErr: errors.IsPreconditionFailed,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			aggregateCreator := orgDeactivateAggregate(tt.args.aggCreator, tt.args.org)
			aggregate, err := aggregateCreator(tt.args.ctx)
			if tt.res.isErr == nil && err != nil {
				t.Errorf("no error expected got: %v", err)
			}
			if tt.res.isErr != nil && !tt.res.isErr(err) {
				t.Errorf("wrong error got %T: %v", err, err)
			}
			if tt.res.isErr == nil && aggregate == nil {
				t.Error("aggregate must not be nil")
			}
		})
	}
}

func TestOrgUpdateAggregates(t *testing.T) {
	type res struct {
		aggregateCount int
		isErr          func(error) bool
	}
	type args struct {
		ctx        context.Context
		aggCreator *es_models.AggregateCreator
		existing   *model.Org
		updated    *model.Org
	}
	tests := []struct {
		name string
		args args
		res  res
	}{
		{
			name: "no existing org error",
			args: args{
				ctx:        auth.NewMockContext("org", "user"),
				aggCreator: es_models.NewAggregateCreator("test"),
				existing:   nil,
				updated:    &model.Org{},
			},
			res: res{
				aggregateCount: 0,
				isErr:          errors.IsPreconditionFailed,
			},
		},
		{
			name: "no updated org error",
			args: args{
				ctx:        auth.NewMockContext("org", "user"),
				aggCreator: es_models.NewAggregateCreator("test"),
				existing:   &model.Org{},
				updated:    nil,
			},
			res: res{
				aggregateCount: 0,
				isErr:          errors.IsPreconditionFailed,
			},
		},
		{
			name: "no changes",
			args: args{
				ctx:        auth.NewMockContext("org", "user"),
				aggCreator: es_models.NewAggregateCreator("test"),
				existing:   &model.Org{},
				updated:    &model.Org{},
			},
			res: res{
				aggregateCount: 0,
				isErr:          errors.IsPreconditionFailed,
			},
		},
		{
			name: "name changed",
			args: args{
				ctx:        auth.NewMockContext("org", "user"),
				aggCreator: es_models.NewAggregateCreator("test"),
				existing: &model.Org{
					ObjectRoot: es_models.ObjectRoot{
						AggregateID: "sdaf",
						Sequence:    5,
					},
					Domain: "caos.ch",
					Name:   "coas",
				},
				updated: &model.Org{
					ObjectRoot: es_models.ObjectRoot{
						AggregateID: "sdaf",
						Sequence:    5,
					},
					Domain: "caos.ch",
					Name:   "caos",
				},
			},
			res: res{
				aggregateCount: 2,
				isErr:          nil,
			},
		},
		{
			name: "domain changed",
			args: args{
				ctx:        auth.NewMockContext("org", "user"),
				aggCreator: es_models.NewAggregateCreator("test"),
				existing: &model.Org{
					ObjectRoot: es_models.ObjectRoot{
						AggregateID: "sdaf",
						Sequence:    5,
					},
					Domain: "caos.swiss",
					Name:   "caos",
				},
				updated: &model.Org{
					ObjectRoot: es_models.ObjectRoot{
						AggregateID: "sdaf",
						Sequence:    5,
					},
					Domain: "caos.ch",
					Name:   "caos",
				},
			},
			res: res{
				aggregateCount: 2,
				isErr:          nil,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := OrgUpdateAggregates(tt.args.ctx, tt.args.aggCreator, tt.args.existing, tt.args.updated)
			if tt.res.isErr == nil && err != nil {
				t.Errorf("no error expected got: %v", err)
			}
			if tt.res.isErr != nil && !tt.res.isErr(err) {
				t.Errorf("wrong error got %T: %v", err, err)
			}
			if tt.res.isErr == nil && len(got) != tt.res.aggregateCount {
				t.Errorf("OrgUpdateAggregates() aggregate count = %d, wanted count %d", len(got), tt.res.aggregateCount)
			}
		})
	}
}

func TestOrgCreatedAggregates(t *testing.T) {
	type res struct {
		aggregateCount int
		isErr          func(error) bool
	}
	type args struct {
		ctx        context.Context
		aggCreator *es_models.AggregateCreator
		org        *model.Org
	}
	tests := []struct {
		name string
		args args
		res  res
	}{
		{
			name: "no org error",
			args: args{
				ctx:        auth.NewMockContext("org", "user"),
				aggCreator: es_models.NewAggregateCreator("test"),
				org:        nil,
			},
			res: res{
				aggregateCount: 0,
				isErr:          errors.IsPreconditionFailed,
			},
		},
		{
			name: "org successful",
			args: args{
				ctx:        auth.NewMockContext("org", "user"),
				aggCreator: es_models.NewAggregateCreator("test"),
				org: &model.Org{
					ObjectRoot: es_models.ObjectRoot{
						AggregateID: "sdaf",
						Sequence:    5,
					},
					Domain: "caos.ch",
					Name:   "caos",
				},
			},
			res: res{
				aggregateCount: 3,
				isErr:          nil,
			},
		},
		{
			name: "no domain error",
			args: args{
				ctx:        auth.NewMockContext("org", "user"),
				aggCreator: es_models.NewAggregateCreator("test"),
				org: &model.Org{
					ObjectRoot: es_models.ObjectRoot{
						AggregateID: "sdaf",
						Sequence:    5,
					},
					Name: "caos",
				},
			},
			res: res{
				aggregateCount: 2,
				isErr:          errors.IsPreconditionFailed,
			},
		},
		{
			name: "no name error",
			args: args{
				ctx:        auth.NewMockContext("org", "user"),
				aggCreator: es_models.NewAggregateCreator("test"),
				org: &model.Org{
					ObjectRoot: es_models.ObjectRoot{
						AggregateID: "sdaf",
						Sequence:    5,
					},
					Domain: "caos.ch",
				},
			},
			res: res{
				aggregateCount: 2,
				isErr:          errors.IsPreconditionFailed,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := orgCreatedAggregates(tt.args.ctx, tt.args.aggCreator, tt.args.org)
			if tt.res.isErr == nil && err != nil {
				t.Errorf("no error expected got %T: %v", err, err)
			}
			if tt.res.isErr != nil && !tt.res.isErr(err) {
				t.Errorf("wrong error got %T: %v", err, err)
			}
			if tt.res.isErr == nil && len(got) != tt.res.aggregateCount {
				t.Errorf("OrgUpdateAggregates() aggregate count = %d, wanted count %d", len(got), tt.res.aggregateCount)
			}
		})
	}
}