package controllers

import (
	"fmt"
	"net/http"
	"os/exec"
	"path/filepath"
	"strconv"

	"github.com/gin-gonic/gin"

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

type CRUDController interface {
	Create(*gin.Context)
	GetAll(*gin.Context)
	GetById(*gin.Context)
	Update(*gin.Context)
	Delete(*gin.Context)
}

type ShopItemController interface {
	CRUDController
	ShopItemView(*gin.Context)
	AddItemView(*gin.Context)
	AddItemHandler(*gin.Context)
	AddItemsView(*gin.Context)
	AddItemsHandler(*gin.Context)
	CreateTag(*gin.Context)
	GetAllTags(*gin.Context)
	EditItemView(*gin.Context)
	EditItemHandler(*gin.Context)
	DeleteItemView(*gin.Context)
	DeleteItemHandler(*gin.Context)
	TagView(*gin.Context)
	TagHandler(*gin.Context)
	AddTagHandler(*gin.Context)
}

type shopItemController struct{}

func NewShopItemController() ShopItemController {
	return &shopItemController{}
}

func (rc *shopItemController) GetAll(c *gin.Context) {
	shopItems, err := repositories.ShopItems.GetAll()

	if err != nil {
		ReplyError(c, fmt.Errorf("Could not query shopItems"))
		return
	}

	c.JSON(http.StatusOK, shopItems)
}

func (rc *shopItemController) GetById(c *gin.Context) {
	shopItem, err := repositories.ShopItems.GetById(c.Param("id"))

	if err != nil {
		ReplyError(c, fmt.Errorf("Could not query shopItem: %v", err))
		return
	}

	ReplyOK(c, shopItem)
}

func (rc *shopItemController) NewShopItemFromForm(ctx *gin.Context) (models.ShopItem, error) {
	defaultImagePath := "static/img/zine.jpg"
	name := ctx.PostForm("name")
	abstract := ctx.PostForm("abstract")
	description := ctx.PostForm("description")
	categoryStr := ctx.PostForm("category")
	variantNames := ctx.PostFormArray("variant-name[]")
	variantValues := ctx.PostFormArray("variant-value[]")
	tagIds := ctx.PostFormArray("tags[]")
	image, err := ctx.FormFile("image")
	dstImage := defaultImagePath
	printMode := ctx.PostForm("print-mode")

	if err == nil {
		dstImage = filepath.Join("static/uploads", image.Filename)
		if err := ctx.SaveUploadedFile(image, dstImage); err != nil {
			return models.ShopItem{}, fmt.Errorf("Could not save image")
		}
	}

	dstPdf := ""
	pdf, err := ctx.FormFile("pdf")
	fmt.Println(pdf)

	if err == nil {
		dstPdf = filepath.Join("static/uploads", pdf.Filename)
		if err := ctx.SaveUploadedFile(pdf, dstPdf); err != nil {
			return models.ShopItem{}, fmt.Errorf("Could not save PDF")
		}

		if dstImage == defaultImagePath {
			dstImage = dstPdf + ".preview.png"
			cmd := exec.Command("pdftoppm", "-png", "-singlefile", dstPdf, dstPdf+".preview")
			_, err := cmd.Output()

			if err != nil {
				fmt.Println("Error during pdftoppm: ", err.Error())
			}
		}
	} else {
		fmt.Println(err)
	}

	if name == "" || description == "" {
		return models.ShopItem{}, fmt.Errorf("Name or description empty")
	}

	category, err := models.ParseCategory(categoryStr)
	if err != nil {
		return models.ShopItem{}, err
	}

	var variants []models.ItemVariant

	fmt.Println("VariantNames: ", variantNames)
	fmt.Println("VariantValues: ", variantValues)
	if len(variantNames) != len(variantValues) {
		return models.ShopItem{}, fmt.Errorf("Different number of variant names and values")
	}

	for idx := range variantNames {
		if variantValues[idx] == "" || variantNames[idx] == "" {
			continue
		}

		price, err := strconv.ParseFloat(variantValues[idx], 64)
		if err != nil {
			return models.ShopItem{}, fmt.Errorf("Could not variant parse price")
		}

		variants = append(variants, models.ItemVariant{
			Name:  variantNames[idx],
			Price: price,
		})
	}

	shopItem := models.ShopItem{
		Name:        name,
		Abstract:    abstract,
		Description: description,
		Category:    category,
		IsPublic:    true,
		BasePrice:   rc.GetBasePrice(variants),
		Image:       dstImage,
		Pdf:         dstPdf,
		Variants:    variants,
		PrintMode:   printMode,
	}

	fmt.Println("Creating Shopitem: ", shopItem)

	for _, tagId := range tagIds {
		tag, err := repositories.Tags.GetById(tagId)

		if err != nil {
			return models.ShopItem{}, fmt.Errorf("Could not get tag by id")
		}

		shopItem.Tags = append(shopItem.Tags, tag)
	}

	return shopItem, nil
}

func (rc *shopItemController) GetBasePrice(variants []models.ItemVariant) float64 {
	result := 0.0

	for idx, variant := range variants {
		if idx == 0 {
			result = variant.Price
			continue
		}

		if variant.Price < result {
			result = variant.Price
		}
	}

	return result
}

func (rc *shopItemController) Create(c *gin.Context) {
	shopItem, err := rc.NewShopItemFromForm(c)

	if err != nil {
		ReplyError(c, fmt.Errorf("shopItem creation failed: %s", err))
		return
	}

	_, err = repositories.ShopItems.Create(shopItem)

	if err != nil {
		ReplyError(c, fmt.Errorf("shopItem creation failed: %s", err))
		return
	}

	ReplyOK(c, "shopItem was created")
}

func (rc *shopItemController) Update(c *gin.Context) {
	shopItemId, err := strconv.Atoi(c.Param("id"))

	if err != nil {
		ReplyError(c, fmt.Errorf("shopItem with Id '%s' does not exist", c.Param("id")))
		return
	}

	shopItem, err := rc.NewShopItemFromForm(c)

	if err != nil {
		ReplyError(c, err)
		return
	}

	shopItem.ID = uint(shopItemId)
	_, err = repositories.ShopItems.Update(shopItem)

	if err != nil {
		ReplyError(c, fmt.Errorf("shopItem creation failed: %s", err))
		return
	}

	ReplyOK(c, "shopItem was updated")
}

// TODO: delete associated cartitems
func (rc *shopItemController) Delete(c *gin.Context) {
	err := repositories.ShopItems.DeleteById(c.Param("id"))

	if err != nil {
		ReplyError(c, fmt.Errorf("shopItem deletion failed: %s", err))
		return
	}

	ReplyOK(c, "shopItem was deleted")
}

func (rc *shopItemController) ShopItemView(c *gin.Context) {
	shopItem, err := repositories.ShopItems.GetById(c.Param("id"))

	if err != nil {
		c.HTML(http.StatusBadRequest, "shopitem.html", gin.H{"data": gin.H{"error": err}})
	}

	//TODO: get tags by item
	tags, err := repositories.Tags.GetAll()

	if err != nil {
		c.HTML(http.StatusBadRequest, "shopitem.html", gin.H{"data": gin.H{"error": err}})
	}

	data := CreateSessionData(c, gin.H{
		"shopItem": shopItem,
		"tags":     tags,
	})

	if err != nil {
		c.HTML(http.StatusBadRequest, "shopitem.html", data)
	}

	c.HTML(http.StatusOK, "shopitem.html", data)
}

func (rc *shopItemController) AddItemView(c *gin.Context) {
	tags, err := repositories.Tags.GetAll()

	if err != nil {
		c.HTML(http.StatusBadRequest, "additem.html", gin.H{"error": err})
	}

	data := CreateSessionData(c, gin.H{
		"error":   "",
		"success": "",
		"tags":    tags,
	})

	c.HTML(http.StatusOK, "additem.html", data)
}

func (rc *shopItemController) AddItemHandler(c *gin.Context) {
	errorHandler := func(err error, tags []models.Tag) {
		data := CreateSessionData(c, gin.H{
			"error":   err,
			"success": "",
			"tags":    tags,
		})

		c.HTML(http.StatusOK, "additem.html", data)
	}

	tags, err := repositories.Tags.GetAll()
	if err != nil {
		errorHandler(err, tags)
		return
	}

	shopItem, err := rc.NewShopItemFromForm(c)
	if err != nil {
		errorHandler(err, tags)
		return
	}

	_, err = repositories.ShopItems.Create(shopItem)
	if err != nil {
		errorHandler(err, tags)
		return
	}

	data := CreateSessionData(c, gin.H{
		"error":   "",
		"success": fmt.Sprintf("Item '%s' Registered", shopItem.Name),
		"tags":    tags,
	})

	c.HTML(http.StatusOK, "additem.html", data)
}

func (rc *shopItemController) AddItemsView(c *gin.Context) {
	data := CreateSessionData(c, gin.H{})

	c.HTML(http.StatusOK, "batchupload.html", data)
}

func (rc *shopItemController) AddItemsHandler(c *gin.Context) {
	errorHandler := func(err error) {
		data := CreateSessionData(c, gin.H{
			"error": err,
		})
		c.HTML(http.StatusBadRequest, "batchupload.html", data)
	}

	form, err := c.MultipartForm()

	if err != nil {
		errorHandler(err)
		return
	}

	files := form.File["pdf"]

	var shopItems []models.ShopItem
	for _, file := range files {
		dstPdf := filepath.Join("static/uploads", file.Filename)

		if err := c.SaveUploadedFile(file, dstPdf); err != nil {
			errorHandler(err)
			return
		}

		dstImage := dstPdf + ".preview.png"
		cmd := exec.Command("pdftoppm", "-png", "-singlefile", dstPdf, dstPdf+".preview")
		_, err := cmd.Output()

		if err != nil {
			fmt.Println("Error during pdftoppm: ", err.Error())
		}

		category, err := models.ParseCategory("Zine")
		if err != nil {
			errorHandler(err)
			return
		}

		variants := []models.ItemVariant{
			{
				Name:  "B/W",
				Price: 1.0,
			},
		}

		shopItem := models.ShopItem{
			Name:        file.Filename,
			Abstract:    file.Filename,
			Description: file.Filename,
			Category:    category,
			IsPublic:    true,
			BasePrice:   rc.GetBasePrice(variants),
			Image:       dstImage,
			Pdf:         dstPdf,
			Variants:    variants,
			PrintMode:   "CreateBooklet",
		}

		_, err = repositories.ShopItems.Create(shopItem)
		if err != nil {
			errorHandler(err)
			return
		}

		shopItems = append(shopItems, shopItem)
	}

	msg := "The Following items were registered:\n"

	for _, item := range shopItems {
		msg += fmt.Sprintf("%s\n", item.Name)
	}

	data := CreateSessionData(c, gin.H{
		"error":   "",
		"success": msg,
	})

	c.HTML(http.StatusOK, "batchupload.html", data)
}

func (rc *shopItemController) EditItemView(c *gin.Context) {
	shopItem, err := repositories.ShopItems.GetById(c.Param("id"))
	tags, err := repositories.Tags.GetAll()

	if err != nil {
		c.HTML(http.StatusBadRequest, "edititem.html", gin.H{"error": err})
	}

	fmt.Println(shopItem)

	data := CreateSessionData(c, gin.H{
		"error":    "",
		"success":  "",
		"shopItem": shopItem,
		"tags":     tags,
	})

	c.HTML(http.StatusOK, "edititem.html", data)
}

func (rc *shopItemController) EditItemHandler(c *gin.Context) {
	shopItem, err := rc.NewShopItemFromForm(c)

	if err != nil {
		c.HTML(http.StatusBadRequest, "edititem.html", gin.H{"error": err})
		return
	}

	newShopItem, err := repositories.ShopItems.GetById(c.Param("id"))

	if err != nil {
		c.HTML(http.StatusBadRequest, "edititem.html", gin.H{"error": err})
		return
	}

	newShopItem.Name = shopItem.Name
	newShopItem.Abstract = shopItem.Abstract
	newShopItem.Description = shopItem.Description
	newShopItem.Category = shopItem.Category
	newShopItem.Variants = shopItem.Variants
	newShopItem.BasePrice = shopItem.BasePrice
	newShopItem.IsPublic = shopItem.IsPublic
	newShopItem.Tags = shopItem.Tags
	newShopItem.Image = shopItem.Image
	newShopItem.Pdf = shopItem.Pdf
	newShopItem.PrintMode = shopItem.PrintMode

	tags, err := repositories.Tags.GetAll()
	if err != nil {
		c.HTML(http.StatusBadRequest, "edititem.html", gin.H{"error": err})
		return
	}

	_, err = repositories.ShopItems.Update(newShopItem)
	if err != nil {
		data := CreateSessionData(c, gin.H{
			"error":   err,
			"success": "",
			"tags":    tags,
		})

		c.HTML(http.StatusOK, "edititem.html", data)
		return
	}

	data := CreateSessionData(c, gin.H{
		"error":   "",
		"success": fmt.Sprintf("Item '%s' Updated", newShopItem.Name),
		"tags":    tags,
	})

	c.HTML(http.StatusOK, "edititem.html", data)
}

func (rc *shopItemController) DeleteItemView(c *gin.Context) {
	shopItem, err := repositories.ShopItems.GetById(c.Param("id"))
	tags, err := repositories.Tags.GetAll()

	if err != nil {
		c.HTML(http.StatusBadRequest, "deleteitem.html", gin.H{"error": err})
	}

	fmt.Println(shopItem)

	data := CreateSessionData(c, gin.H{
		"error":    "",
		"success":  "",
		"shopItem": shopItem,
		"tags":     tags,
	})

	c.HTML(http.StatusOK, "deleteitem.html", data)
}

func (rc *shopItemController) DeleteItemHandler(c *gin.Context) {
	err := repositories.ShopItems.DeleteById(c.Param("id"))

	if err != nil {
		data := CreateSessionData(c, gin.H{
			"error":   err,
			"success": "",
		})

		c.HTML(http.StatusOK, "deleteitem.html", data)
	}

	shopItems, _ := repositories.ShopItems.GetAll()
	fmt.Println(len(shopItems))

	data := CreateSessionData(c, gin.H{
		"title":     "shopItem Page",
		"shopItems": shopItems,
	})

	fmt.Println(data)

	c.HTML(http.StatusOK, "index.html", data)
}

func (rc *shopItemController) TagHandler(ctx *gin.Context) {
	name := ctx.PostForm("name")
	action := ctx.PostForm("action")

	tag, err := repositories.Tags.GetById(ctx.Param("id"))

	if err != nil {
		fmt.Println(err)
		ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
		return
	}

	if action == "update" {
		tag.Name = name
		tag, err = repositories.Tags.Update(tag)

		if err != nil {
			fmt.Println(err)
			ctx.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
			return
		}
	}

	if action == "delete" {
		repositories.Tags.DeleteById(ctx.Param("id"))
	}

	rc.TagView(ctx)
}

func (rc *shopItemController) AddTagHandler(c *gin.Context) {
	tag, err := models.NewTag(c)

	if err != nil {
		fmt.Println(err)
		c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"error": err})
		return
	}

	_, err = repositories.Tags.Create(tag)
	if err != nil {
		data := CreateSessionData(c, gin.H{
			"error":   err,
			"success": "",
		})

		c.HTML(http.StatusOK, "tagview.html", data)
		return
	}

	rc.TagView(c)
}

