package projection

import (
	"context"

	"github.com/zitadel/zitadel/internal/database"
	"github.com/zitadel/zitadel/internal/domain"
	"github.com/zitadel/zitadel/internal/errors"
	"github.com/zitadel/zitadel/internal/eventstore"
	"github.com/zitadel/zitadel/internal/eventstore/handler"
	"github.com/zitadel/zitadel/internal/eventstore/handler/crdb"
	"github.com/zitadel/zitadel/internal/repository/idpconfig"
	"github.com/zitadel/zitadel/internal/repository/instance"
	"github.com/zitadel/zitadel/internal/repository/org"
)

const (
	IDPTable     = "projections.idps3"
	IDPOIDCTable = IDPTable + "_" + IDPOIDCSuffix
	IDPJWTTable  = IDPTable + "_" + IDPJWTSuffix

	IDPOIDCSuffix = "oidc_config"
	IDPJWTSuffix  = "jwt_config"

	IDPIDCol            = "id"
	IDPCreationDateCol  = "creation_date"
	IDPChangeDateCol    = "change_date"
	IDPSequenceCol      = "sequence"
	IDPResourceOwnerCol = "resource_owner"
	IDPInstanceIDCol    = "instance_id"
	IDPStateCol         = "state"
	IDPNameCol          = "name"
	IDPStylingTypeCol   = "styling_type"
	IDPOwnerTypeCol     = "owner_type"
	IDPAutoRegisterCol  = "auto_register"
	IDPTypeCol          = "type"
	IDPOwnerRemovedCol  = "owner_removed"

	OIDCConfigIDPIDCol                 = "idp_id"
	OIDCConfigInstanceIDCol            = "instance_id"
	OIDCConfigClientIDCol              = "client_id"
	OIDCConfigClientSecretCol          = "client_secret"
	OIDCConfigIssuerCol                = "issuer"
	OIDCConfigScopesCol                = "scopes"
	OIDCConfigDisplayNameMappingCol    = "display_name_mapping"
	OIDCConfigUsernameMappingCol       = "username_mapping"
	OIDCConfigAuthorizationEndpointCol = "authorization_endpoint"
	OIDCConfigTokenEndpointCol         = "token_endpoint"

	JWTConfigIDPIDCol        = "idp_id"
	JWTConfigInstanceIDCol   = "instance_id"
	JWTConfigIssuerCol       = "issuer"
	JWTConfigKeysEndpointCol = "keys_endpoint"
	JWTConfigHeaderNameCol   = "header_name"
	JWTConfigEndpointCol     = "endpoint"
)

type idpProjection struct {
	crdb.StatementHandler
}

