package equipment

import (
	"encoding/base64"
	"encoding/json"
	"jannex/admin-dashboard-backend/modules/config"
	"jannex/admin-dashboard-backend/modules/database"
	"jannex/admin-dashboard-backend/modules/logger"
	"jannex/admin-dashboard-backend/modules/structs"
	"jannex/admin-dashboard-backend/modules/utils"
	"os"
	"strconv"
	"strings"

	"github.com/gofiber/fiber/v2"
	"github.com/google/uuid"
	"github.com/rs/zerolog/log"
)

const Base = "https://inv.ex.umbach.dev"
const apiBase = Base + "/api"
const ApiToken = "1367f15d21935e4eb540f897946fb5cd98485c3f"

func InvexApiRequestClient(requestMethod string, url string) (statusCode int, body []byte, err error) {
	a := fiber.AcquireAgent()

	a.Add("Authorization", "Token "+ApiToken)

	req := a.Request()
	req.Header.SetMethod(requestMethod)
	req.SetRequestURI(url)

	if err := a.Parse(); err != nil {
		log.Error().Msgf("Failed to parse request, err: %s", err)
		return 0, nil, err
	}

	code, body, _ := a.Bytes()

	if code == 401 {
		log.Error().Msgf("invex not authorized, code: %d", code)

		logger.AddSystemLog(structs.LogMessage{
			Id:   29,
			Type: utils.LogTypeInfo,
			Messages: []structs.LogData{
				{Type: "error", Value: "invex not authorized"},
				{Type: "statusCode", Value: strconv.Itoa(code)},
			},
		})

		return code, nil, err
	}

	if code == 404 {
		log.Error().Msgf("Invex stock item not found, code: %d", code)

		logger.AddSystemLog(structs.LogMessage{
			Id:   29,
			Type: utils.LogTypeInfo,
			Messages: []structs.LogData{
				{Type: "error", Value: "invex stock item not found"},
				{Type: "statusCode", Value: strconv.Itoa(code)},
			},
		})
		return code, nil, err
	}

	return code, body, nil
}

type Notes struct {
	Image       string
	Description string
}

func getImageType(data string) string {
	switch data {
	case "data:image/png;base64":
		return "png"
	default:
		return "jpeg"
	}
}

