package api

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"path"
	"strconv"
	"time"

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

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

func declareClaimsRoutes(router *gin.RouterGroup) {
	// Tasks
	router.GET("/claims", getClaims)
	router.POST("/claims", newClaim)
	router.DELETE("/claims", clearClaims)

	apiClaimsRoutes := router.Group("/claims/:cid")
	apiClaimsRoutes.Use(ClaimHandler)
	apiClaimsRoutes.GET("", showClaim)
	apiClaimsRoutes.PUT("", updateClaim)
	apiClaimsRoutes.POST("", addClaimDescription)
	apiClaimsRoutes.DELETE("", deleteClaim)

	apiClaimsRoutes.GET("/last_update", getClaimLastUpdate)
	apiClaimsRoutes.PUT("/descriptions", updateClaimDescription)

	// Assignees
	router.GET("/claims-assignees", getAssignees)
	router.POST("/claims-assignees", newAssignee)

	apiClaimAssigneesRoutes := router.Group("/claims-assignees/:aid")
	apiClaimAssigneesRoutes.Use(ClaimAssigneeHandler)
	router.GET("/claims-assignees/:aid", showClaimAssignee)
	router.PUT("/claims-assignees/:aid", updateClaimAssignee)
	router.DELETE("/claims-assignees/:aid", deleteClaimAssignee)
}

func declareExerciceClaimsRoutes(router *gin.RouterGroup) {
	router.GET("/claims", getExerciceClaims)
}

func declareTeamClaimsRoutes(router *gin.RouterGroup) {
	router.GET("/api/teams/:tid/issue.json", func(c *gin.Context) {
		team := c.MustGet("team").(*fic.Team)

		issues, err := team.MyIssueFile()
		if err != nil {
			log.Printf("Unable to MyIssueFile(tid=%d): %s", team.Id, err.Error())
			c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to generate issues.json."})
			return
		}

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

	router.GET("/claims", getTeamClaims)
}

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

	claim, err := fic.GetClaim(cid)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Requested claim not found"})
		return
	}

	c.Set("claim", claim)

	c.Next()
}

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

	assignee, err := fic.GetAssignee(aid)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Requested claim-assignee not found"})
		return
	}

	c.Set("claim-assignee", assignee)

	c.Next()
}

func getClaims(c *gin.Context) {
	claims, err := fic.GetClaims()

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

	c.JSON(http.StatusOK, claims)
}

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

	claims, err := team.GetClaims()
	if err != nil {
		log.Printf("Unable to GetClaims(tid=%d): %s", team.Id, err.Error())
		c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve claim list."})
		return
	}

	c.JSON(http.StatusOK, claims)
}

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

	claims, err := exercice.GetClaims()
	if err != nil {
		log.Printf("Unable to GetClaims(eid=%d): %s", exercice.Id, err.Error())
		c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve claim list."})
		return
	}

	c.JSON(http.StatusOK, claims)
}

func getClaimLastUpdate(c *gin.Context) {
	claim := c.MustGet("claim").(*fic.Claim)

	v, err := claim.GetLastUpdate()
	if err != nil {
		log.Printf("Unable to GetLastUpdate: %s", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim last update retrieval."})
		return
	}

	c.JSON(http.StatusOK, v)
}

type ClaimExported struct {
	Id           int64                   `json:"id"`
	Subject      string                  `json:"subject"`
	IdTeam       *int64                  `json:"id_team"`
	Team         *fic.Team               `json:"team"`
	IdExercice   *int64                  `json:"id_exercice"`
	Exercice     *fic.Exercice           `json:"exercice"`
	IdAssignee   *int64                  `json:"id_assignee"`
	Assignee     *fic.ClaimAssignee      `json:"assignee"`
	Creation     time.Time               `json:"creation"`
	LastUpdate   time.Time               `json:"last_update"`
	State        string                  `json:"state"`
	Priority     string                  `json:"priority"`
	Descriptions []*fic.ClaimDescription `json:"descriptions"`
}

func showClaim(c *gin.Context) {
	claim := c.MustGet("claim").(*fic.Claim)

	var e ClaimExported
	var err error
	if e.Team, err = claim.GetTeam(); err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated team: %s", err.Error())})
		return
	}
	if e.Exercice, err = claim.GetExercice(); err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated exercice: %s", err.Error())})
		return
	}
	if e.Assignee, err = claim.GetAssignee(); err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated assignee: %s", err.Error())})
		return
	}
	if e.Descriptions, err = claim.GetDescriptions(); err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find claim's descriptions: %s", err.Error())})
		return
	}

	e.LastUpdate = e.Creation
	for _, d := range e.Descriptions {
		if d.Date.After(e.LastUpdate) {
			e.LastUpdate = d.Date
		}
	}

	e.Id = claim.Id
	e.IdAssignee = claim.IdAssignee
	e.IdTeam = claim.IdTeam
	e.IdExercice = claim.IdExercice
	e.Subject = claim.Subject
	e.Creation = claim.Creation
	e.State = claim.State
	e.Priority = claim.Priority

	c.JSON(http.StatusOK, e)
}