func newIDPProjection(ctx context.Context, config crdb.StatementHandlerConfig) *idpProjection {
	p := new(idpProjection)
	config.ProjectionName = IDPTable
	config.Reducers = p.reducers()
	config.InitCheck = crdb.NewMultiTableCheck(
		crdb.NewTable([]*crdb.Column{
			crdb.NewColumn(IDPIDCol, crdb.ColumnTypeText),
			crdb.NewColumn(IDPCreationDateCol, crdb.ColumnTypeTimestamp),
			crdb.NewColumn(IDPChangeDateCol, crdb.ColumnTypeTimestamp),
			crdb.NewColumn(IDPSequenceCol, crdb.ColumnTypeInt64),
			crdb.NewColumn(IDPResourceOwnerCol, crdb.ColumnTypeText),
			crdb.NewColumn(IDPInstanceIDCol, crdb.ColumnTypeText),
			crdb.NewColumn(IDPStateCol, crdb.ColumnTypeEnum),
			crdb.NewColumn(IDPNameCol, crdb.ColumnTypeText),
			crdb.NewColumn(IDPStylingTypeCol, crdb.ColumnTypeEnum),
			crdb.NewColumn(IDPOwnerTypeCol, crdb.ColumnTypeEnum),
			crdb.NewColumn(IDPAutoRegisterCol, crdb.ColumnTypeBool, crdb.Default(false)),
			crdb.NewColumn(IDPTypeCol, crdb.ColumnTypeEnum, crdb.Nullable()),
			crdb.NewColumn(IDPOwnerRemovedCol, crdb.ColumnTypeBool, crdb.Default(false)),
		},
			crdb.NewPrimaryKey(IDPInstanceIDCol, IDPIDCol),
			crdb.WithIndex(crdb.NewIndex("resource_owner", []string{IDPResourceOwnerCol})),
			crdb.WithIndex(crdb.NewIndex("owner_removed", []string{IDPOwnerRemovedCol})),
		),
		crdb.NewSuffixedTable([]*crdb.Column{
			crdb.NewColumn(OIDCConfigIDPIDCol, crdb.ColumnTypeText),
			crdb.NewColumn(OIDCConfigInstanceIDCol, crdb.ColumnTypeText),
			crdb.NewColumn(OIDCConfigClientIDCol, crdb.ColumnTypeText, crdb.Nullable()),
			crdb.NewColumn(OIDCConfigClientSecretCol, crdb.ColumnTypeJSONB, crdb.Nullable()),
			crdb.NewColumn(OIDCConfigIssuerCol, crdb.ColumnTypeText, crdb.Nullable()),
			crdb.NewColumn(OIDCConfigScopesCol, crdb.ColumnTypeTextArray, crdb.Nullable()),
			crdb.NewColumn(OIDCConfigDisplayNameMappingCol, crdb.ColumnTypeEnum, crdb.Nullable()),
			crdb.NewColumn(OIDCConfigUsernameMappingCol, crdb.ColumnTypeEnum, crdb.Nullable()),
			crdb.NewColumn(OIDCConfigAuthorizationEndpointCol, crdb.ColumnTypeText, crdb.Nullable()),
			crdb.NewColumn(OIDCConfigTokenEndpointCol, crdb.ColumnTypeText, crdb.Nullable()),
		},
			crdb.NewPrimaryKey(OIDCConfigInstanceIDCol, OIDCConfigIDPIDCol),
			IDPOIDCSuffix,
			crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()),
		),
		crdb.NewSuffixedTable([]*crdb.Column{
			crdb.NewColumn(JWTConfigIDPIDCol, crdb.ColumnTypeText),
			crdb.NewColumn(JWTConfigInstanceIDCol, crdb.ColumnTypeText),
			crdb.NewColumn(JWTConfigIssuerCol, crdb.ColumnTypeText, crdb.Nullable()),
			crdb.NewColumn(JWTConfigKeysEndpointCol, crdb.ColumnTypeText, crdb.Nullable()),
			crdb.NewColumn(JWTConfigHeaderNameCol, crdb.ColumnTypeText, crdb.Nullable()),
			crdb.NewColumn(JWTConfigEndpointCol, crdb.ColumnTypeText, crdb.Nullable()),
		},
			crdb.NewPrimaryKey(JWTConfigInstanceIDCol, JWTConfigIDPIDCol),
			IDPJWTSuffix,
			crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()),
		),
	)
	p.StatementHandler = crdb.NewStatementHandler(ctx, config)
	return p
}

