package model

import (
	"encoding/json"
	"time"

	"github.com/caos/logging"
	"github.com/caos/zitadel/internal/crypto"
	caos_errs "github.com/caos/zitadel/internal/errors"
	es_models "github.com/caos/zitadel/internal/eventstore/models"
	"github.com/caos/zitadel/internal/user/model"
)

type Human struct {
	*User `json:"-"`

	*Password
	*Profile
	*Email
	*Phone
	*Address
	ExternalIDPs []*ExternalIDP `json:"-"`
	InitCode     *InitUserCode  `json:"-"`
	EmailCode    *EmailCode     `json:"-"`
	PhoneCode    *PhoneCode     `json:"-"`
	PasswordCode *PasswordCode  `json:"-"`
	OTP          *OTP           `json:"-"`
}

type InitUserCode struct {
	es_models.ObjectRoot
	Code   *crypto.CryptoValue `json:"code,omitempty"`
	Expiry time.Duration       `json:"expiry,omitempty"`
}

func HumanFromModel(user *model.Human) *Human {
	human := new(Human)
	if user.Password != nil {
		human.Password = PasswordFromModel(user.Password)
	}
	if user.Profile != nil {
		human.Profile = ProfileFromModel(user.Profile)
	}
	if user.Email != nil {
		human.Email = EmailFromModel(user.Email)
	}
	if user.Phone != nil {
		human.Phone = PhoneFromModel(user.Phone)
	}
	if user.Address != nil {
		human.Address = AddressFromModel(user.Address)
	}
	if user.OTP != nil {
		human.OTP = OTPFromModel(user.OTP)
	}
	if user.ExternalIDPs != nil {
		human.ExternalIDPs = ExternalIDPsFromModel(user.ExternalIDPs)
	}
	return human
}

func HumanToModel(user *Human) *model.Human {
	human := new(model.Human)
	if user.Password != nil {
		human.Password = PasswordToModel(user.Password)
	}
	if user.Profile != nil {
		human.Profile = ProfileToModel(user.Profile)
	}
	if user.Email != nil {
		human.Email = EmailToModel(user.Email)
	}
	if user.Phone != nil {
		human.Phone = PhoneToModel(user.Phone)
	}
	if user.Address != nil {
		human.Address = AddressToModel(user.Address)
	}
	if user.ExternalIDPs != nil {
		human.ExternalIDPs = ExternalIDPsToModel(user.ExternalIDPs)
	}
	if user.InitCode != nil {
		human.InitCode = InitCodeToModel(user.InitCode)
	}
	if user.EmailCode != nil {
		human.EmailCode = EmailCodeToModel(user.EmailCode)
	}
	if user.PhoneCode != nil {
		human.PhoneCode = PhoneCodeToModel(user.PhoneCode)
	}
	if user.PasswordCode != nil {
		human.PasswordCode = PasswordCodeToModel(user.PasswordCode)
	}
	if user.OTP != nil {
		human.OTP = OTPToModel(user.OTP)
	}
	return human
}

func InitCodeFromModel(code *model.InitUserCode) *InitUserCode {
	if code == nil {
		return nil
	}
	return &InitUserCode{
		ObjectRoot: code.ObjectRoot,
		Expiry:     code.Expiry,
		Code:       code.Code,
	}
}

func InitCodeToModel(code *InitUserCode) *model.InitUserCode {
	return &model.InitUserCode{
		ObjectRoot: code.ObjectRoot,
		Expiry:     code.Expiry,
		Code:       code.Code,
	}
}

func (p *Human) AppendEvents(events ...*es_models.Event) error {
	for _, event := range events {
		if err := p.AppendEvent(event); err != nil {
			return err
		}
	}
	return nil
}

