package api

import (
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"strings"
	"time"

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

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

func declareTeamsRoutes(router *gin.RouterGroup) {
	router.GET("/teams.json", func(c *gin.Context) {
		teams, err := fic.ExportTeams(false)
		if err != nil {
			log.Println("Unable to ExportTeams:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during teams export."})
			return
		}

		c.JSON(http.StatusOK, teams)
	})
	router.GET("/teams-members.json", func(c *gin.Context) {
		teams, err := fic.ExportTeams(true)
		if err != nil {
			log.Println("Unable to ExportTeams:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during teams export."})
			return
		}

		c.JSON(http.StatusOK, teams)
	})
	router.GET("/teams-associations.json", allAssociations)
	router.GET("/teams-binding", bindingTeams)
	router.GET("/teams-nginx", nginxGenTeams)
	router.POST("/disableinactiveteams", disableInactiveTeams)
	router.POST("/enableallteams", enableAllTeams)
	router.GET("/teams-members-nginx", nginxGenMember)
	router.GET("/teams-tries.json", func(c *gin.Context) {
		tries, err := fic.GetTries(nil, nil)
		if err != nil {
			log.Println("Unable to GetTries:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieves tries."})
			return
		}

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

	router.GET("/teams", func(c *gin.Context) {
		teams, err := fic.GetTeams()
		if err != nil {
			log.Println("Unable to GetTeams:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during teams listing."})
			return
		}

		c.JSON(http.StatusOK, teams)
	})
	router.POST("/teams", createTeam)

	apiTeamsRoutes := router.Group("/teams/:tid")
	apiTeamsRoutes.Use(TeamHandler)
	apiTeamsRoutes.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, c.MustGet("team").(*fic.Team))
	})
	apiTeamsRoutes.PUT("/", updateTeam)
	apiTeamsRoutes.POST("/", addTeamMember)
	apiTeamsRoutes.DELETE("/", deleteTeam)
	apiTeamsRoutes.GET("/score-grid.json", func(c *gin.Context) {
		team := c.MustGet("team").(*fic.Team)

		sg, err := team.ScoreGrid()
		if err != nil {
			log.Printf("Unable to get ScoreGrid(tid=%d): %s", team.Id, err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during score grid calculation."})
			return
		}

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

	apiTeamsPublicRoutes := router.Group("/teams/:tid")
	apiTeamsPublicRoutes.Use(TeamPublicHandler)
	apiTeamsPublicRoutes.GET("/my.json", func(c *gin.Context) {
		tfile, err := fic.MyJSONTeam(c.MustGet("team").(*fic.Team), true)
		if err != nil {
			log.Println("Unable to get MyJSONTeam:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team JSON generation."})
			return
		}

		c.JSON(http.StatusOK, tfile)
	})
	apiTeamsPublicRoutes.GET("/wait.json", func(c *gin.Context) {
		tfile, err := fic.MyJSONTeam(c.MustGet("team").(*fic.Team), false)
		if err != nil {
			log.Println("Unable to get MyJSONTeam:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team JSON generation."})
			return
		}

		c.JSON(http.StatusOK, tfile)
	})
	apiTeamsPublicRoutes.GET("/stats.json", func(c *gin.Context) {
		var team *fic.Team
		if t, ok := c.Get("team"); ok && t != nil {
			team = t.(*fic.Team)
		}
		if team != nil {
			stats, err := team.GetStats()
			if err != nil {
				log.Println("Unable to get GetStats:", err.Error())
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during stats calculation."})
				return
			}

			c.JSON(http.StatusOK, stats)
		} else {
			stats, err := fic.GetTeamsStats(nil)
			if err != nil {
				log.Println("Unable to get GetTeamsStats:", err.Error())
				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during global stats calculation."})
				return
			}

			c.JSON(http.StatusOK, stats)
		}
	})
	apiTeamsRoutes.GET("/history.json", func(c *gin.Context) {
		team := c.MustGet("team").(*fic.Team)

		history, err := team.GetHistory()
		if err != nil {
			log.Println("Unable to get GetHistory:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history calculation."})
			return
		}

		c.JSON(http.StatusOK, history)
	})
	apiTeamsRoutes.DELETE("/history.json", delHistory)
	apiTeamsPublicRoutes.GET("/tries", func(c *gin.Context) {
		team := c.MustGet("team").(*fic.Team)

		tries, err := fic.GetTries(team, nil)
		if err != nil {
			log.Println("Unable to GetTries:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during tries calculation."})
			return
		}

		c.JSON(http.StatusOK, tries)
	})
	apiTeamsRoutes.GET("/members", func(c *gin.Context) {
		team := c.MustGet("team").(*fic.Team)

		members, err := team.GetMembers()
		if err != nil {
			log.Println("Unable to GetMembers:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during members retrieval."})
			return
		}

		c.JSON(http.StatusOK, members)
	})
	apiTeamsRoutes.POST("/members", addTeamMember)
	apiTeamsRoutes.PUT("/members", setTeamMember)

	declareTeamsPasswordRoutes(apiTeamsRoutes)
	declareTeamClaimsRoutes(apiTeamsRoutes)
	declareTeamCertificateRoutes(apiTeamsRoutes)
}

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

	team, err := fic.GetTeam(tid)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Team not found"})
		return
	}

	c.Set("team", team)

	c.Next()
}

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

	if tid != 0 {
		team, err := fic.GetTeam(tid)
		if err != nil {
			c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Team not found"})
			return
		}

		c.Set("team", team)
	} else {
		c.Set("team", nil)
	}

	c.Next()
}