func (p *idpProjection) reducers() []handler.AggregateReducer {
	return []handler.AggregateReducer{
		{
			Aggregate: instance.AggregateType,
			EventRedusers: []handler.EventReducer{
				{
					Event:  instance.IDPConfigAddedEventType,
					Reduce: p.reduceIDPAdded,
				},
				{
					Event:  instance.IDPConfigChangedEventType,
					Reduce: p.reduceIDPChanged,
				},
				{
					Event:  instance.IDPConfigDeactivatedEventType,
					Reduce: p.reduceIDPDeactivated,
				},
				{
					Event:  instance.IDPConfigReactivatedEventType,
					Reduce: p.reduceIDPReactivated,
				},
				{
					Event:  instance.IDPConfigRemovedEventType,
					Reduce: p.reduceIDPRemoved,
				},
				{
					Event:  instance.IDPOIDCConfigAddedEventType,
					Reduce: p.reduceOIDCConfigAdded,
				},
				{
					Event:  instance.IDPOIDCConfigChangedEventType,
					Reduce: p.reduceOIDCConfigChanged,
				},
				{
					Event:  instance.IDPJWTConfigAddedEventType,
					Reduce: p.reduceJWTConfigAdded,
				},
				{
					Event:  instance.IDPJWTConfigChangedEventType,
					Reduce: p.reduceJWTConfigChanged,
				},
				{
					Event:  instance.InstanceRemovedEventType,
					Reduce: reduceInstanceRemovedHelper(IDPInstanceIDCol),
				},
			},
		},
		{
			Aggregate: org.AggregateType,
			EventRedusers: []handler.EventReducer{
				{
					Event:  org.IDPConfigAddedEventType,
					Reduce: p.reduceIDPAdded,
				},
				{
					Event:  org.IDPConfigChangedEventType,
					Reduce: p.reduceIDPChanged,
				},
				{
					Event:  org.IDPConfigDeactivatedEventType,
					Reduce: p.reduceIDPDeactivated,
				},
				{
					Event:  org.IDPConfigReactivatedEventType,
					Reduce: p.reduceIDPReactivated,
				},
				{
					Event:  org.IDPConfigRemovedEventType,
					Reduce: p.reduceIDPRemoved,
				},
				{
					Event:  org.IDPOIDCConfigAddedEventType,
					Reduce: p.reduceOIDCConfigAdded,
				},
				{
					Event:  org.IDPOIDCConfigChangedEventType,
					Reduce: p.reduceOIDCConfigChanged,
				},
				{
					Event:  org.IDPJWTConfigAddedEventType,
					Reduce: p.reduceJWTConfigAdded,
				},
				{
					Event:  org.IDPJWTConfigChangedEventType,
					Reduce: p.reduceJWTConfigChanged,
				},
				{
					Event:  org.OrgRemovedEventType,
					Reduce: p.reduceOwnerRemoved,
				},
			},
		},
	}
}

func (p *idpProjection) reduceIDPAdded(event eventstore.Event) (*handler.Statement, error) {
	var idpEvent idpconfig.IDPConfigAddedEvent
	var idpOwnerType domain.IdentityProviderType
	switch e := event.(type) {
	case *org.IDPConfigAddedEvent:
		idpEvent = e.IDPConfigAddedEvent
		idpOwnerType = domain.IdentityProviderTypeOrg
	case *instance.IDPConfigAddedEvent:
		idpEvent = e.IDPConfigAddedEvent
		idpOwnerType = domain.IdentityProviderTypeSystem
	default:
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-fcUdQ", "reduce.wrong.event.type %v", []eventstore.EventType{org.IDPConfigAddedEventType, instance.IDPConfigAddedEventType})
	}

	return crdb.NewCreateStatement(
		&idpEvent,
		[]handler.Column{
			handler.NewCol(IDPIDCol, idpEvent.ConfigID),
			handler.NewCol(IDPCreationDateCol, idpEvent.CreationDate()),
			handler.NewCol(IDPChangeDateCol, idpEvent.CreationDate()),
			handler.NewCol(IDPSequenceCol, idpEvent.Sequence()),
			handler.NewCol(IDPResourceOwnerCol, idpEvent.Aggregate().ResourceOwner),
			handler.NewCol(IDPInstanceIDCol, idpEvent.Aggregate().InstanceID),
			handler.NewCol(IDPStateCol, domain.IDPConfigStateActive),
			handler.NewCol(IDPNameCol, idpEvent.Name),
			handler.NewCol(IDPStylingTypeCol, idpEvent.StylingType),
			handler.NewCol(IDPAutoRegisterCol, idpEvent.AutoRegister),
			handler.NewCol(IDPOwnerTypeCol, idpOwnerType),
		},
	), nil
}

