package command

import (
	"bytes"
	"context"
	"encoding/json"

	"github.com/zitadel/zitadel/internal/domain"
	"github.com/zitadel/zitadel/internal/eventstore"
	"github.com/zitadel/zitadel/internal/repository/user/schemauser"
)

type UserV3WriteModel struct {
	eventstore.WriteModel

	PhoneWM bool
	EmailWM bool
	DataWM  bool

	SchemaID       string
	SchemaRevision uint64

	Email                    string
	IsEmailVerified          bool
	EmailVerifiedFailedCount int
	Phone                    string
	IsPhoneVerified          bool
	PhoneVerifiedFailedCount int

	Data json.RawMessage

	State domain.UserState
}

func NewExistsUserV3WriteModel(resourceOwner, userID string) *UserV3WriteModel {
	return &UserV3WriteModel{
		WriteModel: eventstore.WriteModel{
			AggregateID:   userID,
			ResourceOwner: resourceOwner,
		},
		PhoneWM: false,
		EmailWM: false,
		DataWM:  false,
	}
}

func NewUserV3WriteModel(resourceOwner, userID string) *UserV3WriteModel {
	return &UserV3WriteModel{
		WriteModel: eventstore.WriteModel{
			AggregateID:   userID,
			ResourceOwner: resourceOwner,
		},
		PhoneWM: true,
		EmailWM: true,
		DataWM:  true,
	}
}

func (wm *UserV3WriteModel) Reduce() error {
	for _, event := range wm.Events {
		switch e := event.(type) {
		case *schemauser.CreatedEvent:
			wm.SchemaID = e.SchemaID
			wm.SchemaRevision = 1
			wm.Data = e.Data

			wm.State = domain.UserStateActive
		case *schemauser.UpdatedEvent:
			if e.SchemaID != nil {
				wm.SchemaID = *e.SchemaID
			}
			if e.SchemaRevision != nil {
				wm.SchemaRevision = *e.SchemaRevision
			}
			if len(e.Data) > 0 {
				wm.Data = e.Data
			}
		case *schemauser.DeletedEvent:
			wm.State = domain.UserStateDeleted
		case *schemauser.EmailUpdatedEvent:
			wm.Email = string(e.EmailAddress)
		case *schemauser.EmailCodeAddedEvent:
			wm.IsEmailVerified = false
			wm.EmailVerifiedFailedCount = 0
		case *schemauser.EmailVerifiedEvent:
			wm.IsEmailVerified = true
			wm.EmailVerifiedFailedCount = 0
		case *schemauser.EmailVerificationFailedEvent:
			wm.EmailVerifiedFailedCount += 1
		case *schemauser.PhoneChangedEvent:
			wm.Phone = string(e.PhoneNumber)
		case *schemauser.PhoneCodeAddedEvent:
			wm.IsPhoneVerified = false
			wm.PhoneVerifiedFailedCount = 0
		case *schemauser.PhoneVerifiedEvent:
			wm.PhoneVerifiedFailedCount = 0
			wm.IsPhoneVerified = true
		case *schemauser.PhoneVerificationFailedEvent:
			wm.PhoneVerifiedFailedCount += 1
		}
	}
	return wm.WriteModel.Reduce()
}

func (wm *UserV3WriteModel) Query() *eventstore.SearchQueryBuilder {
	query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
		ResourceOwner(wm.ResourceOwner).
		AddQuery().
		AggregateTypes(schemauser.AggregateType).
		AggregateIDs(wm.AggregateID).
		EventTypes(
			schemauser.CreatedType,
			schemauser.DeletedType,
		)
	if wm.DataWM {
		query = query.EventTypes(
			schemauser.UpdatedType,
		)
	}
	if wm.EmailWM {
		query = query.EventTypes(
			schemauser.EmailUpdatedType,
			schemauser.EmailVerifiedType,
			schemauser.EmailCodeAddedType,
			schemauser.EmailVerificationFailedType,
		)
	}
	if wm.PhoneWM {
		query = query.EventTypes(
			schemauser.PhoneUpdatedType,
			schemauser.PhoneVerifiedType,
			schemauser.PhoneCodeAddedType,
			schemauser.PhoneVerificationFailedType,
		)
	}
	return query.Builder()
}

func (wm *UserV3WriteModel) NewUpdatedEvent(
	ctx context.Context,
	agg *eventstore.Aggregate,
	schemaID *string,
	schemaRevision *uint64,
	data json.RawMessage,
) *schemauser.UpdatedEvent {
	changes := make([]schemauser.Changes, 0)
	if schemaID != nil && wm.SchemaID != *schemaID {
		changes = append(changes, schemauser.ChangeSchemaID(wm.SchemaID, *schemaID))
	}
	if schemaRevision != nil && wm.SchemaRevision != *schemaRevision {
		changes = append(changes, schemauser.ChangeSchemaRevision(wm.SchemaRevision, *schemaRevision))
	}
	if !bytes.Equal(wm.Data, data) {
		changes = append(changes, schemauser.ChangeData(data))
	}
	if len(changes) == 0 {
		return nil
	}
	return schemauser.NewUpdatedEvent(ctx, agg, changes)
}

func UserV3AggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
	return &eventstore.Aggregate{
		ID:            wm.AggregateID,
		Type:          schemauser.AggregateType,
		ResourceOwner: wm.ResourceOwner,
		InstanceID:    wm.InstanceID,
		Version:       schemauser.AggregateVersion,
	}
}

func (wm *UserV3WriteModel) Exists() bool {
	return wm.State != domain.UserStateDeleted && wm.State != domain.UserStateUnspecified
}