package productpipeline

import (
	"context"
	"errors"
	"fmt"
	"jannex/admin-dashboard-backend/modules/cache"
	"jannex/admin-dashboard-backend/modules/database"
	"jannex/admin-dashboard-backend/modules/logger"
	"jannex/admin-dashboard-backend/modules/structs"
	"strconv"

	"git.ex.umbach.dev/Alex/roese-utils/rslogger"
	"git.ex.umbach.dev/Alex/roese-utils/rsutils"
	"github.com/gofiber/fiber/v2"
	"google.golang.org/api/option"
	"google.golang.org/api/sheets/v4"
	"gorm.io/gorm"
)

var (
	serviceAccountFile = "./groupTasks/secrets/shinnex-424321-b88b144bc9ef.json"
	spreadsheetId      = "1gZkjykb55aLWumBxHxj_ZUq_ktjGK4FlJsH_9qg-ThU"

	// names of worksheets from google sheet
	googleWorksheetPipeline                = "Pipeline"
	googleWorksheetPipelineParameterValues = "Pipeline Parameterwerte"
)

func GetProducts(c *fiber.Ctx) error {
	// fetch from google sheets if cache time expired
	if cache.IsPipelineProductsCacheTimeExpired() {
		if err := FetchGoogleSheets(); err != nil {
			return c.SendStatus(fiber.StatusInternalServerError)
		}
	}

	return c.JSON(cache.GetPipelineProducts())
}

func getGoogleSheetsService() (*sheets.Service, error) {
	ctx := context.Background()

	srv, err := sheets.NewService(ctx, option.WithCredentialsFile(serviceAccountFile))

	if err != nil {
		logger.AddSystemLog(rslogger.LogTypeError, "Unable to retrieve Sheets client: %v", err)
		return nil, err
	}

	return srv, nil
}

func getStateByStatus(status string) uint8 {
	for _, productState := range cache.GetProductsStates() {
		if productState.Status == status {
			return productState.State
		}
	}

	return 0
}

func FetchGoogleSheets() error {
	srv, err := getGoogleSheetsService()

	if err != nil {
		logger.AddSystemLog(rslogger.LogTypeError, "Failed to fetch product states")
		return err
	}

	if err = fetchProductStates(srv); err != nil {
		logger.AddSystemLog(rslogger.LogTypeError, "FetchGoogleSheets failed on fetching product states, err: "+err.Error())
		return err
	}

	if err = fetchProducts(srv); err != nil {
		logger.AddSystemLog(rslogger.LogTypeError, "FetchGoogleSheets failed on fetching products, err: "+err.Error())
		return err
	}

	cache.SortFutureProducts()
	cache.UpdateProductsPipelineLastCacheUpdate()

	return nil
}

func fetchProducts(srv *sheets.Service) error {
	logger.AddSystemLog(rslogger.LogTypeInfo, "Fetching products from google sheets")

	readRange := googleWorksheetPipeline
	resp, err := srv.Spreadsheets.Values.Get(spreadsheetId, readRange).Do()

	if err != nil {
		logger.AddSystemLog(rslogger.LogTypeError, "Unable to retrieve data from sheet: %v", err)
		return err
	}

	// no products found
	if len(resp.Values) == 0 {
		logger.AddSystemLog(rslogger.LogTypeInfo, "No products found")
		return nil
	}

	// get products from database
	var databasePipelineProducts []structs.PipelineProduct

	database.DB.Find(&databasePipelineProducts)

	// check if there is a new product on google sheets which not exists in database

	var newProducts []structs.NewProduct
	var inWorkProducts []structs.InWorkProduct
	var futureProducts []structs.FutureProduct

	for _, row := range resp.Values[1:] { // skip first google worksheet row
		// skip to next if id, status or name is empty
		if row[0] == "" || row[1] == "" || row[2] == "" {
			continue
		}

		productId := fmt.Sprintf("%v", row[0])
		status := fmt.Sprintf("%v", row[1])
		name := fmt.Sprintf("%v", row[2])

		var productVariant string

		if row[3] != "" {
			productVariant = fmt.Sprintf("%v", row[3])
		}

		var productCharacteristics string

		if row[4] != "" {
			productCharacteristics = fmt.Sprintf("%v", row[4])
		}

		state := getStateByStatus(status)

		// add product to database if not exist
		if !isPipelineProductInDatabase(databasePipelineProducts, productId) {
			database.DB.Create(&structs.PipelineProduct{
				Id:    productId,
				Name:  name,
				Votes: 0,
			})
		}

		// cache product

		if state == 1 {
			futureProducts = append(futureProducts, structs.FutureProduct{
				Id:              productId,
				Name:            name,
				Votes:           getVotes(databasePipelineProducts, name), // votes from database
				Variant:         productVariant,
				Characteristics: productCharacteristics,
			})
		} else if state == 2 {
			inWorkProducts = append(inWorkProducts, structs.InWorkProduct{
				Id:              productId,
				Name:            name,
				Variant:         productVariant,
				Characteristics: productCharacteristics,
			})
		} else if state == 3 {
			var url string

			if row[8] != "" {
				url = fmt.Sprintf("%v", row[8])
			}

			newProducts = append(newProducts, structs.NewProduct{
				Id:              productId,
				Name:            name,
				Url:             url,
				Variant:         productVariant,
				Characteristics: productCharacteristics,
			})
		}
	}

	cache.SetPipelineProductsCache(structs.PipelineProductsCache{
		NewProducts:    newProducts,
		InWorkProducts: inWorkProducts,
		FutureProducts: futureProducts,
	})

	return nil
}

