package controllers

import (
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"

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

type CartItemController interface {
	//CRUDController
	CartItemView(*gin.Context)
	AddItemHandler(*gin.Context)
	DeleteItemHandler(*gin.Context)
	EditItemHandler(*gin.Context)
	CheckoutView(*gin.Context)
	CheckoutHandler(*gin.Context)
	OrderView(*gin.Context)
	OrderHandler(*gin.Context)
	OrdersView(*gin.Context)
	OrdersHandler(*gin.Context)
}

type cartItemController struct{}

func NewCartItemController() CartItemController {
	return &cartItemController{}
}

// getSetCookieValue retrieves the value of a cookie from the Set-Cookie header
func getSetCookieValue(c *gin.Context, cookieName string) string {
	// Check the Set-Cookie headers
	cookies := c.Writer.Header()["Set-Cookie"]
	for _, cookie := range cookies {
		if strings.HasPrefix(cookie, cookieName+"=") {
			// Extract the cookie value
			parts := strings.SplitN(cookie, ";", 2)
			if len(parts) > 0 {
				return strings.TrimPrefix(parts[0], cookieName+"=")
			}
		}
	}
	return "" // Return empty string if cookie is not found
}

func GetSessionId(ctx *gin.Context) string {
	sessionId, err := ctx.Cookie("session_id")

	if err != nil {
		//we need to check if we already set cookie in the response so that we dont do this multiple times
		responseCookie := getSetCookieValue(ctx, "session_id")

		if len(responseCookie) != 0 {
			return responseCookie
		}

		sessionId = utils.GenerateSessionId(16)
		ctx.SetCookie("session_id", sessionId, 3600, "/", "", false, true)
	}

	return sessionId
}

func (rc *cartItemController) NewCartItemFromForm(ctx *gin.Context) (models.CartItem, error) {
	sessionId := GetSessionId(ctx)
	shopItemIdStr := ctx.PostForm("ShopItemId")
	shopItemId, err := strconv.Atoi(shopItemIdStr)
	itemVariantIdStr := ctx.PostForm("ItemVariantId")
	itemVariantId, err := strconv.Atoi(itemVariantIdStr)

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

	quantity := 1

	shopItem, err := repositories.ShopItems.GetById(shopItemIdStr)

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

	itemVariant, err := repositories.ShopItems.GetVariantById(itemVariantIdStr)

	cartItem := models.CartItem{
		SessionId:     sessionId,
		ShopItemId:    uint(shopItemId),
		ShopItem:      shopItem,
		ItemVariantId: uint(itemVariantId),
		ItemVariant:   itemVariant,
		Quantity:      quantity,
	}

	return cartItem, nil
}

func (rc *cartItemController) NewAddressFromForm(ctx *gin.Context) (models.AddressInfo, error) {
	firstName := ctx.PostForm("firstName")
	lastName := ctx.PostForm("lastName")
	address := ctx.PostForm("address")
	postalCode := ctx.PostForm("postalCode")
	city := ctx.PostForm("city")
	country := ctx.PostForm("country")

	if firstName == "" {
		return models.AddressInfo{}, fmt.Errorf("first name missing.")
	}

	if lastName == "" {
		return models.AddressInfo{}, fmt.Errorf("Last name missing.")
	}

	if address == "" {
		return models.AddressInfo{}, fmt.Errorf("address missing.")
	}

	if postalCode == "" {
		return models.AddressInfo{}, fmt.Errorf("postalCode missing.")
	}

	if city == "" {
		return models.AddressInfo{}, fmt.Errorf("city missing.")
	}

	if country == "" {
		return models.AddressInfo{}, fmt.Errorf("country missing.")
	}

	return models.AddressInfo{
		FirstName:  firstName,
		LastName:   lastName,
		Address:    address,
		PostalCode: postalCode,
		City:       city,
		Country:    country,
	}, nil
}

func (rc *cartItemController) NewOrderFromForm(ctx *gin.Context) (models.Order, error) {
	sessionId := GetSessionId(ctx)
	status := models.OrderStatus("AwaitingConfirmation")
	token := utils.GenerateToken()
	email := ctx.PostForm("email")
	comment := ctx.PostForm("comment")
	firstName := ctx.PostForm("firstName")
	lastName := ctx.PostForm("lastName")
	address := ctx.PostForm("address")
	postalCode := ctx.PostForm("postalCode")
	city := ctx.PostForm("city")
	country := ctx.PostForm("country")
	shippingStr := ctx.PostForm("shippingMethod")

	//address, err := rc.NewAddressFromForm(ctx)
	//if shippingStr != "pickup" {
	//	if err != nil {
	//		return models.Order{}, fmt.Errorf("Invalid address information.")
	//	}
	//}

	shipping, err := models.GetShippingMethod(shippingStr)

	if err != nil {
		return models.Order{}, fmt.Errorf("Invalid shipping method.")
	}

	cartItems, err := repositories.CartItems.GetAllBySession(sessionId)
	fmt.Println(sessionId)
	fmt.Println(cartItems)

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

	order := models.Order{
		SessionId:  sessionId,
		Status:     status,
		Token:      token,
		Email:      email,
		Comment:    comment,
		FirstName:  firstName,
		LastName:   lastName,
		Address:    address,
		PostalCode: postalCode,
		City:       city,
		Country:    country,
		Shipping:   shipping.Id,
		CartItems:  cartItems,
	}

	return order, nil
}

func (rc *cartItemController) Create(c *gin.Context) {
	cartItem, err := rc.NewCartItemFromForm(c)

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

	_, err = repositories.CartItems.Create(cartItem)

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

	ReplyOK(c, "cartItem was created")
}

func (rc *cartItemController) Update(c *gin.Context) {
	cartItem, err := rc.NewCartItemFromForm(c)

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

	_, err = repositories.CartItems.Update(cartItem)

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

	ReplyOK(c, "cartItem was updated")
}

//func (rc *cartItemController) Delete(c *gin.Context) {
//	err := repositories.CartItems.DeleteBySessionId(c.Param("id"))
//
//	if err != nil {
//		ReplyError(c, fmt.Errorf("cartItem deletion failed: %s", err))
//		return
//	}
//
//	ReplyOK(c, "cartItem was deleted")
//}

func (rc *cartItemController) CartItemView(c *gin.Context) {
	sessionId := GetSessionId(c)

	cartItems, err := repositories.CartItems.GetAllBySession(sessionId)
	//cartItems, err := repositories.CartItems.GetAll()

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

	priceTotal := 0.0
	for _, cartItem := range cartItems {
		priceTotal += (float64(cartItem.Quantity) * cartItem.ItemVariant.Price)
	}

	fmt.Println("PRICE TOTAL", priceTotal)

	data := CreateSessionData(c, gin.H{
		"cartItems":  cartItems,
		"priceTotal": fmt.Sprintf("%.2f", priceTotal), //round 2 decimals
		"shipping":   models.GetShippingMethods(),
	})

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

func (rc *cartItemController) AddItemHandler(c *gin.Context) {
	cartItem, err := rc.NewCartItemFromForm(c)

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

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

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

	rc.CartItemView(c)
}

func (rc *cartItemController) DeleteItemHandler(c *gin.Context) {
	err := repositories.CartItems.DeleteById(c.PostForm("id"))

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

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

	c.Redirect(http.StatusFound, "/cart")
}

func (rc *cartItemController) EditItemHandler(c *gin.Context) {
	cartItemId := c.PostForm("id")
	cartItem, err := repositories.CartItems.GetById(cartItemId)

	if err != nil {
		fmt.Println(err)
		c.Redirect(http.StatusFound, "/cart")
		return
	}

	action := c.PostForm("action")

	if action == "setAmount" {
		amountStr := c.PostForm("amount")
		amount, err := strconv.Atoi(amountStr)

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

		if amount < 0 {
			c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "amount cant be negative"})
			return
		}
		if amount > 500 {
			c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "amount cant be over 500"})
			return
		}

		cartItem.Quantity = amount
	}

	if action == "increase" {
		cartItem.Quantity += 1
	}

	if action == "decrease" {
		cartItem.Quantity -= 1

		if cartItem.Quantity == 0 {
			cartItem.Quantity = 1
		}
	}

	_, err = repositories.CartItems.Update(cartItem)

	if err != nil {
		fmt.Println(err)
	}

	c.Redirect(http.StatusFound, "/cart")
}

