// Package classification JNX Admin-Dashboard API Documentation.
//
// Schemes: https
// Host: jannex
// BasePath: /v1
// Version: 1.0.0
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// swagger:meta
package main

import (
	"fmt"
	"io"
	"jannex/admin-dashboard-backend/modules/config"
	"jannex/admin-dashboard-backend/modules/database"
	"jannex/admin-dashboard-backend/modules/grouptasks"
	"jannex/admin-dashboard-backend/modules/structs"
	"jannex/admin-dashboard-backend/modules/systempermissions"
	"jannex/admin-dashboard-backend/modules/utils"
	"jannex/admin-dashboard-backend/routers/router"
	"jannex/admin-dashboard-backend/routers/router/api/v1/productpipeline"
	"jannex/admin-dashboard-backend/socketserver"
	"os"
	"path/filepath"
	"time"

	"git.ex.umbach.dev/Alex/roese-utils/rsconfig"
	"git.ex.umbach.dev/Alex/roese-utils/rslogger"
	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/logger"
	"github.com/gofiber/websocket/v2"
	"github.com/rs/zerolog/log"

	adlogger "jannex/admin-dashboard-backend/modules/logger"
)

func init() {
	fmt.Println("Server is starting...")

	rsconfig.CreateEnvConfigFileIfNotExists(`DEBUG=false
COLORIZED_OUTPUT=true
HOST=127.0.0.1
PORT=8080

LOG_MANAGER_SERVER_URL=http://localhost:50110
TELEGRAM_BOT_MANAGER_ENABLED=true
TELEGRAM_BOT_MANAGER_SERVER_URL=http://localhost:50056

# Folder paths
FOLDER_GROUPTASKS_GROUPS=./groupTasks/groups/
FOLDER_GROUPTASKS_RUNNINGTASKS=./groupTasks/runningTasks/

FOLDER_LOGS_GROUPTASKS=./logs/grouptasks/
FOLDER_LOGS_SYSTEM=./logs/system/

FOLDER_PUBLIC_STATIC=./public/

# MariaDB
MARIADB_HOSTNAME=127.0.0.1
MARIADB_PORT=3306
MARIADB_USERNAME=db_user
MARIADB_PASSWORD=db_password
MARIADB_DATABASE_NAME=db_database_name

# invex storage system api
INVEX_API_BASE=api_url
INVEX_API_TOKEN=api_token`)

	config.LoadConfig()

	rslogger.InitLogger(config.Cfg.Debug, config.Cfg.ColorizedOutput, config.Cfg.LogManagerServerUrl)

	if os.Getenv("DOCKER") != "" {
		fmt.Println("Waiting for mariadb docker")
		// waiting for the start of mariadb docker
		time.Sleep(10 * time.Second)

		copySwaggerFolder()
	}

	createServerDirectoriesIfNotExists()
	createGlobalRequirementsTxtIfNotExists()

	utils.ValidatorInit()
	grouptasks.InitLoadCategoryGroups()
	database.InitDatabase()
	systempermissions.CreateMasterRoleIfNotExist()

	productpipeline.FetchGoogleSheets()
}

func copySwaggerFolder() {
	// copied by docker build to /app/swagger
	srcDir := "./swagger"
	destDir := config.Cfg.FolderPaths.PublicStatic + "swagger/"

	err := copyDir(srcDir, destDir)

	if err != nil {
		panic("Failed to copy swagger folder: " + err.Error())
	}

	log.Debug().Msgf("Copied swagger folder to %s", destDir)
}

