package api

import (
	"fmt"
	"log"
	"net/http"
	"path"
	"strconv"

	"srs.epita.fr/fic-server/admin/sync"
	"srs.epita.fr/fic-server/libfic"
	"srs.epita.fr/fic-server/settings"

	"github.com/gin-gonic/gin"
)

func declareThemesRoutes(router *gin.RouterGroup) {
	router.GET("/themes", listThemes)
	router.POST("/themes", createTheme)
	router.GET("/themes.json", exportThemes)
	router.GET("/session-forensic.yaml", func(c *gin.Context) {
		if s, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)); err != nil {
			log.Printf("Unable to ReadSettings: %s", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during settings reading."})
			return

		} else if challengeinfo, err := sync.GetFileContent(sync.GlobalImporter, "challenge.json"); err != nil {
			log.Println("Unable to retrieve challenge.json:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to retrive challenge.json: %s", err.Error())})
			return
		} else if ch, err := settings.ReadChallengeInfo(challengeinfo); err != nil {
			log.Printf("Unable to ReadChallengeInfo: %s", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during challenge info reading."})
			return
		} else if sf, err := fic.GenZQDSSessionFile(ch, s); err != nil {
			log.Printf("Unable to GenZQDSSessionFile: %s", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during session file generation."})
			return
		} else {
			c.JSON(http.StatusOK, sf)
		}
	})
	router.GET("/files-bindings", bindingFiles)

	apiThemesRoutes := router.Group("/themes/:thid")
	apiThemesRoutes.Use(ThemeHandler)
	apiThemesRoutes.GET("", showTheme)
	apiThemesRoutes.PUT("", updateTheme)
	apiThemesRoutes.DELETE("", deleteTheme)

	declareExercicesRoutes(apiThemesRoutes)

	// Remote
	router.GET("/remote/themes", sync.ApiListRemoteThemes)
	router.GET("/remote/themes/:thid", sync.ApiGetRemoteTheme)
	router.GET("/remote/themes/:thid/exercices", sync.ApiListRemoteExercices)
}

type Theme struct {
	*fic.Theme
	ForgeLink string `json:"forge_link,omitempty"`
}

func ThemeHandler(c *gin.Context) {
	thid, err := strconv.ParseInt(string(c.Params.ByName("thid")), 10, 64)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid theme identifier"})
		return
	}

	theme, err := fic.GetTheme(thid)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Theme not found"})
		return
	}

	c.Set("theme", theme)

	c.Next()
}

func fixAllURLIds(c *gin.Context) {
	nbFix := 0
	if themes, err := fic.GetThemes(); err == nil {
		for _, theme := range themes {
			if theme.FixURLId() {
				theme.Update()
				nbFix += 1
			}

			if exercices, err := theme.GetExercices(); err == nil {
				for _, exercice := range exercices {
					if exercice.FixURLId() {
						exercice.Update()
						nbFix += 1
					}
				}
			}
		}
	}

	c.JSON(http.StatusOK, nbFix)
}

func bindingFiles(c *gin.Context) {
	files, err := fic.GetFiles()
	if err != nil {
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}

	ret := ""
	for _, file := range files {
		ret += fmt.Sprintf("%s;%s\n", file.GetOrigin(), file.Path)
	}

	c.String(http.StatusOK, ret)
}

func getExercice(args []string) (*fic.Exercice, error) {
	if tid, err := strconv.ParseInt(string(args[0]), 10, 64); err != nil {
		return nil, err
	} else if theme, err := fic.GetTheme(tid); err != nil {
		return nil, err
	} else if eid, err := strconv.Atoi(string(args[1])); err != nil {
		return nil, err
	} else {
		return theme.GetExercice(eid)
	}
}

func listThemes(c *gin.Context) {
	themes, err := fic.GetThemes()
	if err != nil {
		log.Println("Unable to listThemes:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to list themes."})
		return
	}

	c.JSON(http.StatusOK, themes)
}

func exportThemes(c *gin.Context) {
	themes, err := fic.ExportThemes()
	if err != nil {
		log.Println("Unable to exportthemes:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to export themes."})
		return
	}

	c.JSON(http.StatusOK, themes)
}

func showTheme(c *gin.Context) {
	theme := c.MustGet("theme").(*fic.Theme)

	var forgelink string
	if fli, ok := sync.GlobalImporter.(sync.ForgeLinkedImporter); ok {
		if u, _ := fli.GetThemeLink(theme); u != nil {
			forgelink = u.String()
		}
	}

	c.JSON(http.StatusOK, Theme{theme, forgelink})
}

func createTheme(c *gin.Context) {
	var ut fic.Theme
	err := c.ShouldBindJSON(&ut)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	if len(ut.Name) == 0 {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Theme's name not filled"})
		return
	}

	th, err := fic.CreateTheme(&ut)
	if err != nil {
		log.Println("Unable to createTheme:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during theme creation."})
		return
	}

	c.JSON(http.StatusOK, th)
}

func updateTheme(c *gin.Context) {
	theme := c.MustGet("theme").(*fic.Theme)

	var ut fic.Theme
	err := c.ShouldBindJSON(&ut)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	ut.Id = theme.Id

	if len(ut.Name) == 0 {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Theme's name not filled"})
		return
	}

	if _, err := ut.Update(); err != nil {
		log.Println("Unable to updateTheme:", err.Error())
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during theme update."})
		return
	}

	c.JSON(http.StatusOK, ut)
}

func deleteTheme(c *gin.Context) {
	theme := c.MustGet("theme").(*fic.Theme)

	_, err := theme.Delete()
	if err != nil {
		log.Println("Unable to deleteTheme:", err.Error())
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during theme deletion."})
		return
	}

	c.JSON(http.StatusOK, true)
}

func getThemedExercicesStats(theme *fic.Theme, body []byte) (interface{}, error) {
	if exercices, err := theme.GetExercices(); err != nil {
		return nil, err
	} else {
		ret := []exerciceStats{}
		for _, e := range exercices {
			ret = append(ret, exerciceStats{
				IdExercice:  e.Id,
				TeamTries:   e.TriedTeamCount(),
				TotalTries:  e.TriedCount(),
				SolvedCount: e.SolvedCount(),
				FlagSolved:  e.FlagSolved(),
				MCQSolved:   e.MCQSolved(),
			})
		}
		return ret, nil
	}
}
