package repository

import (
	"context"
	"sync"

	"route256/cart/internal/domain/entity"
	"route256/cart/internal/domain/model"
	"route256/cart/internal/infra/http/metrics"
)

type storage = map[entity.UID]*entity.Cart

type InMemoryRepository struct {
	storage storage
	mx      sync.RWMutex
}

func NewInMemoryRepository(cap int) *InMemoryRepository {
	return &InMemoryRepository{
		storage: make(storage, cap),
	}
}

func (r *InMemoryRepository) AddItem(_ context.Context, userID entity.UID, item *model.Item) error {
	r.mx.Lock()
	defer r.mx.Unlock()

	cart, ok := r.storage[userID]
	if !ok {
		cart = &entity.Cart{
			UserID:    userID,
			Items:     []entity.Sku{},
			ItemCount: map[entity.Sku]uint32{},
		}

		r.storage[userID] = cart
		metrics.SetInMemoryObjects(len(r.storage))
	}

	if _, ok := cart.ItemCount[item.Product.Sku]; !ok {
		cart.Items = append(cart.Items, item.Product.Sku)
	}

	cart.ItemCount[item.Product.Sku] += item.Count

	return nil
}

func (r *InMemoryRepository) GetItemsByUserID(_ context.Context, userID entity.UID) (entity.Cart, error) {
	r.mx.Lock()
	defer r.mx.Unlock()

	cart, ok := r.storage[userID]
	if !ok {
		return entity.Cart{}, nil
	}

	resultCart := entity.Cart{
		UserID:    userID,
		Items:     make([]entity.Sku, len(cart.Items)),
		ItemCount: make(map[entity.Sku]uint32, len(cart.ItemCount)),
	}

	for i, sku := range cart.Items {
		resultCart.Items[i] = cart.Items[i]
		resultCart.ItemCount[sku] = cart.ItemCount[sku]
	}

	return resultCart, nil
}

func (r *InMemoryRepository) DeleteItem(_ context.Context, userID entity.UID, sku entity.Sku) error {
	r.mx.Lock()
	defer r.mx.Unlock()

	cart, ok := r.storage[userID]
	if !ok {
		return model.ErrCartNotFound
	}

	if len(cart.Items) == 0 {
		return nil
	}

	delete(cart.ItemCount, sku)

	i := 0
	found := false
	for _, v := range cart.Items {
		if v == sku {
			found = true

			break
		}

		i++
	}

	if !found {
		return model.ErrItemNotFoundInCart
	}

	cart.Items[i] = cart.Items[len(cart.Items)-1]
	cart.Items = cart.Items[:len(cart.Items)-1]

	return nil
}

func (r *InMemoryRepository) DeleteItemsByUserID(_ context.Context, userID entity.UID) error {
	r.mx.Lock()
	defer r.mx.Unlock()

	_, ok := r.storage[userID]
	if ok {
		delete(r.storage, userID)
		metrics.SetInMemoryObjects(len(r.storage))
	}

	return nil
}
