package api

import (
	"fmt"
	"log"
	"net/http"
	"net/url"
	"path"
	"strings"

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

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

func flatifySyncErrors(errs []error) (ret []string) {
	for _, err := range errs {
		ret = append(ret, err.Error())
	}
	return
}

func declareSyncRoutes(router *gin.RouterGroup) {
	apiSyncRoutes := router.Group("/sync")

	// Base sync checks if the local directory is in sync with remote one.
	apiSyncRoutes.POST("/base", func(c *gin.Context) {
		err := sync.GlobalImporter.Sync()
		if err != nil {
			c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": err.Error()})
		} else {
			c.JSON(http.StatusOK, true)
		}
	})

	// Speedy sync performs a recursive synchronization without importing files.
	apiSyncRoutes.POST("/speed", func(c *gin.Context) {
		st := sync.SpeedySyncDeep(sync.GlobalImporter)
		sync.EditDeepReport(&st, false)
		c.JSON(http.StatusOK, st)
	})

	// Deep sync: a fully recursive synchronization (can be limited by theme).
	apiSyncRoutes.GET("/deep", func(c *gin.Context) {
		if sync.DeepSyncProgress == 0 {
			c.AbortWithStatusJSON(http.StatusTooEarly, gin.H{"errmsg": "Pas de synchronisation en cours"})
			return
		}
		c.JSON(http.StatusOK, gin.H{"progress": sync.DeepSyncProgress})
	})
	apiSyncRoutes.POST("/deep", func(c *gin.Context) {
		c.JSON(http.StatusOK, sync.SyncDeep(sync.GlobalImporter))
	})

	apiSyncDeepRoutes := apiSyncRoutes.Group("/deep/:thid")
	apiSyncDeepRoutes.Use(ThemeHandler)
	apiSyncDeepRoutes.POST("", func(c *gin.Context) {
		theme := c.MustGet("theme").(*fic.Theme)

		exceptions := sync.LoadThemeException(sync.GlobalImporter, theme)

		var st []string
		for _, se := range sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250, exceptions) {
			st = append(st, se.Error())
		}
		sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]string{theme.Name: st}}, false)
		sync.DeepSyncProgress = 255
		c.JSON(http.StatusOK, st)
	})

	// Auto sync: to use with continuous deployment, in a development env
	apiSyncRoutes.POST("/auto/*p", autoSync)

	// Themes
	apiSyncRoutes.POST("/fixurlids", fixAllURLIds)

	apiSyncRoutes.POST("/themes", func(c *gin.Context) {
		_, errs := sync.SyncThemes(sync.GlobalImporter)
		c.JSON(http.StatusOK, flatifySyncErrors(errs))
	})

	apiSyncThemesRoutes := apiSyncRoutes.Group("/themes/:thid")
	apiSyncThemesRoutes.Use(ThemeHandler)
	apiSyncThemesRoutes.POST("/fixurlid", func(c *gin.Context) {
		theme := c.MustGet("theme").(*fic.Theme)
		if theme.FixURLId() {
			v, err := theme.Update()
			if err != nil {
				log.Println("Unable to UpdateTheme after fixurlid:", err.Error())
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when saving the theme."})
				return
			}

			c.JSON(http.StatusOK, v)
		} else {
			c.AbortWithStatusJSON(http.StatusOK, 0)
		}
	})

	// Exercices
	declareSyncExercicesRoutes(apiSyncRoutes)
	declareSyncExercicesRoutes(apiSyncThemesRoutes)

	// Videos sync imports resolution.mp4 from path stored in database.
	apiSyncRoutes.POST("/videos", func(c *gin.Context) {
		exercices, err := fic.GetExercices()
		if err != nil {
			log.Println("Unable to GetExercices:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve exercices list."})
			return
		}

		for _, e := range exercices {
			if len(e.VideoURI) == 0 || !strings.HasPrefix(e.VideoURI, "$RFILES$/") {
				continue
			}

			vpath, err := url.PathUnescape(strings.TrimPrefix(e.VideoURI, "$RFILES$/"))
			if err != nil {
				c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": fmt.Sprintf("Unable to perform URL unescape: %s", err.Error())})
				return
			}

			_, err = sync.ImportFile(sync.GlobalImporter, vpath, func(filePath, URI string) (interface{}, error) {
				e.VideoURI = path.Join("$FILES$", strings.TrimPrefix(filePath, fic.FilesDir))
				return e.Update()
			})
			if err != nil {
				c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": err.Error()})
				return
			}
		}

		c.JSON(http.StatusOK, true)
	})
}