type ClaimUploaded struct {
	fic.Claim
	Whoami *int64 `json:"whoami"`
}

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

	if uc.Subject == "" {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Claim's subject cannot be empty."})
		return
	}

	var t *fic.Team
	if uc.IdTeam != nil {
		if team, err := fic.GetTeam(*uc.IdTeam); err != nil {
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated team: %s", err.Error())})
			return
		} else {
			t = team
		}
	} else {
		t = nil
	}

	var e *fic.Exercice
	if uc.IdExercice != nil {
		if exercice, err := fic.GetExercice(*uc.IdExercice); err != nil {
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated exercice: %s", err.Error())})
			return
		} else {
			e = exercice
		}
	} else {
		e = nil
	}

	var a *fic.ClaimAssignee
	if uc.IdAssignee != nil {
		if assignee, err := fic.GetAssignee(*uc.IdAssignee); err != nil {
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated assignee: %s", err.Error())})
			return
		} else {
			a = assignee
		}
	} else {
		a = nil
	}

	if uc.Priority == "" {
		uc.Priority = "medium"
	}

	claim, err := fic.NewClaim(uc.Subject, t, e, a, uc.Priority)
	if err != nil {
		log.Println("Unable to newClaim:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register new claim"})
		return
	}

	c.JSON(http.StatusOK, claim)
}

func clearClaims(c *gin.Context) {
	nb, err := fic.ClearClaims()
	if err != nil {
		log.Printf("Unable to clearClaims: %s", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claims clearing."})
		return
	}

	c.JSON(http.StatusOK, nb)
}

func generateTeamIssuesFile(team fic.Team) error {
	if generation.GeneratorSocket == "" {
		if my, err := team.MyIssueFile(); err != nil {
			return fmt.Errorf("Unable to generate issue FILE (tid=%d): %w", team.Id, err)
		} else if j, err := json.Marshal(my); err != nil {
			return fmt.Errorf("Unable to encode issues' file JSON: %w", err)
		} else if err = ioutil.WriteFile(path.Join(TeamsDir, fmt.Sprintf("%d", team.Id), "issues.json"), j, 0644); err != nil {
			return fmt.Errorf("Unable to write issues' file: %w", err)
		}
	} else {
		resp, err := generation.PerformGeneration(fic.GenStruct{Type: fic.GenTeamIssues, TeamId: team.Id})
		if err != nil {
			return err
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			v, _ := ioutil.ReadAll(resp.Body)
			return fmt.Errorf("%s", string(v))
		}
	}
	return nil
}

func addClaimDescription(c *gin.Context) {
	claim := c.MustGet("claim").(*fic.Claim)

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

	assignee, err := fic.GetAssignee(ud.IdAssignee)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated assignee: %s", err.Error())})
		return
	}

	description, err := claim.AddDescription(ud.Content, assignee, ud.Publish)
	if err != nil {
		log.Println("Unable to addClaimDescription:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to add description"})
		return
	}

	if team, _ := claim.GetTeam(); team != nil {
		err = generateTeamIssuesFile(*team)
		if err != nil {
			log.Println("Unable to generateTeamIssuesFile after addClaimDescription:", err.Error())
		}
	}

	c.JSON(http.StatusOK, description)
}