func (rc *cartItemController) CheckoutView(c *gin.Context) {
	shippingMethod := c.Query("shippingMethod")

	if shippingMethod == "" {
		rc.CartItemView(c)
		return
	}

	c.HTML(http.StatusOK, "checkout.html", gin.H{
		"askAddress":     (shippingMethod != "pickup"),
		"shippingMethod": shippingMethod,
	})
}

func (rc *cartItemController) CheckoutHandler(c *gin.Context) {
	order, err := rc.NewOrderFromForm(c)

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

	existingOrder, err := repositories.Orders.GetBySession(order.SessionId)

	if errors.Is(err, gorm.ErrRecordNotFound) {
		fmt.Println("Creating Order")
		createdOrder, err := repositories.Orders.Create(order)

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

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

		for _, cartItem := range order.CartItems {
			cartItem.OrderID = createdOrder.ID
			repositories.CartItems.Update(cartItem)
		}
	} else if err == nil {
		fmt.Println("Updating Order")
		order.ID = existingOrder.ID
		order.CreatedAt = existingOrder.CreatedAt
		_, err := repositories.Orders.Update(order)
		if err != nil {
			fmt.Println(err)
		}
	}

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

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

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

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

	data := CreateSessionData(c, gin.H{
		"error":         "",
		"success":       "",
		"order":         order,
		"askAddress":    (order.Shipping != "pickup"),
		"isPreview":     true,
		"shipping":      shipping,
		"priceProducts": fmt.Sprintf("%.2f", priceProducts), //round 2 decimals
		"priceTotal":    fmt.Sprintf("%.2f", priceTotal),    //round 2 decimals
	})

	fmt.Println(order)
	c.HTML(http.StatusOK, "orderpreview.html", data)
}

