package schema

import (
	"context"
	"encoding/json"

	"github.com/zitadel/zitadel/internal/domain"
	"github.com/zitadel/zitadel/internal/eventstore"
)

const (
	eventPrefix     = "user_schema."
	CreatedType     = eventPrefix + "created"
	UpdatedType     = eventPrefix + "updated"
	DeactivatedType = eventPrefix + "deactivated"
	ReactivatedType = eventPrefix + "reactivated"
	DeletedType     = eventPrefix + "deleted"

	uniqueSchemaType = "user_schema_type"
)

func NewAddSchemaTypeUniqueConstraint(schemaType string) *eventstore.UniqueConstraint {
	return eventstore.NewAddEventUniqueConstraint(
		uniqueSchemaType,
		schemaType,
		"Errors.UserSchema.Type.AlreadyExists")
}

func NewRemoveSchemaTypeUniqueConstraint(schemaType string) *eventstore.UniqueConstraint {
	return eventstore.NewRemoveUniqueConstraint(
		uniqueSchemaType,
		schemaType,
	)
}

type CreatedEvent struct {
	*eventstore.BaseEvent `json:"-"`

	SchemaType             string                     `json:"schemaType"`
	Schema                 json.RawMessage            `json:"schema,omitempty"`
	PossibleAuthenticators []domain.AuthenticatorType `json:"possibleAuthenticators,omitempty"`
}

func (e *CreatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
	e.BaseEvent = event
}

func (e *CreatedEvent) Payload() interface{} {
	return e
}

func (e *CreatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
	return []*eventstore.UniqueConstraint{NewAddSchemaTypeUniqueConstraint(e.SchemaType)}
}

func NewCreatedEvent(
	ctx context.Context,
	aggregate *eventstore.Aggregate,

	schemaType string,
	schema json.RawMessage,
	possibleAuthenticators []domain.AuthenticatorType,
) *CreatedEvent {
	return &CreatedEvent{
		BaseEvent: eventstore.NewBaseEventForPush(
			ctx,
			aggregate,
			CreatedType,
		),
		SchemaType:             schemaType,
		Schema:                 schema,
		PossibleAuthenticators: possibleAuthenticators,
	}
}

type UpdatedEvent struct {
	*eventstore.BaseEvent `json:"-"`

	SchemaType             *string                    `json:"schemaType,omitempty"`
	Schema                 json.RawMessage            `json:"schema,omitempty"`
	PossibleAuthenticators []domain.AuthenticatorType `json:"possibleAuthenticators,omitempty"`
	oldSchemaType          string
}

func (e *UpdatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
	e.BaseEvent = event
}

func (e *UpdatedEvent) Payload() interface{} {
	return e
}

func (e *UpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
	if e.oldSchemaType == "" {
		return nil
	}
	return []*eventstore.UniqueConstraint{
		NewRemoveSchemaTypeUniqueConstraint(e.oldSchemaType),
		NewAddSchemaTypeUniqueConstraint(*e.SchemaType),
	}
}

func NewUpdatedEvent(
	ctx context.Context,
	aggregate *eventstore.Aggregate,
	changes []Changes,
) *UpdatedEvent {
	updatedEvent := &UpdatedEvent{
		BaseEvent: eventstore.NewBaseEventForPush(
			ctx,
			aggregate,
			UpdatedType,
		),
	}
	for _, change := range changes {
		change(updatedEvent)
	}
	return updatedEvent
}

type Changes func(event *UpdatedEvent)

func ChangeSchemaType(oldSchemaType, schemaType string) func(event *UpdatedEvent) {
	return func(e *UpdatedEvent) {
		e.SchemaType = &schemaType
		e.oldSchemaType = oldSchemaType
	}
}

func ChangeSchema(schema json.RawMessage) func(event *UpdatedEvent) {
	return func(e *UpdatedEvent) {
		e.Schema = schema
	}
}

func ChangePossibleAuthenticators(possibleAuthenticators []domain.AuthenticatorType) func(event *UpdatedEvent) {
	return func(e *UpdatedEvent) {
		e.PossibleAuthenticators = possibleAuthenticators
	}
}

type DeactivatedEvent struct {
	*eventstore.BaseEvent `json:"-"`
}

func (e *DeactivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
	e.BaseEvent = event
}

func (e *DeactivatedEvent) Payload() interface{} {
	return e
}

func (e *DeactivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
	return nil
}

func NewDeactivatedEvent(
	ctx context.Context,
	aggregate *eventstore.Aggregate,
) *DeactivatedEvent {
	return &DeactivatedEvent{
		BaseEvent: eventstore.NewBaseEventForPush(
			ctx,
			aggregate,
			DeactivatedType,
		),
	}
}

type ReactivatedEvent struct {
	*eventstore.BaseEvent `json:"-"`
}

func (e *ReactivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
	e.BaseEvent = event
}

func (e *ReactivatedEvent) Payload() interface{} {
	return e
}

func (e *ReactivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
	return nil
}

func NewReactivatedEvent(
	ctx context.Context,
	aggregate *eventstore.Aggregate,
) *ReactivatedEvent {
	return &ReactivatedEvent{
		BaseEvent: eventstore.NewBaseEventForPush(
			ctx,
			aggregate,
			ReactivatedType,
		),
	}
}

type DeletedEvent struct {
	*eventstore.BaseEvent `json:"-"`

	schemaType string
}

func (e *DeletedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
	e.BaseEvent = event
}

func (e *DeletedEvent) Payload() interface{} {
	return e
}

func (e *DeletedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
	return []*eventstore.UniqueConstraint{
		NewRemoveSchemaTypeUniqueConstraint(e.schemaType),
	}
}

func NewDeletedEvent(
	ctx context.Context,
	aggregate *eventstore.Aggregate,
	schemaType string,
) *DeletedEvent {
	return &DeletedEvent{
		BaseEvent: eventstore.NewBaseEventForPush(
			ctx,
			aggregate,
			DeletedType,
		),
		schemaType: schemaType,
	}
}