func (p *idpProjection) reduceIDPChanged(event eventstore.Event) (*handler.Statement, error) {
	var idpEvent idpconfig.IDPConfigChangedEvent
	switch e := event.(type) {
	case *org.IDPConfigChangedEvent:
		idpEvent = e.IDPConfigChangedEvent
	case *instance.IDPConfigChangedEvent:
		idpEvent = e.IDPConfigChangedEvent
	default:
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-NVvJD", "reduce.wrong.event.type %v", []eventstore.EventType{org.IDPConfigChangedEventType, instance.IDPConfigChangedEventType})
	}

	cols := make([]handler.Column, 0, 5)
	if idpEvent.Name != nil {
		cols = append(cols, handler.NewCol(IDPNameCol, *idpEvent.Name))
	}
	if idpEvent.StylingType != nil {
		cols = append(cols, handler.NewCol(IDPStylingTypeCol, *idpEvent.StylingType))
	}
	if idpEvent.AutoRegister != nil {
		cols = append(cols, handler.NewCol(IDPAutoRegisterCol, *idpEvent.AutoRegister))
	}
	if len(cols) == 0 {
		return crdb.NewNoOpStatement(&idpEvent), nil
	}

	cols = append(cols,
		handler.NewCol(IDPChangeDateCol, idpEvent.CreationDate()),
		handler.NewCol(IDPSequenceCol, idpEvent.Sequence()),
	)

	return crdb.NewUpdateStatement(
		&idpEvent,
		cols,
		[]handler.Condition{
			handler.NewCond(IDPIDCol, idpEvent.ConfigID),
			handler.NewCond(IDPInstanceIDCol, idpEvent.Aggregate().InstanceID),
		},
	), nil
}

func (p *idpProjection) reduceIDPDeactivated(event eventstore.Event) (*handler.Statement, error) {
	var idpEvent idpconfig.IDPConfigDeactivatedEvent
	switch e := event.(type) {
	case *org.IDPConfigDeactivatedEvent:
		idpEvent = e.IDPConfigDeactivatedEvent
	case *instance.IDPConfigDeactivatedEvent:
		idpEvent = e.IDPConfigDeactivatedEvent
	default:
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-94O5l", "reduce.wrong.event.type %v", []eventstore.EventType{org.IDPConfigDeactivatedEventType, instance.IDPConfigDeactivatedEventType})
	}

	return crdb.NewUpdateStatement(
		&idpEvent,
		[]handler.Column{
			handler.NewCol(IDPStateCol, domain.IDPConfigStateInactive),
			handler.NewCol(IDPChangeDateCol, idpEvent.CreationDate()),
			handler.NewCol(IDPSequenceCol, idpEvent.Sequence()),
		},
		[]handler.Condition{
			handler.NewCond(IDPIDCol, idpEvent.ConfigID),
			handler.NewCond(IDPInstanceIDCol, idpEvent.Aggregate().InstanceID),
		},
	), nil
}

func (p *idpProjection) reduceIDPReactivated(event eventstore.Event) (*handler.Statement, error) {
	var idpEvent idpconfig.IDPConfigReactivatedEvent
	switch e := event.(type) {
	case *org.IDPConfigReactivatedEvent:
		idpEvent = e.IDPConfigReactivatedEvent
	case *instance.IDPConfigReactivatedEvent:
		idpEvent = e.IDPConfigReactivatedEvent
	default:
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-I8QyS", "reduce.wrong.event.type %v", []eventstore.EventType{org.IDPConfigReactivatedEventType, instance.IDPConfigReactivatedEventType})
	}

	return crdb.NewUpdateStatement(
		&idpEvent,
		[]handler.Column{
			handler.NewCol(IDPStateCol, domain.IDPConfigStateActive),
			handler.NewCol(IDPChangeDateCol, idpEvent.CreationDate()),
			handler.NewCol(IDPSequenceCol, idpEvent.Sequence()),
		},
		[]handler.Condition{
			handler.NewCond(IDPIDCol, idpEvent.ConfigID),
			handler.NewCond(IDPInstanceIDCol, idpEvent.Aggregate().InstanceID),
		},
	), nil
}

