package controllers

import (
	"context"
	"log"
	"net/http"
	"ordr-api/dto"
	"ordr-api/queries"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

func CreateItem(pool *pgxpool.Pool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		conn, conn_err := pool.Acquire(ctx)
		if conn_err != nil {
			log.Printf("CreateItem(): ERROR: Failed to connect... %s", conn_err.Error())
			ctx.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		defer conn.Release()

		item_name := ctx.Query("item_name")
		in_season := ctx.Query("in_season") == "1"
		if item_name == "" {
			ctx.JSON(http.StatusBadRequest, "CreateItem(): ERROR... Item Name not supplied")
		}

		_, exec_err := conn.Exec(context.Background(), queries.CREATE_ITEM, item_name, in_season)
		if exec_err != nil {
			ctx.JSON(http.StatusInternalServerError, "CreateItem(): ERROR... Failed to create item")
			log.Printf("CreateItem(): ERROR... Failed to create item... %s", exec_err.Error())
			return
		}

		var item_id int
		query_err := conn.QueryRow(context.Background(), "SELECT id FROM item WHERE item_name = $1", item_name).Scan(&item_id)
		if query_err != nil {
			ctx.JSON(http.StatusInternalServerError, "CreateItem(): ERROR... Failed to get nwely created item id")
			log.Printf("CreateItem(): ERROR... Failed to get newly created item... %s", query_err.Error())
			return
		}

		item_price := ctx.Query("item_price")

		if item_price == "" {
			ctx.JSON(http.StatusBadRequest, "CreateItem(): ERROR... item_price not supplied")
			return
		}

		item_price_float, convert_err := strconv.ParseFloat(item_price, 64)
		if convert_err != nil {
			ctx.JSON(http.StatusBadRequest, "CreateUser(): ERROR... Failed to create item price: item price invalid format")
			return
		}

		_, exec_price_err := conn.Exec(context.Background(), queries.CREATE_ITEM_PRICE, item_id, item_price)

		if exec_price_err != nil {
			ctx.JSON(http.StatusInternalServerError, "CreateItem(): ERROR... Failed to insert price...")
			log.Printf("CreateItem(): ERROR... Failed to insert price... %s", exec_price_err.Error())
			return
		}

		var item_price_object dto.ItemPriceResponse
		item_price_object.ItemId = item_id
		item_price_object.ItemName = item_name
		item_price_object.ItemPrice = item_price_float

		ctx.JSON(http.StatusOK, item_price_object)
	}
}

func SetItemPrice(pool *pgxpool.Pool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		conn, conn_err := pool.Acquire(ctx)
		if conn_err != nil {
			log.Printf("GetUserTable(): ERROR: Failed to connect... %s", conn_err.Error())
			ctx.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		defer conn.Release()

		item_id_string := ctx.Query("item_id")
		if item_id_string == "" {
			ctx.JSON(http.StatusBadRequest, "SetItemPrice(): ERROR... item id not provided")
			return
		}

		_, exec_date_err := conn.Exec(context.Background(), queries.SET_ITEM_PRICE_VALID_TO_DATE, item_id_string)
		if exec_date_err != nil {
			ctx.JSON(http.StatusInternalServerError, "SetItemPrice(): ERROR... Failed to set valid_to date")
			log.Printf("SetItemPrice(): ERROR... Failed to set valid_to date... %s", exec_date_err.Error())
			return
		}

		item_price_string := ctx.Query("item_price")
		if item_price_string == "" {
			ctx.JSON(http.StatusBadRequest, "SetItemPrice(): ERROR... Item Price Not Provided")
			return
		}

		item_price_float, item_price_conv_err := strconv.ParseFloat(item_price_string, 64)
		if item_price_conv_err != nil {
			ctx.JSON(http.StatusBadRequest, "SetItemPrice(): ERROR... item price not valid")
			return
		}

		_, exec_new_price_err := conn.Exec(context.Background(), queries.CREATE_ITEM_PRICE, item_id_string, item_price_float)

		if exec_new_price_err != nil {
			ctx.JSON(http.StatusInternalServerError, "SetItemPrice(): ERROR... failed to create item price")
			log.Printf("SetItemPrice(): ERROR... failed to create item price... %s", exec_new_price_err.Error())
			return
		}

		var item_price_object dto.ItemPriceResponse
		query_item_price_err := conn.QueryRow(context.Background(), queries.GET_CURRENT_ITEM_PRICE, item_id_string).Scan(&item_price_object.ItemId, &item_price_object.ItemName, &item_price_object.ItemPrice, &item_price_object.InSeason)
		if query_item_price_err != nil {
			ctx.JSON(http.StatusInternalServerError, "SetItemPrice(): ERROR... Failed to query item price")
			log.Printf("SetItemPrice(): ERROR... Failed to query item price... %s", query_item_price_err.Error())
			return
		}

		ctx.JSON(http.StatusOK, item_price_object)
	}
}

func GetCurrentItemPrice(pool *pgxpool.Pool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		conn, conn_err := pool.Acquire(ctx)
		if conn_err != nil {
			log.Printf("GetCurrentItemPrice(): ERROR: Failed to connect... %s", conn_err.Error())
			ctx.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		defer conn.Release()

		item_id := ctx.Query("item_id")
		if item_id == "" {
			ctx.JSON(http.StatusBadRequest, "GetCurrentItemPrice(): ERROR... item id not provided")
			return
		}

		var item_price dto.ItemPriceResponse

		query_err := conn.QueryRow(context.Background(), queries.GET_CURRENT_ITEM_PRICE, item_id).Scan(&item_price.ItemId, &item_price.ItemName, &item_price.ItemPrice, &item_price.InSeason)

		if query_err != nil {
			if query_err == pgx.ErrNoRows {
				ctx.JSON(http.StatusBadRequest, "GetCurrentItemPrice(): ERROR... no rows match id")
				return
			}
			ctx.JSON(http.StatusInternalServerError, "GetCurrentItemPrice(): ERROR... failed to query db")
			log.Printf("GetCurrentItemPrice(): ERROR... failed to query db... %s", query_err.Error())
			return
		}

		ctx.JSON(http.StatusOK, item_price)
	}
}

func AddItemToOrder(pool *pgxpool.Pool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		conn, conn_err := pool.Acquire(ctx)
		if conn_err != nil {
			log.Printf("AddItemToOrder(): ERROR: Failed to connect... %s", conn_err.Error())
			ctx.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		defer conn.Release()

		item_id := ctx.Query("item_id")
		order_id := ctx.Query("order_id")
		quantity := ctx.Query("quantity")
		created_at := ctx.Query("created_at")

		if item_id == "" || order_id == "" || quantity == "" {
			ctx.JSON(http.StatusBadRequest, "AddItemToOrder(): ERROR... item_id, order_id, or quantity missing")
			return
		}

		if created_at == "" {
			current_timestamp := time.Now()
			created_at = current_timestamp.Format(time.RFC3339)
		}

		int_item_id, item_id_parse_err := strconv.ParseInt(item_id, 10, 64)
		if item_id_parse_err != nil {
			ctx.JSON(http.StatusBadRequest, "AddItemToOrder: ERROR... item id is not an int")
			return
		}
		int_order_id, order_id_parse_err := strconv.ParseInt(order_id, 10, 64)
		if order_id_parse_err != nil {
			ctx.JSON(http.StatusBadRequest, "AddItemToOrder: ERROR... order id is not an int")
			return
		}
		int_quantity, quantity_parse_err := strconv.ParseInt(quantity, 10, 64)
		if quantity_parse_err != nil {
			ctx.JSON(http.StatusBadRequest, "AddItemToOrder: ERROR... quantity is not an int")
			return
		}

		_, time_parse_err := time.Parse(time.RFC3339, created_at)
		if time_parse_err != nil {
			ctx.JSON(http.StatusBadRequest, "AddItemToOrder(): ERROR... time is bad")
			return
		}

		_, exec_err := conn.Exec(context.Background(), queries.ADD_ITEM_TO_ORDER, int_item_id, int_order_id, int_quantity, created_at)
		if exec_err != nil {
			ctx.JSON(http.StatusInternalServerError, "AddItemToOrder(): ERROR... failed to insert rows")
			log.Printf("AddItemToOrder(): ERROR... Failed to insert rows... %s", exec_err.Error())
			return
		}

		_, update_order_filled_exec_err := conn.Exec(context.Background(), queries.SET_ORDER_FILLED, false, order_id)

		if update_order_filled_exec_err != nil {
			ctx.JSON(http.StatusInternalServerError, "AddItemToOrder(): ERROR... failed to set order state")
			log.Printf("AddItemToOrder(): ERROR... failed to set order state... %s", update_order_filled_exec_err.Error())
			return
		}

		var order_item_price dto.OrderItemPriceResponse

		price_data_row := conn.QueryRow(context.Background(), queries.GET_ORDER_ITEM_PRICE, item_id, order_id)

		price_scan_err := price_data_row.Scan(&order_item_price.ItemId, &order_item_price.OrderId, &order_item_price.ItemName, &order_item_price.Quantity, &order_item_price.Made, &order_item_price.CreatedAt, &order_item_price.TotalPrice, &order_item_price.UnitPrice)
		if price_scan_err != nil {
			ctx.JSON(http.StatusInternalServerError, "AddItemToOrder(): ERROR... Failed to scan price data")
			log.Printf("AddItemToOrder(): ERROR... Failed to scan price data... %s", price_scan_err.Error())
			return
		}

		ctx.JSON(http.StatusOK, order_item_price)
	}
}

func GetOrderItems(pool *pgxpool.Pool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		conn, conn_err := pool.Acquire(ctx)
		if conn_err != nil {
			log.Printf("GetOrderItems(): ERROR: Failed to connect... %s", conn_err.Error())
			ctx.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		defer conn.Release()

		order_id := ctx.Query("order_id")

		if order_id == "" {
			ctx.JSON(http.StatusBadRequest, "GetOrderItems(): ERROR: order id not supplied")
			return
		}

		_, parse_err := strconv.ParseInt(order_id, 10, 64)

		if parse_err != nil {
			ctx.JSON(http.StatusBadRequest, "GetOrderItem(): ERROR...  order id not an integer")
			return
		}

		rows, query_err := conn.Query(context.Background(), queries.GET_ORDER_ITEMS, order_id)
		if query_err != nil {
			ctx.JSON(http.StatusInternalServerError, "GetOrderItems(): ERROR... failed to query database.")
			log.Printf("GetOrderItems(): ERROR... Failed to query database... %s", query_err.Error())
			return
		}
		defer rows.Close()

		var items []dto.OrderItemPriceResponse

		for rows.Next() {
			var item dto.OrderItemPriceResponse
			scan_err := rows.Scan(&item.ItemId, &item.OrderId, &item.ItemName, &item.Quantity, &item.Made, &item.CreatedAt, &item.TotalPrice, &item.UnitPrice)
			if scan_err != nil {
				ctx.JSON(http.StatusInternalServerError, "GetOrderItems(): ERROR... Failed to scan data...")
				log.Printf("GetOrderItems(): ERROR... failed to scan data... %s", scan_err.Error())
				return
			}

			items = append(items, item)
		}

		ctx.JSON(http.StatusOK, items)
	}
}

func SetItemMade(pool *pgxpool.Pool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		conn, conn_err := pool.Acquire(ctx)
		if conn_err != nil {
			log.Printf("SetItemMade(): ERROR: Failed to connect... %s", conn_err.Error())
			ctx.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		defer conn.Release()

		var request dto.ItemOrderSetMadeRequest
		request_read_err := ctx.ShouldBindJSON(&request)
		if request_read_err != nil {
			ctx.JSON(http.StatusBadRequest, "SetItemMade(): ERROR... Error Request in bad format")
			return
		}

		_, exec_set_made_err := conn.Exec(context.Background(), queries.SET_MADE, request.Made, request.ItemId, request.OrderId)

		if exec_set_made_err != nil {
			ctx.JSON(http.StatusInternalServerError, "SetItemMade(): ERROR... Internal server error")
			log.Printf("SetItemMade(): ERROR... Failed to set item made... %s", exec_set_made_err.Error())
			return
		}

		_, exec_update_order_err := conn.Exec(context.Background(), queries.UPDATE_ORDER_FILLED, request.OrderId)
		if exec_update_order_err != nil {
			ctx.JSON(http.StatusInternalServerError, "SetItemMade(): ERROR... Failed to update order")
			log.Printf("SetItemMade(): ERROR... Failed to update order... %s", exec_update_order_err.Error())
			return
		}

		var order_filled dto.OrderFilledResponse
		scan_err := conn.QueryRow(context.Background(), queries.GET_ORDER_MADE_DETAILS, request.OrderId).Scan(&order_filled.OrderId, &order_filled.Filled)
		if scan_err != nil {
			if scan_err == pgx.ErrNoRows {
				ctx.JSON(http.StatusBadRequest, "SetItemMade(): Invalid data")
				return
			}
			ctx.JSON(http.StatusInternalServerError, "SetItemMade(): ERROR... Error querying result")
			log.Printf("SetItemMade(): ERROR... Error querying result... %s", scan_err.Error())
			return
		}

		ctx.JSON(http.StatusOK, order_filled)
	}
}
func SetItemQuantity(pool *pgxpool.Pool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		conn, conn_err := pool.Acquire(ctx)
		if conn_err != nil {
			log.Printf("SetItemMade(): ERROR: Failed to connect... %s", conn_err.Error())
			ctx.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		defer conn.Release()

		var request dto.ItemOrderSetQuantityRequest
		request_read_err := ctx.ShouldBindJSON(&request)
		if request_read_err != nil {
			ctx.JSON(http.StatusBadRequest, "SetItemQuantity(): ERROR... Error Request in bad format")
			return
		}

		_, exec_set_quantity_err := conn.Exec(context.Background(), queries.SET_QUANTITY, request.Quantity, request.ItemId, request.OrderId)

		if exec_set_quantity_err != nil {
			ctx.JSON(http.StatusInternalServerError, "SetItemMade(): ERROR... Internal server error")
			log.Printf("SetItemQuantity(): ERROR... Failed to set item quantity... %s", exec_set_quantity_err.Error())
			return
		}

		_, exec_update_order_err := conn.Exec(context.Background(), queries.UPDATE_ORDER_FILLED, request.OrderId)
		if exec_update_order_err != nil {
			ctx.JSON(http.StatusInternalServerError, "SetItemMade(): ERROR... Failed to update order")
			log.Printf("SetItemQuantity(): ERROR... Failed to update order... %s", exec_update_order_err.Error())
			return
		}

		var order_filled dto.OrderFilledResponse
		scan_err := conn.QueryRow(context.Background(), queries.GET_ORDER_MADE_DETAILS, request.OrderId).Scan(&order_filled.OrderId, &order_filled.Filled)
		if scan_err != nil {
			if scan_err == pgx.ErrNoRows {
				ctx.JSON(http.StatusBadRequest, "SetItemQuantity(): Invalid data")
				return
			}
			ctx.JSON(http.StatusInternalServerError, "SetItemQuantity(): ERROR... Error querying result")
			log.Printf("SetItemQuantity(): ERROR... Error querying result... %s", scan_err.Error())
			return
		}

		ctx.JSON(http.StatusOK, order_filled)
	}
}

func DeleteOrderItem(pool *pgxpool.Pool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		conn, conn_err := pool.Acquire(ctx)
		if conn_err != nil {
			log.Printf("DeleteOrderItem(): ERROR: Failed to connect... %s", conn_err.Error())
			ctx.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		defer conn.Release()

		item_id := ctx.Query("item_id")
		order_id := ctx.Query("order_id")

		if item_id == "" || order_id == "" {
			ctx.String(http.StatusBadRequest, "DeleteOrderItem(): Bad Request")
			return
		}

		item_id_int, item_parse_err := strconv.ParseInt(item_id, 10, 64)
		order_id_int, order_parse_err := strconv.ParseInt(order_id, 10, 64)

		if item_parse_err != nil || order_parse_err != nil {
			ctx.String(http.StatusBadRequest, "DeleteOrderItem(): Bad Request")
			return
		}

		_, exec_err := conn.Exec(context.Background(), queries.REMOVE_ITEM_FROM_ORDER, item_id_int, order_id_int)
		if exec_err != nil {
			ctx.JSON(http.StatusInternalServerError, "DeleteOrderItem(): ERROR.. Failed to delete item")
			log.Printf("DeleteOrderItem(): ERROR... Failed to delte item... %s", exec_err.Error())
			return
		}
	}
}

func DeleteItem(pool *pgxpool.Pool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		conn, conn_err := pool.Acquire(ctx)
		if conn_err != nil {
			log.Printf("DeleteItem(): ERROR: Failed to connect... %s", conn_err.Error())
			ctx.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		defer conn.Release()

		item_id := ctx.Query("item_id")
		item_id_int, parse_err := strconv.ParseInt(item_id, 10, 64)
		if parse_err != nil || item_id == "" {
			ctx.JSON(http.StatusBadRequest, "DeleteItem(): Invalid data entry")
			return
		}

		_, exec_err := conn.Exec(context.Background(), "DELETE FROM item WHERE id = $1", item_id_int)
		if exec_err != nil {
			ctx.JSON(http.StatusInternalServerError, "DeleteItem(): Failed to delete item")
			log.Printf("DeleteItem(): ERROR... Failed to delete item... %s", exec_err.Error())
			return
		}
	}
}

func GetItems(pool *pgxpool.Pool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		conn, conn_err := pool.Acquire(ctx)
		if conn_err != nil {
			log.Printf("DeleteItem(): ERROR: Failed to connect... %s", conn_err.Error())
			ctx.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		defer conn.Release()

		rows, query_err := conn.Query(context.Background(), queries.GET_CURRENT_ITEMS_PRICE)

		if query_err != nil {
			ctx.JSON(http.StatusInternalServerError, "INTERNAL SERVER ERROR")
			log.Printf("GetItems(): ERROR... Failed to query table... %s", query_err.Error())
			return
		}

		var items []dto.ItemPriceResponse

		for rows.Next() {
			var item dto.ItemPriceResponse
			scan_err := rows.Scan(&item.ItemId, &item.ItemName, &item.ItemPrice, &item.InSeason)

			if scan_err != nil {
				ctx.JSON(http.StatusInternalServerError, "INTERNAL SERVER ERROR")
				log.Printf("GetItems(): ERROR... failed to scan info...%s", scan_err.Error())
				return
			}

			items = append(items, item)
		}
		ctx.JSON(http.StatusOK, items)
	}
}

func GetItemHistory(pool *pgxpool.Pool) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		conn, conn_err := pool.Acquire(ctx)
		if conn_err != nil {
			log.Printf("DeleteItem(): ERROR: Failed to connect... %s", conn_err.Error())
			ctx.AbortWithStatus(http.StatusInternalServerError)
			return
		}
		defer conn.Release()

		item_id := ctx.Query("item_id")

		if item_id == "" {
			ctx.String(http.StatusBadRequest, "Bad Request")
			return
		}

		item_id_int, parse_err := strconv.ParseInt(item_id, 10, 64)

		if parse_err != nil {
			ctx.String(http.StatusBadRequest, "Bad Request")
			return
		}

		rows, query_err := conn.Query(ctx, queries.GET_ITEM_PRICE_HISTORY, item_id_int)
		if query_err != nil {
			ctx.String(http.StatusInternalServerError, "Internal Server Error")

			log.Printf("GetItemHistory(): ERROR... Failed to query table... %s", query_err.Error())
			return
		}

		var item_history_records []dto.ItemHistory
		for rows.Next() {
			var item_history_record dto.ItemHistory
			scan_err := rows.Scan(&item_history_record.ItemId, &item_history_record.ItemName, &item_history_record.ItemPrice, &item_history_record.ValidFrom, &item_history_record.ValidTo)
			if scan_err != nil {
				ctx.String(http.StatusInternalServerError, "Internal Server Error")

				log.Printf("GetItemHistory(): ERROR... Failed to query table... %s", scan_err.Error())
				return
			}
			item_history_records = append(item_history_records, item_history_record)
		}

		ctx.JSON(http.StatusOK, item_history_records)
	}
}