func CreateEquipmentDocumentation(c *fiber.Ctx, body structs.CreateEquipmentDocumentationRequest) error {
	var bodyNotes []map[string]string

	err := json.Unmarshal(body.Notes, &bodyNotes)

	if err != nil {
		log.Error().Msgf("Failed to unmarshal json, err: %s", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	userId := c.Locals("userId").(string)

	newEquipmentDocumentation := structs.EquipmentDocumentation{
		Id:              uuid.New().String(),
		StockItemId:     body.StockItemId,
		Type:            body.Type,
		Title:           body.Title,
		CreatedByUserId: userId,
	}

	if err := os.Mkdir(config.Cfg.FolderPaths.PublicStatic+"/equipmentdocumentation/"+newEquipmentDocumentation.Id, os.ModePerm); err != nil {
		log.Error().Msgf("Failed to create folder, err: %s", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	var notes []Notes

	// loop throught bodyNotes and save image to static folder under random name
	for _, bodyNote := range bodyNotes {
		var newFileName string

		if bodyNote["Image"] != "" {
			// image is sent as base64 string
			// decode it and save it to the static folder

			// encoded base64 image structure is: data:image/jpeg;base64,/9j/4AA...
			splittedImageData := strings.Split(bodyNote["Image"], ",")

			decodedImage, err := base64.StdEncoding.DecodeString(splittedImageData[1])

			if err != nil {
				log.Error().Msgf("Failed to decode base64 string, err: %s", err)
				return c.SendStatus(fiber.StatusInternalServerError)

			}

			newFileName = uuid.New().String() + "." + getImageType(splittedImageData[0])

			err = os.WriteFile(config.Cfg.FolderPaths.PublicStatic+"/equipmentdocumentation/"+newEquipmentDocumentation.Id+"/"+newFileName, decodedImage, 0644)

			if err != nil {
				log.Error().Msgf("Failed to save image, err: %s", err)
				return c.SendStatus(fiber.StatusInternalServerError)
			}
		}

		notes = append(notes, Notes{
			Image:       newFileName,
			Description: bodyNote["Description"],
		})
	}

	marshaledNotes, err := json.Marshal(notes)

	if err != nil {
		log.Error().Msgf("Failed to marshal notes, err: %s", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	newEquipmentDocumentation.Notes = string(marshaledNotes)

	database.DB.Create(&newEquipmentDocumentation)

	logger.AddSystemLog(structs.LogMessage{
		Id:   27,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: userId},
			{Type: "equipmentDocumentationId", Value: newEquipmentDocumentation.Id},
			{Type: "stockItemId", Value: newEquipmentDocumentation.StockItemId},
			{Type: "type", Value: strconv.Itoa(int(body.Type))},
			{Type: "title", Value: body.Title},
			{Type: "description", Value: string(marshaledNotes)},
		},
	})

	return c.JSON(fiber.Map{"message": "ok"})
}

func GetEquipmentDocumentations(stockItemId string, c *fiber.Ctx) error {
	var documentations []structs.EquipmentDocumentation

	database.DB.Where("stock_item_id = ?", stockItemId).Find(&documentations)

	var err error
	statusCode := 200

	if len(documentations) == 0 {
		// there are no documentations for this equipment on the our database
		// so there will be checked on invex if the stock item exists
		statusCode, _, err = InvexApiRequestClient(fiber.MethodGet, apiBase+"/stock/"+stockItemId+"/")

		if err != nil {
			log.Error().Msgf("Invex api request error: %s", err)
			return c.SendStatus(fiber.StatusInternalServerError)
		}
	}

	logger.AddSystemLog(structs.LogMessage{
		Id:   30,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: c.Locals("userId").(string)},
			{Type: "stockItemId", Value: stockItemId},
			{Type: "statusCode", Value: strconv.Itoa(statusCode)},
		},
	})

	return c.JSON(structs.EquipmentDocumentationResponse{
		Status:         statusCode,
		Documentations: documentations})
}

func GetEquipmentDocumentation(stockItemId string, documentationId string, c *fiber.Ctx) error {
	var documentation structs.EquipmentDocumentation

	database.DB.Where("id = ? AND stock_item_id = ?", documentationId, stockItemId).Find(&documentation)

	return c.JSON(documentation)
}

func EditEquipmentDocumentation(c *fiber.Ctx, body structs.EditEquipmentDocumentationRequest) error {
	var bodyNotes []map[string]string

	err := json.Unmarshal(body.Notes, &bodyNotes)

	if err != nil {
		log.Error().Msgf("Failed to unmarshal json, err: %s", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	var notes []Notes

	// loop throught bodyNotes and save image to static folder under random name
	for _, bodyNote := range bodyNotes {
		newFileName := bodyNote["Image"]

		if bodyNote["Image"] != "" {
			// check if image starts with base64 string
			if strings.HasPrefix(bodyNote["Image"], "data:") {
				// image is sent as base64 string
				// decode it and save it to the static folder

				// encoded base64 image structure is: data:image/jpeg;base64,/9j/4AA...
				splittedImageData := strings.Split(bodyNote["Image"], ",")

				decodedImage, err := base64.StdEncoding.DecodeString(splittedImageData[1])

				if err != nil {
					log.Error().Msgf("Failed to decode base64 string, err: %s", err)
					return c.SendStatus(fiber.StatusInternalServerError)

				}

				newFileName = uuid.New().String() + "." + getImageType(splittedImageData[0])

				err = os.WriteFile(config.Cfg.FolderPaths.PublicStatic+"/equipmentdocumentation/"+body.DocumentationId+"/"+newFileName, decodedImage, 0644)

				if err != nil {
					log.Error().Msgf("Failed to save image, err: %s", err)
					return c.SendStatus(fiber.StatusInternalServerError)
				}
			}
		}

		notes = append(notes, Notes{
			Image:       newFileName,
			Description: bodyNote["Description"],
		})
	}

	// read all images on the folder and delete images that are not longer used in notes
	files, err := os.ReadDir(config.Cfg.FolderPaths.PublicStatic + "/equipmentdocumentation/" + body.DocumentationId)

	if err != nil {
		log.Error().Msgf("Failed to read directory, err: %s", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	for _, file := range files {
		if file.IsDir() {
			continue
		}

		if !isInList(file.Name(), notes) {
			err := os.Remove(config.Cfg.FolderPaths.PublicStatic + "/equipmentdocumentation/" + body.DocumentationId + "/" + file.Name())

			if err != nil {
				log.Error().Msgf("Failed to remove file, err: %s", err)
				return c.SendStatus(fiber.StatusInternalServerError)
			}
		}
	}

	// marshal notes to json
	marshaledNotes, err := json.Marshal(notes)

	if err != nil {
		log.Error().Msgf("Failed to marshal notes, err: %s", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	database.DB.Model(&structs.EquipmentDocumentation{}).Where("id = ?", body.DocumentationId).Updates(structs.EquipmentDocumentation{
		Type:  body.Type,
		Title: body.Title,
		Notes: string(marshaledNotes),
	})

	logger.AddSystemLog(structs.LogMessage{
		Id:   28,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: c.Locals("userId").(string)},
			{Type: "equipmentDocumentationId", Value: body.DocumentationId},
			{Type: "title", Value: body.Title},
			{Type: "type", Value: strconv.Itoa(int(body.Type))},
			{Type: "description", Value: string(marshaledNotes)},
		},
	})

	return c.JSON(fiber.Map{"message": "ok"})
}

func isInList(fileName string, notes []Notes) bool {
	for _, note := range notes {
		if note.Image == fileName {
			return true
		}
	}

	return false
}

// fetching the thumbnail from the invex server and sending it back to the client
func GetEquipmentInvexThumbnail(c *fiber.Ctx, stockItemId string) error {
	// first request to /api/stock/:stockItemId/ to get the thumbnail url
	_, body, err := InvexApiRequestClient(fiber.MethodGet, apiBase+"/stock/"+stockItemId+"/")

	if err != nil {
		log.Error().Msgf("Invex api request error: %s", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	// parse body as json
	var data map[string]interface{}

	if err := json.Unmarshal(body, &data); err != nil {
		log.Error().Msgf("Failed to unmarshal json, err: %s", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	partDetail := data["part_detail"].(map[string]interface{})
	thumbnail := partDetail["thumbnail"].(string)

	// second request to /media/part_images/:thumbnail to get the thumbnail image
	_, body, err = InvexApiRequestClient(fiber.MethodGet, Base+"/"+thumbnail)

	if err != nil {
		log.Error().Msgf("Invex api request error: %s", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	c.Set("Content-Type", "image/png")

	return c.Send(body)
}