func main() {
	app := fiber.New(fiber.Config{
		BodyLimit: 100 * 1024 * 1024,
	})

	app.Use(cors.New())

	if config.Cfg.Debug {
		app.Use(logger.New(logger.Config{
			Format: "${pid} ${locals:requestid} ${status} - ${latency} ${method} ${path}​\n",
		}))
	}

	router.SetupRoutes(app)

	app.Use("/ws", func(c *fiber.Ctx) error {
		// IsWebSocketUpgrade returns true if the client
		// requested upgrade to the WebSocket protocol.
		if websocket.IsWebSocketUpgrade(c) {
			sessionId := c.Query("auth")
			// needed for a user who uses multiple tabs in the browser
			// with the same session id because otherwise the last browser
			// tab would subscribe to the topic and the other tabs would
			// not receive any messages
			browserTabSession := c.Query("bts")

			if len(sessionId) != utils.LenHeaderXAuthorization ||
				len(browserTabSession) != utils.LenHeaderXAuthorization {
				return c.SendStatus(fiber.StatusUnauthorized)
			}

			// validate ws session
			var userSession structs.UserSession

			database.DB.First(&userSession, "id = ?", sessionId)

			if userSession.Id != "" {
				var user structs.User

				database.DB.First(&user, "id = ?", userSession.UserId)

				if user.Id != "" {
					c.Locals("sessionId", sessionId)
					c.Locals("browserTabSession", browserTabSession)
					c.Locals("userId", user.Id)
				}
			}

			return c.Next()
		}

		return fiber.ErrUpgradeRequired
	})

	go socketserver.RunHub()
	socketserver.WebSocketServer(app)

	go grouptasks.StartUnlockLockedGroupTaskStepsTicker()

	adlogger.AddSystemLog(rslogger.LogTypeInfo, "Server started")

	app.Listen(config.Cfg.Host + ":" + config.Cfg.Port)
}

// create global requirements.txt in cfg.PublicStatic groupTasks folder
func createGlobalRequirementsTxtIfNotExists() {
	cfg := config.Cfg.FolderPaths
	filePath := cfg.GroupTasksGroups + "requirements.txt"

	_, err := os.Stat(filePath)

	if os.IsNotExist(err) {
		file, err := os.Create(filePath)

		if err != nil {
			log.Error().Msgf("Failed to create global requirements.txt file: %s", err.Error())
			return
		}

		defer file.Close()

		_, err = file.WriteString("# HERE YOU CAN DEFINE GLOBAL REQUIREMENTS\n# WHICH WILL BE INSTALLED ON THE SAME WAY AS THE GROUP TASKS REQUIREMENTS,\n# BUT YOU DON'T HAVE TO DEFINE THEM IN EVERY GROUP TASK\n")

		if err != nil {
			log.Error().Msgf("Failed to write to requirements.txt file: %s", err.Error())
			return
		}

		log.Info().Msgf("Created global requirements.txt file")
	}
}

func createServerDirectoriesIfNotExists() {
	cfg := config.Cfg.FolderPaths

	paths := []string{
		cfg.GroupTasksGroups,
		cfg.GroupTasksRunningTasks,
		cfg.PublicStatic + "avatars/",
		cfg.PublicStatic + "grouptasks/",
		cfg.PublicStatic + "sounds/",
		cfg.PublicStatic + "equipmentdocumentation/"}

	for _, path := range paths {
		_, err := os.Stat(path)

		if os.IsNotExist(err) {
			if err := os.MkdirAll(path, os.ModePerm); err != nil {
				log.Error().Msgf("Failed to create server directory: %s", err.Error())
				continue
			}

			log.Info().Msgf("Created server directory: %s", path)
		}
	}
}

func copyDir(srcDir, destDir string) error {
	err := os.MkdirAll(destDir, 0755)
	if err != nil {
		return err
	}

	files, err := filepath.Glob(filepath.Join(srcDir, "*"))
	if err != nil {
		return err
	}

	for _, file := range files {
		destFile := filepath.Join(destDir, filepath.Base(file))
		if err := copyFile(file, destFile); err != nil {
			return err
		}
	}

	return nil
}

func copyFile(srcFile, destFile string) error {
	source, err := os.Open(srcFile)
	if err != nil {
		return err
	}
	defer source.Close()

	destination, err := os.Create(destFile)
	if err != nil {
		return err
	}
	defer destination.Close()

	_, err = io.Copy(destination, source)
	return err
}
