package api

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

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

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

func declareQARoutes(router *gin.RouterGroup) {
	exercicesRoutes := router.Group("/qa")
	exercicesRoutes.GET("", getExerciceQA)
	exercicesRoutes.POST("", createExerciceQA)

	exercicesRoutes.GET("/export", exportQA)
	exercicesRoutes.GET("/export.json", exportQAJSON)

	qaRoutes := exercicesRoutes.Group("/:qid")
	qaRoutes.Use(qaHandler)
	qaRoutes.PUT("", updateExerciceQA)
	qaRoutes.DELETE("", deleteExerciceQA)
	qaRoutes.GET("comments", getQAComments)
	qaRoutes.POST("comments", createQAComment)

	declareGitlabExportRoutes(qaRoutes)

	commentsRoutes := qaRoutes.Group("comments/:cid")
	commentsRoutes.Use(qaCommentHandler)
	commentsRoutes.DELETE("", deleteQAComment)
}

func qaHandler(c *gin.Context) {
	var qa *fic.QAQuery

	qid, err := strconv.ParseInt(string(c.Param("qid")), 10, 64)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad QA identifier."})
		return
	}

	if exercice, ok := c.Get("exercice"); ok {
		qa, err = exercice.(*fic.Exercice).GetQAQuery(qid)
	} else {
		qa, err = fic.GetQAQuery(qid)
	}

	if err != nil {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "QA entry not found."})
		return
	}

	c.Set("qa", qa)

	c.Next()
}

func qaCommentHandler(c *gin.Context) {
	qa := c.MustGet("qa").(*fic.QAQuery)
	var comment *fic.QAComment
	if cid, err := strconv.ParseInt(string(c.Param("cid")), 10, 64); err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad comment identifier."})
		return
	} else if comment, err = qa.GetComment(cid); err != nil {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Comment entry not found."})
		return
	}

	c.Set("comment", comment)

	c.Next()
}

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

func getExerciceQA(c *gin.Context) {
	exercice := c.MustGet("exercice").(*fic.Exercice)

	qas, err := exercice.GetQAQueries()
	if err != nil {
		log.Println("Unable to GetQAQueries: ", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA entries: %s", err.Error())})
		return
	}

	forgelink, err := GitLab_getExerciceId(exercice)
	if err != nil {
		log.Println("Unable to make request to admin:", err.Error())
		c.JSON(http.StatusOK, qas)
		return
	}

	var res []*QAQuery
	for _, qa := range qas {
		if qa.Exported != nil {
			res = append(res, &QAQuery{
				qa,
				gitlabBaseURL + "/" + forgelink + fmt.Sprintf("/-/issues/%d", *qa.Exported),
			})
		} else {
			res = append(res, &QAQuery{
				qa,
				"",
			})
		}
	}

	c.JSON(http.StatusOK, res)
}

func exportQA(c *gin.Context) {
	var report string

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

	for _, th := range themes {
		report += fmt.Sprintf("# %s  {#%s}\n\n", th.Name, th.URLId)

		exercices, err := th.GetExercices()
		if err != nil {
			log.Println("Unable to GetExercices: ", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices for theme #%d: %s", th.Id, err.Error())})
			return
		}

		for _, exercice := range exercices {
			report += fmt.Sprintf("## %s  {#%s}\n\n", exercice.Title, exercice.URLId)

			qa, err := exercice.GetQAQueries()
			if err != nil {
				log.Println("Unable to GetQAQueries: ", err.Error())
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA entries: %s", err.Error())})
				return
			}

			for _, q := range qa {
				emoji := "❓"

				if q.Closed != nil {
					emoji = "👌"
				} else if q.Solved != nil {
					emoji = "✅"
				}

				report += fmt.Sprintf("### %s [%s] %s\n\nOuvert par %s le %s\n\n", emoji, q.State, q.Subject, q.User, q.Creation)

				comments, err := q.GetComments()
				if err != nil {
					log.Println("Unable to GetQAComments: ", err.Error())
					c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA comments: %s", err.Error())})
					return
				}

				for _, c := range comments {
					report += fmt.Sprintf("Le %s, %s :\n%s\n\n", c.Date, c.User, c.Content)
				}
			}
		}
	}

	c.JSON(http.StatusOK, report)
}

type ExportTheme struct {
	Theme     *fic.Theme       `json:"theme"`
	Exercices []ExportExercice `json:"exercices"`
}

type ExportExercice struct {
	Exercice *fic.Exercice  `json:"exercice"`
	Reports  []ExportReport `json:"reports"`
}

type ExportReport struct {
	Report   *fic.QAQuery     `json:"report"`
	Comments []*fic.QAComment `json:"comments"`
}

func exportQAJSON(c *gin.Context) {
	report := map[string]ExportTheme{}

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

	for _, th := range themes {
		export_theme := ExportTheme{
			Theme: th,
		}

		exercices, err := th.GetExercices()
		if err != nil {
			log.Println("Unable to GetExercices: ", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices for theme #%d: %s", th.Id, err.Error())})
			return
		}

		for _, exercice := range exercices {
			export_exercice := ExportExercice{
				Exercice: exercice,
			}

			qa, err := exercice.GetQAQueries()
			if err != nil {
				log.Println("Unable to GetQAQueries: ", err.Error())
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA entries: %s", err.Error())})
				return
			}

			for _, q := range qa {
				export_report := ExportReport{
					Report: q,
				}

				comments, err := q.GetComments()
				if err != nil {
					log.Println("Unable to GetQAComments: ", err.Error())
					c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA comments: %s", err.Error())})
					return
				}

				for _, c := range comments {
					export_report.Comments = append(export_report.Comments, c)
				}

				export_exercice.Reports = append(export_exercice.Reports, export_report)
			}

			export_theme.Exercices = append(export_theme.Exercices, export_exercice)
		}

		report[th.Name] = export_theme
	}

	c.JSON(http.StatusOK, report)
}

type QAQueryAndComment struct {
	*fic.QAQuery
	Content string `json:"content"`
}

func createExerciceQA(c *gin.Context) {
	teamid := c.MustGet("LoggedTeam").(int64)
	ficteam := c.MustGet("LoggedUser").(string)

	// Create a new query
	var uq QAQueryAndComment
	if err := c.ShouldBindJSON(&uq); err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	if len(uq.State) == 0 {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "State not filled"})
		return
	}

	if len(uq.Subject) == 0 {
		if uq.State == "ok" {
			tmp := time.Now()
			uq.Subject = "RAS"
			uq.Solved = &tmp
		} else if uq.State == "timer" {
			tmp := time.Now()
			uq.Subject = fmt.Sprintf("Temps passé équipe #%d (%s)", teamid, ficteam)
			uq.Solved = &tmp
		} else {
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Subject not filled"})
			return
		}
	}

	exercice := c.MustGet("exercice").(*fic.Exercice)
	qa, err := exercice.NewQAQuery(uq.Subject, &teamid, ficteam, uq.State, uq.Solved)
	if err != nil {
		log.Println("Unable to NewQAQuery: ", err.Error())
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unable to create the new QA query. Please retry."})
		return
	}

	if len(uq.Content) > 0 {
		_, err = qa.AddComment(uq.Content, &teamid, ficteam)

		if err != nil {
			log.Println("Unable to AddComment: ", err.Error())
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "QA entry added successfully, but unable to create the associated comment. Please retry."})
			return
		}
	}

	c.JSON(http.StatusOK, qa)
}

