// Package classification Jannex API Documentation.
//
// Schemes: https
// Host: janex
// BasePath: /v1
// Version: 0.0.1
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// swagger:meta
package main

import (
	"encoding/json"
	"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/logger"
	"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/socketserver"
	"os"
	"path/filepath"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	flogger "github.com/gofiber/fiber/v2/middleware/logger"
	"github.com/gofiber/websocket/v2"
	"github.com/rs/zerolog/log"
)

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

	createEnvConfigFileIfNotExists()

	config.LoadConfig()
	logger.InitLogger()

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

		copySwaggerFolder()
	}

	createServerDirectoriesIfNotExists()
	createLogLanguageFilesIfNotExist()

	utils.ValidatorInit()
	logger.InitLanguageLogMessages()
	grouptasks.InitLoadCategoryGroups()
	database.InitDatabase()
	systempermissions.CreateMasterRoleIfNotExist()
}

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(flogger.New(flogger.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")

			if len(sessionId) != 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("userId", user.Id)
				}
			}

			return c.Next()
		}

		return fiber.ErrUpgradeRequired
	})

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

	go grouptasks.StartUnlockLockedGroupTaskStepsTicker()

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

func createEnvConfigFileIfNotExists() {
	if os.Getenv("DOCKER") != "" {
		return
	}

	content := `DEBUG=false
COLORIZED_OUTPUT=true
HOST=127.0.0.1
PORT=8080

# 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/

# Log language configurations
LOG_LANGUAGE_GROUPTASKS=./grouptasks_lang_log_messages.json
LOG_LANGUAGE_SYSTEM=./system_lang_log_messages.json

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

	if _, err := os.Stat(".env"); os.IsNotExist(err) {
		err := os.WriteFile(".env", []byte(content), 0644)

		if err != nil {
			panic("Failed to create .env file, err: " + err.Error())
		}

		fmt.Println("Config .env was created. Please configure and start the server again.")
		os.Exit(1)
	}
}

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

	paths := []string{
		cfg.GroupTasksGroups,
		cfg.GroupTasksRunningTasks,
		cfg.LogsGroupTasks,
		cfg.LogsSystem,
		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 createLogLanguageFilesIfNotExist() {
	cfg := config.Cfg

	files := []string{cfg.LogLanguageGroupTasks, cfg.LogLanguageSystem}

	for _, filename := range files {
		_, err := os.Stat(filename)

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

			if err != nil {
				log.Error().Msgf("Failed to create log language file %s", err.Error())
				continue
			}

			defer file.Close()

			encoder := json.NewEncoder(file)
			encoder.SetIndent("", " ")

			data := []structs.LanguageLogMessages{{
				Id: 0,
				Languages: []structs.LanguageLogMessagesLanguage{
					{Lang: "en", Message: "Your message"},
				}}}

			if err := encoder.Encode(data); err != nil {
				log.Error().Msgf("Failed to write default language log messages into file %s", filename)
				continue
			}

			log.Info().Msgf("Create log language file %s", filename)
		}
	}
}

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
}