func declareSyncExercicesRoutes(router *gin.RouterGroup) {
	router.POST("/exercices", func(c *gin.Context) {
		theme := c.MustGet("theme").(*fic.Theme)
		exceptions := sync.LoadThemeException(sync.GlobalImporter, theme)

		_, errs := sync.SyncExercices(sync.GlobalImporter, theme, exceptions)
		c.JSON(http.StatusOK, flatifySyncErrors(errs))
	})
	apiSyncExercicesRoutes := router.Group("/exercices/:eid")
	apiSyncExercicesRoutes.Use(ExerciceHandler)
	apiSyncExercicesRoutes.POST("", func(c *gin.Context) {
		theme := c.MustGet("theme").(*fic.Theme)
		exercice := c.MustGet("exercice").(*fic.Exercice)

		exceptions := sync.LoadExerciceException(sync.GlobalImporter, theme, exercice, nil)

		_, _, _, errs := sync.SyncExercice(sync.GlobalImporter, theme, exercice.Path, nil, exceptions)
		c.JSON(http.StatusOK, flatifySyncErrors(errs))
	})
	apiSyncExercicesRoutes.POST("/files", func(c *gin.Context) {
		exercice := c.MustGet("exercice").(*fic.Exercice)
		theme := c.MustGet("theme").(*fic.Theme)

		exceptions := sync.LoadExerciceException(sync.GlobalImporter, theme, exercice, nil)

		c.JSON(http.StatusOK, flatifySyncErrors(sync.SyncExerciceFiles(sync.GlobalImporter, exercice, exceptions)))
	})
	apiSyncExercicesRoutes.POST("/fixurlid", func(c *gin.Context) {
		exercice := c.MustGet("exercice").(*fic.Exercice)
		if exercice.FixURLId() {
			v, err := exercice.Update()
			if err != nil {
				log.Println("Unable to UpdateExercice after fixurlid:", err.Error())
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when saving the exercice."})
				return
			}

			c.JSON(http.StatusOK, v)
		} else {
			c.AbortWithStatusJSON(http.StatusOK, 0)
		}
	})
	apiSyncExercicesRoutes.POST("/hints", func(c *gin.Context) {
		exercice := c.MustGet("exercice").(*fic.Exercice)
		theme := c.MustGet("theme").(*fic.Theme)

		exceptions := sync.LoadExerciceException(sync.GlobalImporter, theme, exercice, nil)

		_, errs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice), exceptions)
		c.JSON(http.StatusOK, flatifySyncErrors(errs))
	})
	apiSyncExercicesRoutes.POST("/flags", func(c *gin.Context) {
		exercice := c.MustGet("exercice").(*fic.Exercice)
		theme := c.MustGet("theme").(*fic.Theme)

		exceptions := sync.LoadExerciceException(sync.GlobalImporter, theme, exercice, nil)
		_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice, exceptions)
		_, herrs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice), exceptions)
		c.JSON(http.StatusOK, flatifySyncErrors(append(errs, herrs...)))
	})
}

// autoSync tries to performs a smart synchronization, when in development environment.
// It'll sync most of modified things, and will delete out of sync data.
// Avoid using it in a production environment.
func autoSync(c *gin.Context) {
	p := strings.Split(strings.TrimPrefix(c.Params.ByName("p"), "/"), "/")

	if !IsProductionEnv {
		if err := sync.GlobalImporter.Sync(); err != nil {
			log.Println("Unable to sync.GI.Sync:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to perform the pull."})
			return
		}
	}

	themes, err := fic.GetThemes()
	if err != nil {
		log.Println("Unable to GetThemes:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve theme list."})
		return
	}

	// No argument, do a deep sync
	if len(p) == 0 {
		if !IsProductionEnv {
			for _, theme := range themes {
				theme.DeleteDeep()
			}
		}

		st := sync.SyncDeep(sync.GlobalImporter)
		c.JSON(http.StatusOK, st)
		return
	}

	var theTheme *fic.Theme

	// Find the given theme
	for _, theme := range themes {
		if theme.Path == p[0] {
			theTheme = theme
			break
		}
	}

	if theTheme == nil {
		// The theme doesn't exists locally, perhaps it has not been imported already?
		rThemes, err := sync.GetThemes(sync.GlobalImporter)
		if err == nil {
			for _, theme := range rThemes {
				if theme == p[0] {
					sync.SyncThemes(sync.GlobalImporter)

					themes, err := fic.GetThemes()
					if err == nil {
						for _, theme := range themes {
							if theme.Path == p[0] {
								theTheme = theme
								break
							}
						}
					}

					break
				}
			}
		}

		if theTheme == nil {
			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": fmt.Sprintf("Theme not found %q", p[0])})
			return
		}
	}

	if !IsProductionEnv {
		exercices, err := theTheme.GetExercices()
		if err == nil {
			for _, exercice := range exercices {
				if len(p) <= 1 || exercice.Path == path.Join(p[0], p[1]) {
					exercice.DeleteDeep()
				}
			}
		}
	}

	exceptions := sync.LoadThemeException(sync.GlobalImporter, theTheme)

	var st []string
	for _, se := range sync.SyncThemeDeep(sync.GlobalImporter, theTheme, 0, 250, exceptions) {
		st = append(st, se.Error())
	}
	sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]string{theTheme.Name: st}}, false)
	sync.DeepSyncProgress = 255

	resp, err := generation.FullGeneration()
	if err == nil {
		defer resp.Body.Close()
	}

	c.JSON(http.StatusOK, st)
}
