package schemauser

import (
	"context"
	"time"

	"github.com/zitadel/zitadel/internal/api/http"
	"github.com/zitadel/zitadel/internal/crypto"
	"github.com/zitadel/zitadel/internal/domain"
	"github.com/zitadel/zitadel/internal/eventstore"
	"github.com/zitadel/zitadel/internal/zerrors"
)

const (
	emailEventPrefix            = eventPrefix + "email."
	EmailUpdatedType            = emailEventPrefix + "updated"
	EmailVerifiedType           = emailEventPrefix + "verified"
	EmailVerificationFailedType = emailEventPrefix + "verification.failed"
	EmailCodeAddedType          = emailEventPrefix + "code.added"
	EmailCodeSentType           = emailEventPrefix + "code.sent"
)

type EmailUpdatedEvent struct {
	eventstore.BaseEvent `json:"-"`

	EmailAddress domain.EmailAddress `json:"email,omitempty"`
}

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

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

func NewEmailUpdatedEvent(ctx context.Context, aggregate *eventstore.Aggregate, emailAddress domain.EmailAddress) *EmailUpdatedEvent {
	return &EmailUpdatedEvent{
		BaseEvent: *eventstore.NewBaseEventForPush(
			ctx,
			aggregate,
			EmailUpdatedType,
		),
		EmailAddress: emailAddress,
	}
}

func EmailUpdatedEventMapper(event eventstore.Event) (eventstore.Event, error) {
	emailChangedEvent := &EmailUpdatedEvent{
		BaseEvent: *eventstore.BaseEventFromRepo(event),
	}
	err := event.Unmarshal(emailChangedEvent)
	if err != nil {
		return nil, zerrors.ThrowInternal(err, "USER-4M0sd", "unable to unmarshal human password changed")
	}

	return emailChangedEvent, nil
}

type EmailVerifiedEvent struct {
	eventstore.BaseEvent `json:"-"`

	IsEmailVerified bool `json:"-"`
}

func (e *EmailVerifiedEvent) Payload() interface{} {
	return nil
}

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

func NewEmailVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailVerifiedEvent {
	return &EmailVerifiedEvent{
		BaseEvent: *eventstore.NewBaseEventForPush(
			ctx,
			aggregate,
			EmailVerifiedType,
		),
	}
}

func HumanVerifiedEventMapper(event eventstore.Event) (eventstore.Event, error) {
	emailVerified := &EmailVerifiedEvent{
		BaseEvent:       *eventstore.BaseEventFromRepo(event),
		IsEmailVerified: true,
	}
	return emailVerified, nil
}

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

func (e *EmailVerificationFailedEvent) Payload() interface{} {
	return nil
}

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

func NewHumanEmailVerificationFailedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailVerificationFailedEvent {
	return &EmailVerificationFailedEvent{
		BaseEvent: *eventstore.NewBaseEventForPush(
			ctx,
			aggregate,
			EmailVerificationFailedType,
		),
	}
}

func EmailVerificationFailedEventMapper(event eventstore.Event) (eventstore.Event, error) {
	return &EmailVerificationFailedEvent{
		BaseEvent: *eventstore.BaseEventFromRepo(event),
	}, nil
}

type EmailCodeAddedEvent struct {
	eventstore.BaseEvent `json:"-"`

	Code              *crypto.CryptoValue `json:"code,omitempty"`
	Expiry            time.Duration       `json:"expiry,omitempty"`
	URLTemplate       string              `json:"url_template,omitempty"`
	CodeReturned      bool                `json:"code_returned,omitempty"`
	TriggeredAtOrigin string              `json:"triggerOrigin,omitempty"`
}

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

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

func (e *EmailCodeAddedEvent) TriggerOrigin() string {
	return e.TriggeredAtOrigin
}

func NewEmailCodeAddedEvent(
	ctx context.Context,
	aggregate *eventstore.Aggregate,
	code *crypto.CryptoValue,
	expiry time.Duration,
	urlTemplate string,
	codeReturned bool,
) *EmailCodeAddedEvent {
	return &EmailCodeAddedEvent{
		BaseEvent: *eventstore.NewBaseEventForPush(
			ctx,
			aggregate,
			EmailCodeAddedType,
		),
		Code:              code,
		Expiry:            expiry,
		URLTemplate:       urlTemplate,
		CodeReturned:      codeReturned,
		TriggeredAtOrigin: http.DomainContext(ctx).Origin(),
	}
}

func EmailCodeAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
	codeAdded := &EmailCodeAddedEvent{
		BaseEvent: *eventstore.BaseEventFromRepo(event),
	}
	err := event.Unmarshal(codeAdded)
	if err != nil {
		return nil, zerrors.ThrowInternal(err, "USER-3M0sd", "unable to unmarshal human email code added")
	}

	return codeAdded, nil
}

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

func (e *EmailCodeSentEvent) Payload() interface{} {
	return nil
}

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

func NewHumanEmailCodeSentEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailCodeSentEvent {
	return &EmailCodeSentEvent{
		BaseEvent: *eventstore.NewBaseEventForPush(
			ctx,
			aggregate,
			EmailCodeSentType,
		),
	}
}

func EmailCodeSentEventMapper(event eventstore.Event) (eventstore.Event, error) {
	return &EmailCodeSentEvent{
		BaseEvent: *eventstore.BaseEventFromRepo(event),
	}, nil
}