func (p *idpProjection) reduceIDPRemoved(event eventstore.Event) (*handler.Statement, error) {
	var idpEvent idpconfig.IDPConfigRemovedEvent
	switch e := event.(type) {
	case *org.IDPConfigRemovedEvent:
		idpEvent = e.IDPConfigRemovedEvent
	case *instance.IDPConfigRemovedEvent:
		idpEvent = e.IDPConfigRemovedEvent
	default:
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-B4zy8", "reduce.wrong.event.type %v", []eventstore.EventType{org.IDPConfigRemovedEventType, instance.IDPConfigRemovedEventType})
	}

	return crdb.NewDeleteStatement(
		&idpEvent,
		[]handler.Condition{
			handler.NewCond(IDPIDCol, idpEvent.ConfigID),
			handler.NewCond(IDPInstanceIDCol, idpEvent.Aggregate().InstanceID),
		},
	), nil
}

func (p *idpProjection) reduceOIDCConfigAdded(event eventstore.Event) (*handler.Statement, error) {
	var idpEvent idpconfig.OIDCConfigAddedEvent
	switch e := event.(type) {
	case *org.IDPOIDCConfigAddedEvent:
		idpEvent = e.OIDCConfigAddedEvent
	case *instance.IDPOIDCConfigAddedEvent:
		idpEvent = e.OIDCConfigAddedEvent
	default:
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-2FuAA", "reduce.wrong.event.type %v", []eventstore.EventType{org.IDPOIDCConfigAddedEventType, instance.IDPOIDCConfigAddedEventType})
	}

	return crdb.NewMultiStatement(&idpEvent,
		crdb.AddUpdateStatement(
			[]handler.Column{
				handler.NewCol(IDPChangeDateCol, idpEvent.CreationDate()),
				handler.NewCol(IDPSequenceCol, idpEvent.Sequence()),
				handler.NewCol(IDPTypeCol, domain.IDPConfigTypeOIDC),
			},
			[]handler.Condition{
				handler.NewCond(IDPIDCol, idpEvent.IDPConfigID),
				handler.NewCond(IDPInstanceIDCol, idpEvent.Aggregate().InstanceID),
			},
		),
		crdb.AddCreateStatement(
			[]handler.Column{
				handler.NewCol(OIDCConfigIDPIDCol, idpEvent.IDPConfigID),
				handler.NewCol(OIDCConfigInstanceIDCol, idpEvent.Aggregate().InstanceID),
				handler.NewCol(OIDCConfigClientIDCol, idpEvent.ClientID),
				handler.NewCol(OIDCConfigClientSecretCol, idpEvent.ClientSecret),
				handler.NewCol(OIDCConfigIssuerCol, idpEvent.Issuer),
				handler.NewCol(OIDCConfigScopesCol, database.StringArray(idpEvent.Scopes)),
				handler.NewCol(OIDCConfigDisplayNameMappingCol, idpEvent.IDPDisplayNameMapping),
				handler.NewCol(OIDCConfigUsernameMappingCol, idpEvent.UserNameMapping),
				handler.NewCol(OIDCConfigAuthorizationEndpointCol, idpEvent.AuthorizationEndpoint),
				handler.NewCol(OIDCConfigTokenEndpointCol, idpEvent.TokenEndpoint),
			},
			crdb.WithTableSuffix(IDPOIDCSuffix),
		),
	), nil
}