func updateExerciceQA(c *gin.Context) {
	query := c.MustGet("qa").(*fic.QAQuery)

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

	uq.Id = query.Id

	if uq.User != query.User && (uq.IdExercice != query.IdExercice || uq.IdTeam != query.IdTeam || uq.User != query.User || uq.Creation != query.Creation || uq.State != query.State || uq.Subject != query.Subject || uq.Closed != query.Closed) {
		c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "You can only update your own entry."})
		return
	}

	_, err := uq.Update()
	if err != nil {
		log.Println("Unable to Update QAQuery:", err.Error())
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unable to update the query. Please try again."})
		return
	}

	c.JSON(http.StatusOK, uq)
}

func deleteExerciceQA(c *gin.Context) {
	query := c.MustGet("qa").(*fic.QAQuery)
	user := c.MustGet("LoggedUser").(string)

	if user != query.User {
		c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "You can only delete your own entry."})
		return
	}

	_, err := query.Delete()
	if err != nil {
		log.Println("Unable to Delete QAQuery:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete the query. Please try again."})
		return
	}

	c.JSON(http.StatusNoContent, nil)
}

func getQAComments(c *gin.Context) {
	query := c.MustGet("qa").(*fic.QAQuery)

	comments, err := query.GetComments()
	if err != nil {
		log.Println("Unable to GetComments: ", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list comments: %s", err.Error())})
		return
	}

	c.JSON(http.StatusOK, comments)
}

func createQAComment(c *gin.Context) {
	// Create a new query
	var uc *fic.QAComment
	if err := c.ShouldBindJSON(&uc); err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	if len(uc.Content) == 0 {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Empty comment."})
		return
	}

	teamid := c.MustGet("LoggedTeam").(int64)
	ficteam := c.MustGet("LoggedUser").(string)

	query := c.MustGet("qa").(*fic.QAQuery)
	comment, err := query.AddComment(uc.Content, &teamid, ficteam)
	if err != nil {
		log.Println("Unable to AddComment: ", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to add your comment. Please try again later."})
		return
	}

	c.JSON(http.StatusOK, comment)
}

func deleteQAComment(c *gin.Context) {
	ficteam := c.MustGet("LoggedUser").(string)
	comment := c.MustGet("comment").(*fic.QAComment)

	if ficteam != comment.User {
		c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "You can only delete your own comment."})
		return
	}

	_, err := comment.Delete()
	if err != nil {
		log.Println("Unable to Delete QAComment:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete the comment. Please try again."})
		return
	}

	c.JSON(http.StatusNoContent, nil)

}
