package controllers

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

	"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)
	EditItemView(*gin.Context)
	EditItemHandler(*gin.Context)
	DeleteItemView(*gin.Context)
	DeleteItemHandler(*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)
}

// this currently creates quite big preview images
// workaround is running the following command in the uploads folder:
// for file in *.png; do convert "$file" -resize 35% "$file"; done
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")
	fitToPage := ctx.PostForm("fit-to-page")
	customPrintOptions := ctx.PostForm("custom-print-options")

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

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

	if err == nil {
		dstPdf = filepath.Join("static/uploads", pdf.Filename)
		dstPdf = strings.ReplaceAll(dstPdf, " ", "_")
		fmt.Println("Saving pdf at ", dstPdf)
		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())
			}

			cmd2 := exec.Command("convert", dstImage, "-resize", "35%", dstImage)
			_, err = cmd2.Output()

			if err != nil {
				fmt.Println("Error during resizing preview image: ", 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,
		})
	}

	doFit := false
	fmt.Println("FitToPage: ", fitToPage)
	if fitToPage == "true" {
		doFit = true
	}

	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,
		WasPrinted:         false,
		FitToPage:          doFit,
		CustomPrintOptions: customPrintOptions,
	}

	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, "error.html", gin.H{"data": gin.H{"error": "Item does not exist"}})
		return
	}

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

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

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

	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)
		dstPdf = strings.ReplaceAll(dstPdf, " ", "_")
		fmt.Println("Saving pdf at ", dstPdf)

		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())
		}

		cmd2 := exec.Command("convert", dstImage, "-resize", "35%", dstImage)
		_, err = cmd2.Output()

		if err != nil {
			fmt.Println("Error during resizing preview image: ", 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",
			WasPrinted:         false,
			FitToPage:          false,
			CustomPrintOptions: "",
		}

		_, 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 GetCheckedTags(item models.ShopItem) ([]models.CheckedTag, error) {
	allTags, err := repositories.Tags.GetAll()

	if err != nil {
		return nil, err
	}

	var tags []models.CheckedTag
	for _, tag := range allTags {
		tmpTag := models.CheckedTag{
			Tag:     tag,
			Checked: "",
		}

		for _, itemTag := range item.Tags {
			if itemTag.Name == tmpTag.Name {
				tmpTag.Checked = "checked"
				break
			}
		}

		tags = append(tags, tmpTag)
	}

	return tags, nil
}

func (rc *shopItemController) getEditTemplatData(shopItem models.ShopItem) (gin.H, error) {
	tags, err := GetCheckedTags(shopItem)

	if err != nil {
		return gin.H{}, err
	}

	priceBW := ""
	priceColored := ""
	for _, variant := range shopItem.Variants {
		if variant.Name == "B/W" {
			priceBW = fmt.Sprintf("%.2f", variant.Price)
		}

		if variant.Name == "Colored" {
			priceColored = fmt.Sprintf("%.2f", variant.Price)
		}
	}

	templateData := gin.H{
		"error":        "",
		"success":      "",
		"shopItem":     shopItem,
		"tags":         tags,
		"priceBW":      priceBW,
		"priceColored": priceColored,
	}

	id := fmt.Sprintf("%d", shopItem.ID)
	nextShopItem, err := repositories.ShopItems.GetNextOfId(id)

	if err == nil {
		fmt.Println("Setting nextitem")
		fmt.Println(nextShopItem)
		templateData["nextShopItem"] = nextShopItem
	}

	previousShopItem, err := repositories.ShopItems.GetPreviousOfId(id)
	if err == nil {
		templateData["previousShopItem"] = previousShopItem
	}

	return templateData, nil
}

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

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

	templateData, err := rc.getEditTemplatData(shopItem)

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

	data := CreateSessionData(c, templateData)
	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.WasPrinted = false
	newShopItem.FitToPage = shopItem.FitToPage
	newShopItem.CustomPrintOptions = shopItem.CustomPrintOptions

	if len(shopItem.Tags) != 0 {
		newShopItem.Tags = shopItem.Tags
	}

	if shopItem.Image != "static/img/zine.jpg" {
		newShopItem.Image = shopItem.Image
	}

	if shopItem.Pdf != "" {
		newShopItem.Pdf = shopItem.Pdf
	}

	newShopItem.PrintMode = shopItem.PrintMode

	templateData, err := rc.getEditTemplatData(newShopItem)

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

	_, err = repositories.ShopItems.Update(newShopItem)
	if err != nil {
		templateData["error"] = err
		data := CreateSessionData(c, templateData)
		c.HTML(http.StatusOK, "edititem.html", data)
		return
	}

	templateData["success"] = fmt.Sprintf("Item '%s' Updated", newShopItem.Name)
	data := CreateSessionData(c, templateData)
	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 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)
}
