package repository

import (
	"context"
	"testing"

	"route256/cart/internal/domain/entity"
	"route256/cart/internal/domain/model"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestAddItem(t *testing.T) {
	t.Parallel()

	var (
		repo = NewInMemoryRepository(10)
		uid  = entity.UID(1337)
		sku  = entity.Sku(123)
		item = &model.Item{
			Product: &model.Product{
				Name:  "",
				Price: 0,
				Sku:   sku,
			},
			Count: 2,
		}
		ctx = context.Background()
	)

	err := repo.AddItem(ctx, uid, item)
	require.NoError(t, err, "check add item error")

	assert.Len(t, repo.storage, 1, "check map length")
	assert.Len(t, repo.storage[uid].Items, 1, "check list length")
	assert.Equal(t, uint32(2), repo.storage[uid].ItemCount[sku], "check count value")
	assert.Equal(t, uid, repo.storage[uid].UserID, "check uid")
}

func TestAddItemMultiple(t *testing.T) {
	t.Parallel()

	var (
		repo  = NewInMemoryRepository(10)
		uid   = entity.UID(1337)
		item1 = &model.Item{
			Product: &model.Product{
				Name:  "",
				Price: 0,
				Sku:   1,
			},
			Count: 1,
		}
		item2 = &model.Item{
			Product: &model.Product{
				Name:  "",
				Price: 0,
				Sku:   2,
			},
			Count: 2,
		}
		ctx = context.Background()
	)

	err := repo.AddItem(ctx, uid, item1)
	require.NoError(t, err, "check add item error")

	err = repo.AddItem(ctx, uid, item2)
	require.NoError(t, err, "check add item error")

	assert.Len(t, repo.storage, 1, "check map length")
	assert.Len(t, repo.storage[uid].Items, 2, "check list length")
	assert.Equal(t, uint32(1), repo.storage[uid].ItemCount[1], "check count value of item 1")
	assert.Equal(t, uint32(2), repo.storage[uid].ItemCount[2], "check count value of item 2")
	assert.Equal(t, uid, repo.storage[uid].UserID, "check uid")
}

func TestGetItemsByUserID(t *testing.T) {
	t.Parallel()

	var (
		repo  = NewInMemoryRepository(10)
		uid   = entity.UID(1337)
		item1 = &model.Item{
			Product: &model.Product{
				Name:  "",
				Price: 0,
				Sku:   1,
			},
			Count: 1,
		}
		item2 = &model.Item{
			Product: &model.Product{
				Name:  "",
				Price: 0,
				Sku:   2,
			},
			Count: 2,
		}
		ctx = context.Background()
	)

	err := repo.AddItem(ctx, uid, item1)
	require.NoError(t, err, "check add item error")

	err = repo.AddItem(ctx, uid, item2)
	require.NoError(t, err, "check add item error")

	cart, err := repo.GetItemsByUserID(ctx, uid)
	require.NoError(t, err, "check get items error")

	require.Equal(t, uid, cart.UserID, "check cart uid")
	require.Len(t, cart.Items, 2, "check cart len")
	require.Equal(t, uint32(1), cart.ItemCount[item1.Product.Sku], "check item 1 count")
	require.Equal(t, uint32(2), cart.ItemCount[item2.Product.Sku], "check item 2 count")
}

func TestGetItemsByUserIDNoCart(t *testing.T) {
	t.Parallel()

	var (
		repo = NewInMemoryRepository(10)
		uid  = entity.UID(1337)
		ctx  = context.Background()
	)

	cart, err := repo.GetItemsByUserID(ctx, uid)
	require.NoError(t, err, "check get items error")

	require.Equal(t, entity.Cart{}, cart, "check cart")
}

func TestDeleteItem(t *testing.T) {
	t.Parallel()

	var (
		repo = NewInMemoryRepository(10)
		uid  = entity.UID(1337)
		sku  = entity.Sku(123)
		item = &model.Item{
			Product: &model.Product{
				Name:  "",
				Price: 0,
				Sku:   sku,
			},
			Count: 2,
		}
		ctx = context.Background()
	)

	err := repo.AddItem(ctx, uid, item)
	require.NoError(t, err, "check add item error")

	assert.Len(t, repo.storage, 1, "check map length")
	assert.Len(t, repo.storage[uid].Items, 1, "check list length")

	err = repo.DeleteItem(ctx, uid, item.Product.Sku)
	require.NoError(t, err, "check delete item error")

	assert.Len(t, repo.storage[uid].Items, 0, "check list length")
}

func TestDeleteItemUnknownUser(t *testing.T) {
	t.Parallel()

	var (
		repo = NewInMemoryRepository(10)
		uid  = entity.UID(1337)
		sku  = entity.Sku(123)
		ctx  = context.Background()
	)

	err := repo.DeleteItem(ctx, uid, sku)
	require.ErrorIs(t, err, model.ErrCartNotFound, "check cart not found err")
}

func TestDeleteItemUnknownItem(t *testing.T) {
	t.Parallel()

	var (
		repo = NewInMemoryRepository(10)
		uid  = entity.UID(1337)
		sku  = entity.Sku(123)
		item = &model.Item{
			Product: &model.Product{
				Name:  "",
				Price: 0,
				Sku:   sku,
			},
			Count: 2,
		}
		ctx = context.Background()
	)

	err := repo.AddItem(ctx, uid, item)
	require.NoError(t, err, "check add item error")

	assert.Len(t, repo.storage, 1, "check map length")
	assert.Len(t, repo.storage[uid].Items, 1, "check list length")

	err = repo.DeleteItem(ctx, uid, 1)
	require.ErrorIs(t, err, model.ErrItemNotFoundInCart, "check delete item error")

	assert.Len(t, repo.storage[uid].Items, 1, "check list length")
}

func TestDeleteItemEmptyCart(t *testing.T) {
	t.Parallel()

	var (
		repo = NewInMemoryRepository(10)
		uid  = entity.UID(1337)
		sku  = entity.Sku(123)
		item = &model.Item{
			Product: &model.Product{
				Name:  "",
				Price: 0,
				Sku:   sku,
			},
			Count: 2,
		}
		ctx = context.Background()
	)

	err := repo.AddItem(ctx, uid, item)
	require.NoError(t, err, "check add item error")

	assert.Len(t, repo.storage, 1, "check map length")
	assert.Len(t, repo.storage[uid].Items, 1, "check list length")

	err = repo.DeleteItem(ctx, uid, item.Product.Sku)
	require.NoError(t, err, "check delete item error")

	assert.Len(t, repo.storage[uid].Items, 0, "check list length")

	err = repo.DeleteItem(ctx, uid, item.Product.Sku)
	require.NoError(t, err, "check empty cart no err")
}

func TestDeleteItemsByUserID(t *testing.T) {
	t.Parallel()

	var (
		repo = NewInMemoryRepository(10)
		uid  = entity.UID(1337)
		sku  = entity.Sku(123)
		item = &model.Item{
			Product: &model.Product{
				Name:  "",
				Price: 0,
				Sku:   sku,
			},
			Count: 2,
		}
		ctx = context.Background()
	)

	err := repo.AddItem(ctx, uid, item)
	require.NoError(t, err, "check add item error")

	assert.Len(t, repo.storage, 1, "check map length")
	assert.Len(t, repo.storage[uid].Items, 1, "check list length")

	err = repo.DeleteItemsByUserID(ctx, uid)
	require.NoError(t, err, "check delete items error")

	assert.Len(t, repo.storage, 0, "check storage length")
}