func (p *idpProjection) reduceOIDCConfigChanged(event eventstore.Event) (*handler.Statement, error) {
	var idpEvent idpconfig.OIDCConfigChangedEvent
	switch e := event.(type) {
	case *org.IDPOIDCConfigChangedEvent:
		idpEvent = e.OIDCConfigChangedEvent
	case *instance.IDPOIDCConfigChangedEvent:
		idpEvent = e.OIDCConfigChangedEvent
	default:
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-x2IVI", "reduce.wrong.event.type %v", []eventstore.EventType{org.IDPOIDCConfigChangedEventType, instance.IDPOIDCConfigChangedEventType})
	}

	cols := make([]handler.Column, 0, 8)

	if idpEvent.ClientID != nil {
		cols = append(cols, handler.NewCol(OIDCConfigClientIDCol, *idpEvent.ClientID))
	}
	if idpEvent.ClientSecret != nil {
		cols = append(cols, handler.NewCol(OIDCConfigClientSecretCol, idpEvent.ClientSecret))
	}
	if idpEvent.Issuer != nil {
		cols = append(cols, handler.NewCol(OIDCConfigIssuerCol, *idpEvent.Issuer))
	}
	if idpEvent.AuthorizationEndpoint != nil {
		cols = append(cols, handler.NewCol(OIDCConfigAuthorizationEndpointCol, *idpEvent.AuthorizationEndpoint))
	}
	if idpEvent.TokenEndpoint != nil {
		cols = append(cols, handler.NewCol(OIDCConfigTokenEndpointCol, *idpEvent.TokenEndpoint))
	}
	if idpEvent.Scopes != nil {
		cols = append(cols, handler.NewCol(OIDCConfigScopesCol, database.StringArray(idpEvent.Scopes)))
	}
	if idpEvent.IDPDisplayNameMapping != nil {
		cols = append(cols, handler.NewCol(OIDCConfigDisplayNameMappingCol, *idpEvent.IDPDisplayNameMapping))
	}
	if idpEvent.UserNameMapping != nil {
		cols = append(cols, handler.NewCol(OIDCConfigUsernameMappingCol, *idpEvent.UserNameMapping))
	}

	if len(cols) == 0 {
		return crdb.NewNoOpStatement(&idpEvent), nil
	}

	return crdb.NewMultiStatement(&idpEvent,
		crdb.AddUpdateStatement(
			[]handler.Column{
				handler.NewCol(IDPChangeDateCol, idpEvent.CreationDate()),
				handler.NewCol(IDPSequenceCol, idpEvent.Sequence()),
			},
			[]handler.Condition{
				handler.NewCond(IDPIDCol, idpEvent.IDPConfigID),
				handler.NewCond(IDPInstanceIDCol, idpEvent.Aggregate().InstanceID),
			},
		),
		crdb.AddUpdateStatement(
			cols,
			[]handler.Condition{
				handler.NewCond(OIDCConfigIDPIDCol, idpEvent.IDPConfigID),
				handler.NewCond(OIDCConfigInstanceIDCol, idpEvent.Aggregate().InstanceID),
			},
			crdb.WithTableSuffix(IDPOIDCSuffix),
		),
	), nil
}

func (p *idpProjection) reduceJWTConfigAdded(event eventstore.Event) (*handler.Statement, error) {
	var idpEvent idpconfig.JWTConfigAddedEvent
	switch e := event.(type) {
	case *org.IDPJWTConfigAddedEvent:
		idpEvent = e.JWTConfigAddedEvent
	case *instance.IDPJWTConfigAddedEvent:
		idpEvent = e.JWTConfigAddedEvent
	default:
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-qvPdb", "reduce.wrong.event.type %v", []eventstore.EventType{org.IDPJWTConfigAddedEventType, instance.IDPJWTConfigAddedEventType})
	}

	return crdb.NewMultiStatement(&idpEvent,
		crdb.AddUpdateStatement(
			[]handler.Column{
				handler.NewCol(IDPChangeDateCol, idpEvent.CreationDate()),
				handler.NewCol(IDPSequenceCol, idpEvent.Sequence()),
				handler.NewCol(IDPTypeCol, domain.IDPConfigTypeJWT),
			},
			[]handler.Condition{
				handler.NewCond(IDPIDCol, idpEvent.IDPConfigID),
				handler.NewCond(IDPInstanceIDCol, idpEvent.Aggregate().InstanceID),
			},
		),

		crdb.AddCreateStatement(
			[]handler.Column{
				handler.NewCol(JWTConfigIDPIDCol, idpEvent.IDPConfigID),
				handler.NewCol(JWTConfigInstanceIDCol, idpEvent.Aggregate().InstanceID),
				handler.NewCol(JWTConfigEndpointCol, idpEvent.JWTEndpoint),
				handler.NewCol(JWTConfigIssuerCol, idpEvent.Issuer),
				handler.NewCol(JWTConfigKeysEndpointCol, idpEvent.KeysEndpoint),
				handler.NewCol(JWTConfigHeaderNameCol, idpEvent.HeaderName),
			},
			crdb.WithTableSuffix(IDPJWTSuffix),
		),
	), nil
}

