package middleware

import (
	"context"
	"strings"

	"git.perx.ru/perxis/perxis-go/pkg/cache"
	envService "git.perx.ru/perxis/perxis-go/pkg/environments"
	service "git.perx.ru/perxis/perxis-go/pkg/items"
)

func makeKey(ss ...string) string {
	return strings.Join(ss, "-")
}

func CachingMiddleware(cache, cachePublished cache.Cache, envs envService.Environments) Middleware {
	return func(next service.Items) service.Items {
		return &cachingMiddleware{
			cache:          cache,
			cachePublished: cachePublished,
			Items:          next,
			envs:           envs,
		}
	}
}

type cachingMiddleware struct {
	cache          cache.Cache
	cachePublished cache.Cache
	envs           envService.Environments
	service.Items
}

func (m cachingMiddleware) Get(ctx context.Context, spaceId, envId, collectionId, itemId string, options ...*service.GetOptions) (itm *service.Item, err error) {

	value, e := m.cache.Get(makeKey(spaceId, envId, collectionId, itemId))
	if e == nil {
		return value.(*service.Item).Clone(), err
	}
	itm, err = m.Items.Get(ctx, spaceId, envId, collectionId, itemId, options...)
	if err == nil {
		env, err := m.envs.Get(ctx, itm.SpaceID, itm.EnvID)
		if err != nil {
			return nil, err
		}
		_ = m.cache.Set(makeKey(itm.SpaceID, env.ID, itm.CollectionID, itm.ID), itm)
		for _, al := range env.Aliases {
			_ = m.cache.Set(makeKey(itm.SpaceID, al, itm.CollectionID, itm.ID), itm)
		}
	}
	return itm.Clone(), err
}

func (m cachingMiddleware) Update(ctx context.Context, item *service.Item, options ...*service.UpdateOptions) (err error) {

	err = m.Items.Update(ctx, item, options...)
	if err == nil {
		env, err := m.envs.Get(ctx, item.SpaceID, item.EnvID)
		if err != nil {
			return err
		}
		_ = m.cache.Remove(makeKey(item.SpaceID, env.ID, item.CollectionID, item.ID))
		_ = m.cachePublished.Remove(makeKey(item.SpaceID, env.ID, item.CollectionID, item.ID))
		for _, al := range env.Aliases {
			_ = m.cache.Remove(makeKey(item.SpaceID, al, item.CollectionID, item.ID))
			_ = m.cachePublished.Remove(makeKey(item.SpaceID, al, item.CollectionID, item.ID))
		}
	}
	return err
}

func (m cachingMiddleware) Delete(ctx context.Context, del *service.Item, options ...*service.DeleteOptions) (err error) {

	err = m.Items.Delete(ctx, del, options...)
	if err == nil {
		env, err := m.envs.Get(ctx, del.SpaceID, del.EnvID)
		if err != nil {
			return err
		}
		_ = m.cache.Remove(makeKey(del.SpaceID, env.ID, del.CollectionID, del.ID))
		_ = m.cachePublished.Remove(makeKey(del.SpaceID, env.ID, del.CollectionID, del.ID))
		for _, al := range env.Aliases {
			_ = m.cache.Remove(makeKey(del.SpaceID, al, del.CollectionID, del.ID))
			_ = m.cachePublished.Remove(makeKey(del.SpaceID, al, del.CollectionID, del.ID))
		}

	}
	return err
}

func (m cachingMiddleware) Publish(ctx context.Context, item *service.Item, options ...*service.PublishOptions) (err error) {

	err = m.Items.Publish(ctx, item, options...)
	if err == nil {
		env, err := m.envs.Get(ctx, item.SpaceID, item.EnvID)
		if err != nil {
			return err
		}
		_ = m.cache.Remove(makeKey(item.SpaceID, env.ID, item.CollectionID, item.ID))
		_ = m.cachePublished.Remove(makeKey(item.SpaceID, env.ID, item.CollectionID, item.ID))
		for _, al := range env.Aliases {
			_ = m.cache.Remove(makeKey(item.SpaceID, al, item.CollectionID, item.ID))
			_ = m.cachePublished.Remove(makeKey(item.SpaceID, al, item.CollectionID, item.ID))
		}
	}
	return err
}

func (m cachingMiddleware) Unpublish(ctx context.Context, item *service.Item, options ...*service.UnpublishOptions) (err error) {

	err = m.Items.Unpublish(ctx, item, options...)
	if err == nil {
		env, err := m.envs.Get(ctx, item.SpaceID, item.EnvID)
		if err != nil {
			return err
		}
		_ = m.cache.Remove(makeKey(item.SpaceID, env.ID, item.CollectionID, item.ID))
		_ = m.cachePublished.Remove(makeKey(item.SpaceID, env.ID, item.CollectionID, item.ID))
		for _, al := range env.Aliases {
			_ = m.cache.Remove(makeKey(item.SpaceID, al, item.CollectionID, item.ID))
			_ = m.cachePublished.Remove(makeKey(item.SpaceID, al, item.CollectionID, item.ID))
		}
	}
	return err
}

func (m cachingMiddleware) GetPublished(ctx context.Context, spaceId, envId, collectionId, itemId string, options ...*service.GetPublishedOptions) (itm *service.Item, err error) {

	opts := service.MergeGetPublishedOptions(options...)

	val, e := m.cachePublished.Get(makeKey(spaceId, envId, collectionId, itemId))
	if e == nil {
		value := val.(map[string]*service.Item)
		if i, ok := value[opts.LocaleID]; ok {
			return i.Clone(), nil
		}
	}

	itm, err = m.Items.GetPublished(ctx, spaceId, envId, collectionId, itemId, opts)

	if err == nil {
		env, err := m.envs.Get(ctx, itm.SpaceID, itm.EnvID)
		if err != nil {
			return nil, err
		}
		var value = make(map[string]*service.Item)
		if val != nil {
			value = val.(map[string]*service.Item)
		}
		value[opts.LocaleID] = itm
		_ = m.cachePublished.Set(makeKey(itm.SpaceID, env.ID, itm.CollectionID, itm.ID), value)
		for _, al := range env.Aliases {
			_ = m.cachePublished.Set(makeKey(itm.SpaceID, al, itm.CollectionID, itm.ID), value)
		}
	}

	return itm.Clone(), err
}

func (m cachingMiddleware) Archive(ctx context.Context, item *service.Item, options ...*service.ArchiveOptions) (err error) {

	err = m.Items.Archive(ctx, item, options...)
	if err == nil {
		env, err := m.envs.Get(ctx, item.SpaceID, item.EnvID)
		if err != nil {
			return err
		}
		_ = m.cache.Remove(makeKey(item.SpaceID, env.ID, item.CollectionID, item.ID))
		_ = m.cachePublished.Remove(makeKey(item.SpaceID, env.ID, item.CollectionID, item.ID))
		for _, al := range env.Aliases {
			_ = m.cache.Remove(makeKey(item.SpaceID, al, item.CollectionID, item.ID))
			_ = m.cachePublished.Remove(makeKey(item.SpaceID, al, item.CollectionID, item.ID))
		}
	}
	return err
}