func (h *Human) AppendEvent(event *es_models.Event) (err error) {
	switch event.Type {
	case UserAdded,
		UserRegistered,
		UserProfileChanged,
		HumanAdded,
		HumanRegistered,
		HumanProfileChanged:
		h.setData(event)
	case InitializedUserCodeAdded,
		InitializedHumanCodeAdded:
		h.appendInitUsercodeCreatedEvent(event)
	case UserPasswordChanged,
		HumanPasswordChanged:
		err = h.appendUserPasswordChangedEvent(event)
	case UserPasswordCodeAdded,
		HumanPasswordCodeAdded:
		err = h.appendPasswordSetRequestedEvent(event)
	case UserEmailChanged,
		HumanEmailChanged:
		err = h.appendUserEmailChangedEvent(event)
	case UserEmailCodeAdded,
		HumanEmailCodeAdded:
		err = h.appendUserEmailCodeAddedEvent(event)
	case UserEmailVerified,
		HumanEmailVerified:
		h.appendUserEmailVerifiedEvent()
	case UserPhoneChanged,
		HumanPhoneChanged:
		err = h.appendUserPhoneChangedEvent(event)
	case UserPhoneCodeAdded,
		HumanPhoneCodeAdded:
		err = h.appendUserPhoneCodeAddedEvent(event)
	case UserPhoneVerified,
		HumanPhoneVerified:
		h.appendUserPhoneVerifiedEvent()
	case UserPhoneRemoved,
		HumanPhoneRemoved:
		h.appendUserPhoneRemovedEvent()
	case UserAddressChanged,
		HumanAddressChanged:
		err = h.appendUserAddressChangedEvent(event)
	case MFAOTPAdded,
		HumanMFAOTPAdded:
		err = h.appendOTPAddedEvent(event)
	case MFAOTPVerified,
		HumanMFAOTPVerified:
		h.appendOTPVerifiedEvent()
	case MFAOTPRemoved,
		HumanMFAOTPRemoved:
		h.appendOTPRemovedEvent()
	case HumanExternalIDPAdded:
		err = h.appendExternalIDPAddedEvent(event)
	case HumanExternalIDPRemoved, HumanExternalIDPCascadeRemoved:
		err = h.appendExternalIDPRemovedEvent(event)
	}
	if err != nil {
		return err
	}
	h.ComputeObject()
	return nil
}

func (h *Human) ComputeObject() {
	if h.State == int32(model.UserStateUnspecified) || h.State == int32(model.UserStateInitial) {
		if h.Email != nil && h.IsEmailVerified {
			h.State = int32(model.UserStateActive)
		} else {
			h.State = int32(model.UserStateInitial)
		}
	}
	if h.Password != nil && h.Password.ObjectRoot.IsZero() {
		h.Password.ObjectRoot = h.User.ObjectRoot
	}
	if h.Profile != nil && h.Profile.ObjectRoot.IsZero() {
		h.Profile.ObjectRoot = h.User.ObjectRoot
	}
	if h.Email != nil && h.Email.ObjectRoot.IsZero() {
		h.Email.ObjectRoot = h.User.ObjectRoot
	}
	if h.Phone != nil && h.Phone.ObjectRoot.IsZero() {
		h.Phone.ObjectRoot = h.User.ObjectRoot
	}
	if h.Address != nil && h.Address.ObjectRoot.IsZero() {
		h.Address.ObjectRoot = h.User.ObjectRoot
	}
}

func (u *Human) setData(event *es_models.Event) error {
	if err := json.Unmarshal(event.Data, u); err != nil {
		logging.Log("EVEN-8ujgd").WithError(err).Error("could not unmarshal event data")
		return caos_errs.ThrowInternal(err, "MODEL-sj4jd", "could not unmarshal event")
	}
	return nil
}

func (u *Human) appendInitUsercodeCreatedEvent(event *es_models.Event) error {
	initCode := new(InitUserCode)
	err := initCode.SetData(event)
	if err != nil {
		return err
	}
	initCode.ObjectRoot.CreationDate = event.CreationDate
	u.InitCode = initCode
	return nil
}

func (c *InitUserCode) SetData(event *es_models.Event) error {
	c.ObjectRoot.AppendEvent(event)
	if err := json.Unmarshal(event.Data, c); err != nil {
		logging.Log("EVEN-7duwe").WithError(err).Error("could not unmarshal event data")
		return caos_errs.ThrowInternal(err, "MODEL-lo34s", "could not unmarshal event")
	}
	return nil
}