func (rc *shopItemController) TagView(c *gin.Context) {
	tags, err := repositories.Tags.GetAll()

	if err != nil {
		c.HTML(http.StatusBadRequest, "tagview.html", gin.H{"data": gin.H{"error": err}})
	}

	data := CreateSessionData(c, gin.H{
		"tags": tags,
	})

	if err != nil {
		c.HTML(http.StatusBadRequest, "tagview.html", data)
	}

	c.HTML(http.StatusOK, "tagview.html", data)
}

func (rc *shopItemController) CreateTag(c *gin.Context) {
	tag, err := models.NewTagByJson(c)

	if err != nil {
		ReplyError(c, err)
	}

	_, err = repositories.Tags.Create(tag)
	if err != nil {
		ReplyError(c, fmt.Errorf("shopItem creation failed: %s", err))
		return
	}

	//userID := user.(models.User).ID
	//rc.DB.Model(&models.shopItem{}).Where("id = ?"), room.ID).Association("Admins").Append(&models.User{ID: userID})

	//if result.Error != nil {
	//	ReplyError(c, fmt.Errorf("shopItem creation failed: %s", result.Error))
	//	return
	//}

	ReplyOK(c, fmt.Sprintf("tag '%s' was created", tag.Name))

}

func (rc *shopItemController) GetAllTags(c *gin.Context) {
	tags, err := repositories.Tags.GetAll()

	if err != nil {
		ReplyError(c, fmt.Errorf("Could not query Tags"))
		return
	}

	c.JSON(http.StatusOK, tags)
}

func ReplyError(ctx *gin.Context, err error) {
	ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}

func ReplyOK(ctx *gin.Context, message any) {
	ctx.JSON(http.StatusOK, message)
}