func getVotes(pipelineProducts []structs.PipelineProduct, name string) int {
	for _, pipelineProduct := range pipelineProducts {
		if pipelineProduct.Name == name {
			return pipelineProduct.Votes
		}
	}

	return 0
}

func fetchProductStates(srv *sheets.Service) error {
	logger.AddSystemLog(rslogger.LogTypeInfo, "Fetching product states from google sheets")

	readRange := googleWorksheetPipelineParameterValues
	resp, err := srv.Spreadsheets.Values.Get(spreadsheetId, readRange).Do()

	if err != nil {
		logger.AddSystemLog(rslogger.LogTypeError, "Unable to retrieve data from sheet: %v", err)
		return err
	}

	if len(resp.Values) == 0 {
		logger.AddSystemLog(rslogger.LogTypeInfo, "No product states found")
		return nil
	}

	var productStates []structs.ProductState

	for index, row := range resp.Values {
		// skip first google sheets row
		if index == 0 {
			continue
		}

		status := fmt.Sprintf("%v", row[0])
		state, err := strconv.ParseUint(fmt.Sprintf("%v", row[1]), 10, 64)

		if err != nil {
			return errors.New("Failed to parse uint8, err: " + err.Error())
		}

		productStates = append(productStates, structs.ProductState{
			Status: status,
			State:  uint8(state),
		})
	}

	cache.SetProductStates(productStates)

	return nil
}

func isPipelineProductInDatabase(pipelineProducts []structs.PipelineProduct, id string) bool {
	for _, pipelineProduct := range pipelineProducts {
		if pipelineProduct.Id == id {
			return true
		}
	}
	return false
}

func VoteProduct(c *fiber.Ctx) error {
	var query structs.VoteProductQuery

	if err := rsutils.QueryParserHelper(c, &query); err != nil {
		logger.AddSystemLog(rslogger.LogTypeError, "VoteProduct invalid query, err: %s", err.Error())
		return c.SendStatus(fiber.StatusBadRequest)
	}

	if query.T != "u" && query.T != "d" {
		return c.SendStatus(fiber.StatusBadRequest)
	}

	var body structs.VoteProductRequest

	if err := rsutils.BodyParserHelper(c, &body); err != nil {
		logger.AddSystemLog(rslogger.LogTypeError, "VoteProduct invalid body, err: %s", err.Error())
		return c.SendStatus(fiber.StatusBadRequest)
	}

	if !cache.IsIdInFutureProducts(body.Id) {
		logger.AddSystemLog(rslogger.LogTypeWarning, "VoteProduct invalid product id provided, id: %s", body.Id)
		return c.SendStatus(fiber.StatusBadRequest)
	}

	operator := "+"

	if query.T == "d" {
		operator = "-"
	}

	database.DB.Model(&structs.PipelineProduct{}).Where("id = ?", body.Id).Update("votes", gorm.Expr(fmt.Sprintf("votes %s ?", operator), 1))

	cache.VoteFutureProduct(body.Id, query.T == "u")
	cache.SortFutureProducts()

	logger.AddSystemLog(rslogger.LogTypeInfo, "Vote name: %v up vote: %v", body.Id, query.T)

	return c.SendStatus(fiber.StatusOK)
}