func nginxGenTeams(c *gin.Context) {
	teams, err := fic.GetTeams()
	if err != nil {
		log.Println("Unable to GetTeams:", err.Error())
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}

	ret := ""
	for _, team := range teams {
		ret += fmt.Sprintf("        if ($remote_user = \"%s\") { set $team \"%d\"; }\n", strings.ToLower(team.Name), team.Id)
	}

	c.String(http.StatusOK, ret)
}

func nginxGenMember(c *gin.Context) {
	teams, err := fic.GetTeams()
	if err != nil {
		log.Println("Unable to GetTeams:", err.Error())
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}

	ret := ""
	for _, team := range teams {
		if members, err := team.GetMembers(); err == nil {
			for _, member := range members {
				ret += fmt.Sprintf("        if ($remote_user = \"%s\") { set $team \"%d\"; }\n", member.Nickname, team.Id)
			}
		} else {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}
	}

	c.String(http.StatusOK, ret)
}

func bindingTeams(c *gin.Context) {
	teams, err := fic.GetTeams()
	if err != nil {
		log.Println("Unable to GetTeams:", err.Error())
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}

	ret := ""
	for _, team := range teams {
		if members, err := team.GetMembers(); err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		} else {
			var mbs []string
			for _, member := range members {
				mbs = append(mbs, fmt.Sprintf("%s %s", member.Firstname, member.Lastname))
			}
			ret += fmt.Sprintf("%d;%s;%s\n", team.Id, team.Name, strings.Join(mbs, ";"))
		}
	}

	c.String(http.StatusOK, ret)
}

func allAssociations(c *gin.Context) {
	teams, err := fic.GetTeams()
	if err != nil {
		log.Println("Unable to GetTeams:", err.Error())
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}

	var ret []string

	for _, team := range teams {
		assocs, err := pki.GetTeamAssociations(TeamsDir, team.Id)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}

		for _, a := range assocs {
			ret = append(ret, a)
		}
	}

	c.JSON(http.StatusOK, ret)
}

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

	if ut.Color == 0 {
		ut.Color = fic.HSL{
			H: rand.Float64(),
			S: 1,
			L: 0.5,
		}.ToRGB()
	}

	team, err := fic.CreateTeam(strings.TrimSpace(ut.Name), ut.Color, ut.ExternalId)
	if err != nil {
		log.Println("Unable to CreateTeam:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team creation."})
		return
	}

	c.JSON(http.StatusOK, team)
}

