package api

import (
	"encoding/hex"
	"fmt"
	"log"
	"net/http"
	"path/filepath"
	"strconv"

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

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

func declareFilesGlobalRoutes(router *gin.RouterGroup) {
	router.DELETE("/files/", clearFiles)

	// Remote
	router.GET("/remote/themes/:thid/exercices/:exid/files", sync.ApiGetRemoteExerciceFiles)
}

func declareFilesRoutes(router *gin.RouterGroup) {
	router.GET("/files", listFiles)
	router.POST("/files", createExerciceFile)

	apiFilesRoutes := router.Group("/files/:fileid")
	apiFilesRoutes.Use(FileHandler)
	apiFilesRoutes.GET("", showFile)
	apiFilesRoutes.PUT("", updateFile)
	apiFilesRoutes.DELETE("", deleteFile)

	apiFileDepsRoutes := apiFilesRoutes.Group("/dependancies/:depid")
	apiFileDepsRoutes.Use(FileDepHandler)
	apiFileDepsRoutes.DELETE("", deleteFileDep)

	// Check
	apiFilesRoutes.POST("/check", checkFile)
	apiFilesRoutes.POST("/gunzip", gunzipFile)
}

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

	var file *fic.EFile
	if exercice, exists := c.Get("exercice"); exists {
		file, err = exercice.(*fic.Exercice).GetFile(fileid)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "File not found"})
			return
		}
	} else {
		file, err = fic.GetFile(fileid)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "File not found"})
			return
		}
	}

	c.Set("file", file)

	c.Next()
}

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

	c.Set("file-depid", depid)

	c.Next()
}

type APIFile struct {
	*fic.EFile
	Depends []fic.Flag `json:"depends,omitempty"`
}

func genFileList(in []*fic.EFile, e error) (out []APIFile, err error) {
	if e != nil {
		return nil, e
	}

	for _, f := range in {
		g := APIFile{EFile: f}

		var deps []fic.Flag
		deps, err = f.GetDepends()
		if err != nil {
			return
		}

		for _, d := range deps {
			if k, ok := d.(*fic.FlagKey); ok {
				k, err = fic.GetFlagKey(k.Id)
				if err != nil {
					return
				}

				g.Depends = append(g.Depends, k)
			} else if m, ok := d.(*fic.MCQ); ok {
				m, err = fic.GetMCQ(m.Id)
				if err != nil {
					return
				}

				g.Depends = append(g.Depends, m)
			} else {
				err = fmt.Errorf("Unknown type %T to handle file dependancy", k)
				return
			}
		}

		out = append(out, g)
	}

	return
}

func listFiles(c *gin.Context) {
	var files []APIFile
	var err error

	if exercice, exists := c.Get("exercice"); exists {
		files, err = genFileList(exercice.(*fic.Exercice).GetFiles())
	} else {
		files, err = genFileList(fic.GetFiles())
	}
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
		return
	}

	c.JSON(http.StatusOK, files)
}

func clearFiles(c *gin.Context) {
	_, err := fic.ClearFiles()
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
		return
	}

	c.JSON(http.StatusOK, true)
}

func showFile(c *gin.Context) {
	c.JSON(http.StatusOK, c.MustGet("file").(*fic.EFile))
}

type uploadedFile struct {
	URI    string
	Digest string
}

func createExerciceFile(c *gin.Context) {
	exercice, exists := c.Get("exercice")
	if !exists {
		c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "File can only be added inside an exercice."})
		return
	}

	paramsFiles, err := sync.GetExerciceFilesParams(sync.GlobalImporter, exercice.(*fic.Exercice))
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
		return
	}

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

	ret, err := sync.ImportFile(sync.GlobalImporter, uf.URI,
		func(filePath string, origin string) (interface{}, error) {
			if digest, err := hex.DecodeString(uf.Digest); err != nil {
				return nil, err
			} else {
				published := true
				disclaimer := ""

				if f, exists := paramsFiles[filepath.Base(filePath)]; exists {
					published = !f.Hidden

					if disclaimer, err = sync.ProcessMarkdown(sync.GlobalImporter, f.Disclaimer, exercice.(*fic.Exercice).Path); err != nil {
						return nil, fmt.Errorf("error during markdown formating of disclaimer: %w", err)
					}
				}

				return exercice.(*fic.Exercice).ImportFile(filePath, origin, digest, nil, disclaimer, published)
			}
		})
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
		return
	}

	c.JSON(http.StatusOK, ret)
}

func updateFile(c *gin.Context) {
	file := c.MustGet("file").(*fic.EFile)

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

	uf.Id = file.Id

	if _, err := uf.Update(); err != nil {
		log.Println("Unable to updateFile:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update file."})
		return
	}

	c.JSON(http.StatusOK, uf)
}

func deleteFile(c *gin.Context) {
	file := c.MustGet("file").(*fic.EFile)

	_, err := file.Delete()
	if err != nil {
		log.Println("Unable to updateFile:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update file."})
		return
	}

	c.JSON(http.StatusOK, true)
}

func deleteFileDep(c *gin.Context) {
	file := c.MustGet("file").(*fic.EFile)
	depid := c.MustGet("file-depid").(int64)

	err := file.DeleteDepend(&fic.FlagKey{Id: int(depid)})
	if err != nil {
		log.Println("Unable to deleteFileDep:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete file dependency."})
		return
	}

	c.JSON(http.StatusOK, true)
}

func checkFile(c *gin.Context) {
	file := c.MustGet("file").(*fic.EFile)

	err := file.CheckFileOnDisk()
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
		return
	}

	c.JSON(http.StatusOK, true)
}

func gunzipFile(c *gin.Context) {
	file := c.MustGet("file").(*fic.EFile)

	err := file.GunzipFileOnDisk()
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
		return
	}

	c.JSON(http.StatusOK, true)
}