func (p *idpProjection) reduceJWTConfigChanged(event eventstore.Event) (*handler.Statement, error) {
	var idpEvent idpconfig.JWTConfigChangedEvent
	switch e := event.(type) {
	case *org.IDPJWTConfigChangedEvent:
		idpEvent = e.JWTConfigChangedEvent
	case *instance.IDPJWTConfigChangedEvent:
		idpEvent = e.JWTConfigChangedEvent
	default:
		return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-x2IVI", "reduce.wrong.event.type %v", []eventstore.EventType{org.IDPJWTConfigChangedEventType, instance.IDPJWTConfigChangedEventType})
	}

	cols := make([]handler.Column, 0, 4)

	if idpEvent.JWTEndpoint != nil {
		cols = append(cols, handler.NewCol(JWTConfigEndpointCol, *idpEvent.JWTEndpoint))
	}
	if idpEvent.Issuer != nil {
		cols = append(cols, handler.NewCol(JWTConfigIssuerCol, *idpEvent.Issuer))
	}
	if idpEvent.KeysEndpoint != nil {
		cols = append(cols, handler.NewCol(JWTConfigKeysEndpointCol, *idpEvent.KeysEndpoint))
	}
	if idpEvent.HeaderName != nil {
		cols = append(cols, handler.NewCol(JWTConfigHeaderNameCol, *idpEvent.HeaderName))
	}

	if len(cols) == 0 {
		return crdb.NewNoOpStatement(&idpEvent), nil
	}

	return crdb.NewMultiStatement(&idpEvent,
		crdb.AddUpdateStatement(
			[]handler.Column{
				handler.NewCol(IDPChangeDateCol, idpEvent.CreationDate()),
				handler.NewCol(IDPSequenceCol, idpEvent.Sequence()),
			},
			[]handler.Condition{
				handler.NewCond(IDPIDCol, idpEvent.IDPConfigID),
				handler.NewCond(IDPInstanceIDCol, idpEvent.Aggregate().InstanceID),
			},
		),
		crdb.AddUpdateStatement(
			cols,
			[]handler.Condition{
				handler.NewCond(JWTConfigIDPIDCol, idpEvent.IDPConfigID),
				handler.NewCond(JWTConfigInstanceIDCol, idpEvent.Aggregate().InstanceID),
			},
			crdb.WithTableSuffix(IDPJWTSuffix),
		),
	), nil
}

func (p *idpProjection) reduceOwnerRemoved(event eventstore.Event) (*handler.Statement, error) {
	e, ok := event.(*org.OrgRemovedEvent)
	if !ok {
		return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-YsbQC", "reduce.wrong.event.type %s", org.OrgRemovedEventType)
	}

	return crdb.NewDeleteStatement(
		e,
		[]handler.Condition{
			handler.NewCond(IDPInstanceIDCol, e.Aggregate().InstanceID),
			handler.NewCond(IDPResourceOwnerCol, e.Aggregate().ID),
		},
	), nil
}