func (rc *cartItemController) OrderView(c *gin.Context) {
	orderToken := c.Param("token")

	order, err := repositories.Orders.GetByToken(orderToken)
	if err != nil {
		c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Order does not exist."})
		return
	}

	shipping, err := models.GetShippingMethod(order.Shipping)
	if err != nil {
		c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Could not get shipping method"})
		return
	}

	priceProducts, priceTotal, err := order.CalculatePrices()
	if err != nil {
		c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Could not calculate final prices"})
		return
	}

	fmt.Printf("Order: %v\n", order)
	fmt.Printf("PriceTotal: %v\n", priceTotal)
	fmt.Printf("Amount Items: %v\n", len(order.CartItems))

	for _, item := range order.CartItems {
		fmt.Printf("Cartitem: %v", item)
	}

	c.HTML(http.StatusOK, "order.html", CreateSessionData(c, gin.H{
		"error":         "",
		"success":       "",
		"order":         order,
		"shipping":      shipping,
		"priceProducts": fmt.Sprintf("%.2f", priceProducts), //round 2 decimals
		"priceTotal":    fmt.Sprintf("%.2f", priceTotal),    //round 2 decimals
	}))
}

func (rc *cartItemController) OrderHandler(c *gin.Context) {
	confirmation := c.PostForm("confirm-order")

	if confirmation == "" {
		c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Something went wrong, try again later"})
		return
	}

	if confirmation != "true" {
		c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Order was not confirmed."})
		return
	}

	sessionId := GetSessionId(c)
	order, err := repositories.Orders.GetBySession(sessionId)

	if err != nil {
		c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Something went wrong, try again later"})
		return
	}

	order.Status = models.AwaitingPayment

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

	for idx := range order.CartItems {
		order.CartItems[idx].SessionId = "0"
		repositories.CartItems.Update(order.CartItems[idx])
	}

	_, err = repositories.Orders.Update(order)

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

	//TODO: cartItemRepository delete all by session - otherwise items stay in cart after completing order..

	c.Redirect(http.StatusFound, fmt.Sprintf("/order/%s", order.Token))
}

func (rc *cartItemController) OrdersView(c *gin.Context) {
	orders, err := repositories.Orders.GetAll()

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

	c.HTML(http.StatusOK, "editorders.html", CreateSessionData(c, gin.H{
		"error":   "",
		"success": "",
		"orders":  orders,
	}))
}

func (rc *cartItemController) OrdersHandler(c *gin.Context) {
	token := c.Param("token")

	if token == "" {
		c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "EmptyToken"})
		return
	}

	action := c.PostForm("action")

	if action == "update" {
		order, err := repositories.Orders.GetByToken(token)

		if err != nil {
			c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "No order with given token found"})
			return
		}

		status := c.PostForm("order-status")

		//TODO validate status
		if status == "" {
			c.HTML(http.StatusBadRequest, "error.html", gin.H{"error": "Invalid Order Status"})
			return
		}

		order.Status = models.OrderStatus(status)

		_, err = repositories.Orders.Update(order)

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

	if action == "delete" {
		fmt.Println("Deleting Order ", token)
		err := repositories.Orders.DeleteByToken(token)

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

	c.Redirect(http.StatusFound, "/orders")
}
