package repositories

import (
	"fmt"
	"gorm.io/gorm"
	"strconv"

	"git.dynamicdiscord.de/kalipso/zineshop/models"
)

type ShopItemRepository interface {
	Create(models.ShopItem) (models.ShopItem, error)
	GetAll() ([]models.ShopItem, error)
	GetAllSorted(string) ([]models.ShopItem, error)
	GetAllNewestFirst() ([]models.ShopItem, error)
	GetAllNewestLast() ([]models.ShopItem, error)
	GetAllLexicalFirst() ([]models.ShopItem, error)
	GetAllLexicalLast() ([]models.ShopItem, error)
	GetAllPublic() ([]models.ShopItem, error)
	GetById(string) (models.ShopItem, error)
	GetNextOfId(string) (models.ShopItem, error)
	GetPreviousOfId(string) (models.ShopItem, error)
	GetByTagId(string) ([]models.ShopItem, error)
	GetVariantById(string) (models.ItemVariant, error)
	Update(models.ShopItem) (models.ShopItem, error)
	DeleteById(string) error
}

type GORMShopItemRepository struct {
	DB *gorm.DB
}

func NewGORMShopItemRepository(db *gorm.DB) ShopItemRepository {
	return &GORMShopItemRepository{
		DB: db,
	}
}

func (r *GORMShopItemRepository) Create(shopItem models.ShopItem) (models.ShopItem, error) {
	result := r.DB.Create(&shopItem)
	if result.Error != nil {
		return models.ShopItem{}, result.Error
	}

	return shopItem, nil
}

func (r *GORMShopItemRepository) GetAll() ([]models.ShopItem, error) {
	var shopItems []models.ShopItem
	result := r.DB.Preload("Tags").Preload("Variants").Find(&shopItems)

	return shopItems, result.Error
}

func (r *GORMShopItemRepository) GetAllSorted(sortString string) ([]models.ShopItem, error) {
	var shopItems []models.ShopItem
	result := r.DB.Preload("Tags").Preload("Variants").Order(sortString).Find(&shopItems)

	return shopItems, result.Error
}

func (r *GORMShopItemRepository) GetAllNewestFirst() ([]models.ShopItem, error) {
	return r.GetAllSorted("created_at desc")
}

func (r *GORMShopItemRepository) GetAllNewestLast() ([]models.ShopItem, error) {
	return r.GetAllSorted("created_at asc")
}

func (r *GORMShopItemRepository) GetAllLexicalFirst() ([]models.ShopItem, error) {
	return r.GetAllSorted("name asc")
}

func (r *GORMShopItemRepository) GetAllLexicalLast() ([]models.ShopItem, error) {
	return r.GetAllSorted("name desc")
}

func (r *GORMShopItemRepository) GetAllPublic() ([]models.ShopItem, error) {
	var shopItems []models.ShopItem
	result := r.DB.Preload("Tags").Preload("Variants").Where("is_public = 1").Find(&shopItems)

	return shopItems, result.Error
}

func (r *GORMShopItemRepository) GetById(id string) (models.ShopItem, error) {
	shopItemId, err := strconv.Atoi(id)

	if err != nil {
		return models.ShopItem{}, err
	}

	var shopItem models.ShopItem
	result := r.DB.Preload("Tags").Preload("Variants").First(&shopItem, uint(shopItemId))

	if result.Error != nil {
		return models.ShopItem{}, result.Error
	}

	return shopItem, nil
}

func (r *GORMShopItemRepository) GetNextOfId(id string) (models.ShopItem, error) {
	var nextItem models.ShopItem
	if err := r.DB.Where("id > ?", id).Order("id asc").First(&nextItem).Error; err != nil {
		if err != gorm.ErrRecordNotFound {
			return models.ShopItem{}, err
		} else {
			return models.ShopItem{}, fmt.Errorf("No Item found")
		}
	}
	return nextItem, nil
}

func (r *GORMShopItemRepository) GetPreviousOfId(id string) (models.ShopItem, error) {
	var previousItem models.ShopItem
	if err := r.DB.Where("id < ?", id).Order("id desc").First(&previousItem).Error; err != nil {
		if err != gorm.ErrRecordNotFound {
			return models.ShopItem{}, err
		} else {
			return models.ShopItem{}, fmt.Errorf("No Item found")
		}
	}
	return previousItem, nil
}

func (r *GORMShopItemRepository) GetByTagId(id string) ([]models.ShopItem, error) {
	tagId, err := strconv.Atoi(id)

	if err != nil {
		return nil, err
	}

	var shopItems []models.ShopItem
	result := r.DB.Joins("JOIN item_tags ON item_tags.shop_item_id = shop_items.id").Where("item_tags.tag_id = ?", tagId).Preload("Tags").Preload("Variants").Find(&shopItems)

	if result.Error != nil {
		return nil, result.Error
	}

	return shopItems, nil
}

func (r *GORMShopItemRepository) GetVariantById(id string) (models.ItemVariant, error) {
	itemVariantId, err := strconv.Atoi(id)

	if err != nil {
		return models.ItemVariant{}, err
	}

	var itemVariant models.ItemVariant
	result := r.DB.First(&itemVariant, uint(itemVariantId))

	if result.Error != nil {
		return models.ItemVariant{}, result.Error
	}

	return itemVariant, nil
}

func (r *GORMShopItemRepository) Update(shopItem models.ShopItem) (models.ShopItem, error) {
	err := r.DB.Model(&shopItem).Association("Tags").Replace(shopItem.Tags)
	if err != nil {
		return models.ShopItem{}, err
	}

	err = r.DB.Model(&shopItem).Association("Variants").Replace(shopItem.Variants)
	if err != nil {
		return models.ShopItem{}, err
	}

	result := r.DB.Save(&shopItem)
	if result.Error != nil {
		return models.ShopItem{}, result.Error
	}

	return shopItem, nil
}

func (r *GORMShopItemRepository) DeleteById(id string) error {
	shopItemId, err := strconv.Atoi(id)

	if err != nil {
		return err
	}

	result := r.DB.Delete(&models.ShopItem{}, shopItemId)
	return result.Error
}