func updateTeam(c *gin.Context) {
	team := c.MustGet("team").(*fic.Team)

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

	ut.Id = team.Id

	if ut.Password != nil && *ut.Password == "" {
		ut.Password = nil
	}

	_, err = ut.Update()
	if err != nil {
		log.Println("Unable to updateTeam:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team updating."})
		return
	}

	c.JSON(http.StatusOK, ut)
}

func disableInactiveTeams(c *gin.Context) {
	teams, err := fic.GetTeams()
	if err != nil {
		log.Println("Unable to GetTeams:", err.Error())
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}

	for _, team := range teams {
		var serials []uint64
		serials, err = pki.GetTeamSerials(TeamsDir, team.Id)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}

		var assocs []string
		assocs, err = pki.GetTeamAssociations(TeamsDir, team.Id)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}

		if len(serials) == 0 && len(assocs) == 0 {
			if team.Active {
				team.Active = false
				team.Update()
			}
		} else if !team.Active {
			team.Active = true
			team.Update()
		}
	}

	c.JSON(http.StatusOK, true)
}

func enableAllTeams(c *gin.Context) {
	teams, err := fic.GetTeams()
	if err != nil {
		log.Println("Unable to GetTeams:", err.Error())
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}

	for _, team := range teams {
		if !team.Active {
			team.Active = true
			team.Update()
		}
	}

	c.JSON(http.StatusOK, true)
}

func deleteTeam(c *gin.Context) {
	team := c.MustGet("team").(*fic.Team)

	assocs, err := pki.GetTeamAssociations(TeamsDir, team.Id)
	if err != nil {
		log.Printf("Unable to GetTeamAssociations(tid=%d): %s", team.Id, err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve team association."})
		return
	}

	for _, assoc := range assocs {
		err = pki.DeleteTeamAssociation(TeamsDir, assoc)
		if err != nil {
			log.Printf("Unable to DeleteTeamAssociation(assoc=%s): %s", assoc, err.Error())
			return
		}
	}

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

	c.JSON(http.StatusOK, true)
}

func addTeamMember(c *gin.Context) {
	team := c.MustGet("team").(*fic.Team)

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

	for _, member := range members {
		_, err := team.AddMember(strings.TrimSpace(member.Firstname), strings.TrimSpace(member.Lastname), strings.TrimSpace(member.Nickname), strings.TrimSpace(member.Company))
		if err != nil {
			log.Println("Unable to AddMember:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during member creation."})
			return
		}
	}

	mmbrs, err := team.GetMembers()
	if err != nil {
		log.Println("Unable to retrieve members list:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve members list."})
		return
	}

	c.JSON(http.StatusOK, mmbrs)
}

func setTeamMember(c *gin.Context) {
	team := c.MustGet("team").(*fic.Team)
	team.ClearMembers()
	addTeamMember(c)
}

type uploadedHistory struct {
	Kind        string
	Time        time.Time
	Primary     *int64
	Secondary   *int64
	Coefficient float32
}

func updateHistory(team *fic.Team, body []byte) (interface{}, error) {
	var uh uploadedHistory
	if err := json.Unmarshal(body, &uh); err != nil {
		return nil, err
	}

	var givenId int64
	if uh.Secondary != nil {
		givenId = *uh.Secondary
	} else if uh.Primary != nil {
		givenId = *uh.Primary
	}

	return team.UpdateHistoryCoeff(uh.Kind, uh.Time, givenId, uh.Coefficient)
}

func delHistory(c *gin.Context) {
	team := c.MustGet("team").(*fic.Team)

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

	_, err = team.DelHistoryItem(uh.Kind, uh.Time, uh.Primary, uh.Secondary)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to delete this history line: %s", err.Error())})
		return
	}

	c.JSON(http.StatusOK, true)
}
