func ignoreDeleteError(err error) error {
	if err != nil {
		merr, ok := err.(*errors.Error)
		if !ok {
			return err
		}
		if merr.Code == 400 || merr.Code == 404 {
			return nil
		}
		return err
	}
	return nil
}
ignoreDeleteError will ignore any 400 or 404 errors returned, useful for idempotent deletes
package handler
import (
	"context"
	"encoding/json"
	"math/rand"
	"strings"
	"time"
	"github.com/google/uuid"
	"github.com/micro/micro/v3/util/auth/namespace"
	customer "github.com/m3o/services/customers/proto"
	eventspb "github.com/m3o/services/pkg/events/proto/customers"
	apiproto "github.com/micro/micro/v3/proto/api"
	aproto "github.com/micro/micro/v3/proto/auth"
	"github.com/micro/micro/v3/service"
	"github.com/micro/micro/v3/service/auth"
	mauth "github.com/micro/micro/v3/service/auth/client"
	"github.com/micro/micro/v3/service/client"
	"github.com/micro/micro/v3/service/errors"
	mevents "github.com/micro/micro/v3/service/events"
	log "github.com/micro/micro/v3/service/logger"
	mstore "github.com/micro/micro/v3/service/store"
)
type Customers struct {
	accountsService aproto.AccountsService
	apiService      apiproto.ApiService
	auth            auth.Auth
}
const (
	statusUnverified = "unverified"
	statusVerified   = "verified"
	statusActive     = "active"
	statusDeleted    = "deleted"
	statusBanned     = "banned"
	prefixCustomer      = "customers/"
	prefixCustomerEmail = "email/"
	microNamespace = "micro"
)
var validStatus = map[string]bool{
	statusUnverified: true,
	statusVerified:   true,
	statusActive:     true,
	statusDeleted:    true,
}
type CustomerModel struct {
	ID      string
	Email   string
	Status  string
	Created int64
	Updated int64
	Name    string
	Meta    map[string]string
}
func New(service *service.Service) *Customers {
	c := &Customers{
		accountsService: aproto.NewAccountsService("auth", service.Client()),
		apiService:      apiproto.NewApiService("api", service.Client()),
		auth:            mauth.NewAuth(),
	}
	return c
}
func objToProto(cust *CustomerModel) *customer.Customer {
	return &customer.Customer{
		Id:      cust.ID,
		Status:  cust.Status,
		Created: cust.Created,
		Email:   cust.Email,
		Updated: cust.Updated,
		Name:    cust.Name,
		Meta:    cust.Meta,
	}
}
func objToEvent(cust *CustomerModel) *eventspb.Customer {
	return &eventspb.Customer{
		Id:      cust.ID,
		Status:  cust.Status,
		Created: cust.Created,
		Email:   cust.Email,
		Updated: cust.Updated,
		Name:    cust.Name,
		Meta:    cust.Meta,
	}
}
func (c *Customers) Create(ctx context.Context, request *customer.CreateRequest, response *customer.CreateResponse) error {
	acc, err := authorizeCall(ctx, "")
	if err != nil {
		return err
	}
	email := request.Email
	if email == "" {
		email = request.Id
	}
	if strings.TrimSpace(email) == "" {
		return errors.BadRequest("customers.create", "Email is required")
	}
	var cust *CustomerModel
	existingCust, err := readCustomerByEmail(email)
	if err != nil {
		if !strings.Contains(err.Error(), "not found") {
			return err
		}
		cust = &CustomerModel{
			ID:     uuid.New().String(),
			Status: statusUnverified,
			Email:  email,
		}
	} else {
		if existingCust.Status == statusUnverified {
			cust = existingCust
		} else {
			return errors.BadRequest("customers.create.exists", "Customer with this email already exists")
		}
	}
	if err := writeCustomer(cust); err != nil {
		return err
	}
	response.Customer = objToProto(cust)
	ev := &eventspb.Event{
		Type:     eventspb.EventType_EventTypeCreated,
		CallerId: acc.ID,
		Created:  &eventspb.Created{},
		Customer: objToEvent(cust),
	}
	if err := mevents.Publish(eventspb.Topic, ev); err != nil {
		log.Errorf("Error publishing event %+v", ev)
	}
	return nil
}
func (c *Customers) MarkVerified(ctx context.Context, request *customer.MarkVerifiedRequest, response *customer.MarkVerifiedResponse) error {
	email := request.Email
	if email == "" {
		email = request.Id
	}
	if strings.TrimSpace(email) == "" {
		return errors.BadRequest("customers.markverified", "Email is required")
	}
	cust, err := readCustomerByEmail(email)
	if err != nil {
		return err
	}
	acc, err := authorizeCall(ctx, cust.ID)
	if err != nil {
		return err
	}
	cus, err := updateCustomerStatusByEmail(email, statusVerified)
	if err != nil {
		return err
	}
	ev := &eventspb.Event{
		Type:     eventspb.EventType_EventTypeVerified,
		Customer: objToEvent(cus),
		CallerId: acc.ID,
	}
	if err := mevents.Publish(eventspb.Topic, ev); err != nil {
		log.Errorf("Error publishing event %+v %s", ev, err)
	}
	return nil
}
func readCustomerByID(customerID string) (*CustomerModel, error) {
	return readCustomer(customerID, prefixCustomer)
}
func readCustomerByEmail(email string) (*CustomerModel, error) {
	return readCustomer(email, prefixCustomerEmail)
}
func readCustomer(id, prefix string) (*CustomerModel, error) {
	recs, err := mstore.Read(prefix + id)
	if err != nil {
		return nil, err
	}
	if len(recs) != 1 {
		return nil, errors.InternalServerError("customers.read.toomanyrecords", "Cannot find record to update")
	}
	rec := recs[0]
	cust := &CustomerModel{}
	if err := json.Unmarshal(rec.Value, cust); err != nil {
		return nil, err
	}
	if cust.Status == statusDeleted {
		return nil, errors.NotFound("customers.read.notfound", "Customer not found")
	}
	if cust.Meta == nil {
		cust.Meta = map[string]string{}
	}
	if cust.Meta["generated_name"] == "" {
		cust.Meta["generated_name"] = superheroes[rand.Int()%len(superheroes)]
	}
	writeCustomer(cust)
	return cust, nil
}
func (c *Customers) Read(ctx context.Context, request *customer.ReadRequest, response *customer.ReadResponse) error {
	if strings.TrimSpace(request.Id) == "" && strings.TrimSpace(request.Email) == "" {
		return errors.BadRequest("customers.read", "ID or Email is required")
	}
	var cust *CustomerModel
	var err error
	if request.Id != "" {
		cust, err = readCustomerByID(request.Id)
	} else {
		cust, err = readCustomerByEmail(request.Email)
	}
	if err != nil {
		return err
	}
	if _, err := authorizeCall(ctx, cust.ID); err != nil {
		return err
	}
	response.Customer = objToProto(cust)
	return nil
}
func (c *Customers) Delete(ctx context.Context, request *customer.DeleteRequest, response *customer.DeleteResponse) error {
	if strings.TrimSpace(request.Id) == "" && strings.TrimSpace(request.Email) == "" {
		return errors.BadRequest("customers.delete", "ID or Email is required")
	}
	if len(request.Id) == 0 {
		c, err := readCustomerByEmail(request.Email)
		if err != nil {
			return err
		}
		request.Id = c.ID
	}
	if _, err := authorizeCall(ctx, request.Id); err != nil {
		return err
	}
	if err := c.deleteCustomer(ctx, request.Id, request.Force); err != nil {
		log.Errorf("Error deleting customer %s %s", request.Id, err)
		return errors.InternalServerError("customers.delete", "Error deleting customer")
	}
	return nil
}
func updateCustomerStatusByEmail(email, status string) (*CustomerModel, error) {
	return updateCustomerStatus(email, status, prefixCustomerEmail)
}
func updateCustomerStatusByID(id, status string) (*CustomerModel, error) {
	return updateCustomerStatus(id, status, prefixCustomer)
}
func updateCustomerStatus(id, status, prefix string) (*CustomerModel, error) {
	cust, err := readCustomer(id, prefix)
	if err != nil {
		return nil, err
	}
	cust.Status = status
	if err := writeCustomer(cust); err != nil {
		return nil, err
	}
	return cust, nil
}
func writeCustomer(cust *CustomerModel) error {
	now := time.Now().Unix()
	if cust.Created == 0 {
		cust.Created = now
	}
	cust.Updated = now
	if cust.Meta == nil {
		cust.Meta = map[string]string{}
	}
	if cust.Meta["generated_name"] == "" {
		cust.Meta["generated_name"] = superheroes[rand.Int()%len(superheroes)]
	}
	b, _ := json.Marshal(*cust)
	if err := mstore.Write(&mstore.Record{
		Key:   prefixCustomer + cust.ID,
		Value: b,
	}); err != nil {
		return err
	}
	if err := mstore.Write(&mstore.Record{
		Key:   prefixCustomerEmail + cust.Email,
		Value: b,
	}); err != nil {
		return err
	}
	return nil
}
func authorizeCall(ctx context.Context, customerID string) (*auth.Account, error) {
	account, ok := auth.AccountFromContext(ctx)
	if !ok {
		return nil, errors.Unauthorized("customers", "Unauthorized request")
	}
	if account.Issuer != microNamespace {
		return nil, errors.Unauthorized("customers", "Unauthorized request")
	}
	if account.Type == "customer" && account.ID == customerID {
		return account, nil
	}
	if account.Type == "user" && hasScope("admin", account.Scopes) {
		return account, nil
	}
	if account.Type == "service" {
		return account, nil
	}
	return nil, errors.Unauthorized("customers", "Unauthorized request")
}
func authorizeAdmin(ctx context.Context) error {
	account, ok := auth.AccountFromContext(ctx)
	if !ok {
		return errors.Unauthorized("customers", "Unauthorized request")
	}
	if account.Issuer != microNamespace {
		return errors.Unauthorized("customers", "Unauthorized request")
	}
	if account.Type == "user" && hasScope("admin", account.Scopes) {
		return nil
	}
	if account.Type == "service" {
		return nil
	}
	return errors.Unauthorized("customers", "Unauthorized request")
}
func hasScope(scope string, scopes []string) bool {
	for _, sc := range scopes {
		if sc == scope {
			return true
		}
	}
	return false
}
func (c *Customers) deleteCustomer(ctx context.Context, customerID string, force bool) error {
	_, err := c.accountsService.Delete(ctx, &aproto.DeleteAccountRequest{
		Id:      customerID,
		Options: &aproto.Options{Namespace: microNamespace},
	}, client.WithAuthToken())
	if ignoreDeleteError(err) != nil {
		return err
	}
	var cust *CustomerModel
	if !force {
		cust, err = updateCustomerStatusByID(customerID, statusDeleted)
		if err != nil {
			return err
		}
	} else {
		cust, err = c.forceDelete(customerID)
		if err != nil {
			return err
		}
	}
	var callerID string
	if acc, ok := auth.AccountFromContext(ctx); ok {
		callerID = acc.ID
	}
	ev := &eventspb.Event{
		Type:     eventspb.EventType_EventTypeDeleted,
		Customer: objToEvent(cust),
		CallerId: callerID,
	}
	if err := mevents.Publish(eventspb.Topic, ev); err != nil {
		log.Errorf("Error publishing event %+v", ev)
	}
	return nil
}
func (c *Customers) forceDelete(customerID string) (*CustomerModel, error) {
	cust, err := readCustomer(customerID, prefixCustomer)
	if err != nil {
		return nil, err
	}
	if err := mstore.Delete(prefixCustomerEmail + cust.Email); err != nil {
		return nil, err
	}
	if err := mstore.Delete(prefixCustomer + customerID); err != nil {
		return nil, err
	}
	return cust, nil
}