func updateClaimDescription(c *gin.Context) {
	claim := c.MustGet("claim").(*fic.Claim)

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

	if _, err := ud.Update(); err != nil {
		log.Println("Unable to updateClaimDescription:", err.Error())
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during claim description updating."})
		return
	}
	if team, _ := claim.GetTeam(); team != nil {
		err = generateTeamIssuesFile(*team)
		if err != nil {
			log.Println("Unable to generateTeamIssuesFile:", err.Error())
		}
	}

	c.JSON(http.StatusOK, ud)
}

func updateClaim(c *gin.Context) {
	claim := c.MustGet("claim").(*fic.Claim)

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

	uc.Id = claim.Id

	_, err = uc.Update()
	if err != nil {
		log.Printf("Unable to updateClaim: %s", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim update."})
		return
	}

	if claim.State != uc.State {
		if uc.Whoami != nil {
			if assignee, err := fic.GetAssignee(*uc.Whoami); err == nil {
				claim.AddDescription(fmt.Sprintf("%s a changé l'état de la tâche vers %q (était %q).", assignee.Name, uc.State, claim.State), assignee, true)
			}
		}
	}

	if claim.IdAssignee != uc.IdAssignee {
		if uc.Whoami != nil {
			if whoami, err := fic.GetAssignee(*uc.Whoami); err == nil {
				if uc.IdAssignee != nil {
					if assignee, err := fic.GetAssignee(*uc.IdAssignee); err == nil {
						if assignee.Id != whoami.Id {
							claim.AddDescription(fmt.Sprintf("%s a assigné la tâche à %s.", whoami.Name, assignee.Name), whoami, false)
						} else {
							claim.AddDescription(fmt.Sprintf("%s s'est assigné la tâche.", assignee.Name), whoami, false)
						}
					}
				} else {
					claim.AddDescription(fmt.Sprintf("%s a retiré l'attribution de la tâche.", whoami.Name), whoami, false)
				}
			}
		}
	}

	if team, _ := claim.GetTeam(); team != nil {
		err = generateTeamIssuesFile(*team)
	}

	c.JSON(http.StatusOK, uc)
}

func deleteClaim(c *gin.Context) {
	claim := c.MustGet("claim").(*fic.Claim)

	if nb, err := claim.Delete(); err != nil {
		log.Println("Unable to deleteClaim:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim deletion."})
		return
	} else {
		c.JSON(http.StatusOK, nb)
	}
}

func getAssignees(c *gin.Context) {
	assignees, err := fic.GetAssignees()
	if err != nil {
		log.Println("Unable to getAssignees:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during assignees retrieval."})
		return
	}

	c.JSON(http.StatusOK, assignees)
}

func showClaimAssignee(c *gin.Context) {
	c.JSON(http.StatusOK, c.MustGet("claim-assignee").(*fic.ClaimAssignee))
}
func newAssignee(c *gin.Context) {
	var ua fic.ClaimAssignee
	err := c.ShouldBindJSON(&ua)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
		return
	}

	assignee, err := fic.NewClaimAssignee(ua.Name)
	if err != nil {
		log.Println("Unable to newAssignee:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during assignee creation."})
		return
	}

	c.JSON(http.StatusOK, assignee)
}

func updateClaimAssignee(c *gin.Context) {
	assignee := c.MustGet("claim-assignee").(*fic.ClaimAssignee)

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

	ua.Id = assignee.Id

	if _, err := ua.Update(); err != nil {
		log.Println("Unable to updateClaimAssignee:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim assignee update."})
		return
	}

	c.JSON(http.StatusOK, ua)
}

func deleteClaimAssignee(c *gin.Context) {
	assignee := c.MustGet("claim-assignee").(*fic.ClaimAssignee)

	if _, err := assignee.Delete(); err != nil {
		log.Println("Unable to deleteClaimAssignee:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during claim assignee deletion: %s", err.Error())})
		return
	}

	c.JSON(http.StatusOK, true)
}
