package api

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"time"

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

var DashboardDir string

func declarePublicRoutes(router *gin.RouterGroup) {
	router.GET("/public/", listPublic)
	router.GET("/public/:sid", getPublic)
	router.DELETE("/public/:sid", deletePublic)
	router.PUT("/public/:sid", savePublic)
}

type FICPublicScene struct {
	Type   string                 `json:"type"`
	Params map[string]interface{} `json:"params"`
}

type FICPublicDisplay struct {
	Scenes          []FICPublicScene       `json:"scenes"`
	Side            []FICPublicScene       `json:"side"`
	CustomCountdown map[string]interface{} `json:"customCountdown"`
	HideEvents      bool                   `json:"hideEvents"`
	HideCountdown   bool                   `json:"hideCountdown"`
	HideCarousel    bool                   `json:"hideCarousel"`
	PropagationTime *time.Time             `json:"propagationTime,omitempty"`
}

func InitDashboardPresets(dir string) error {
	return nil
}

func readPublic(path string) (FICPublicDisplay, error) {
	var s FICPublicDisplay
	if fd, err := os.Open(path); err != nil {
		return s, err
	} else {
		defer fd.Close()
		jdec := json.NewDecoder(fd)

		if err := jdec.Decode(&s); err != nil {
			return s, err
		}

		return s, nil
	}
}

func savePublicTo(path string, s FICPublicDisplay) error {
	if fd, err := os.Create(path); err != nil {
		return err
	} else {
		defer fd.Close()
		jenc := json.NewEncoder(fd)

		if err := jenc.Encode(s); err != nil {
			return err
		}

		return nil
	}
}

type DashboardFiles struct {
	Presets []string             `json:"presets"`
	Nexts   []*NextDashboardFile `json:"nexts"`
}

type NextDashboardFile struct {
	Name   string    `json:"name"`
	Screen int       `json:"screen"`
	Date   time.Time `json:"date"`
}

func listPublic(c *gin.Context) {
	files, err := os.ReadDir(DashboardDir)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
		return
	}

	var ret DashboardFiles
	for _, file := range files {
		if strings.HasPrefix(file.Name(), "preset-") {
			ret.Presets = append(ret.Presets, strings.TrimSuffix(strings.TrimPrefix(file.Name(), "preset-"), ".json"))
			continue
		}

		if !strings.HasPrefix(file.Name(), "public") || len(file.Name()) < 18 {
			continue
		}

		ts, err := strconv.ParseInt(file.Name()[8:18], 10, 64)
		if err == nil {
			s, _ := strconv.Atoi(file.Name()[6:7])
			ret.Nexts = append(ret.Nexts, &NextDashboardFile{
				Name:   file.Name()[6:18],
				Screen: s,
				Date:   time.Unix(ts, 0),
			})
		}
	}

	c.JSON(http.StatusOK, ret)
}

func getPublic(c *gin.Context) {
	if strings.Contains(c.Params.ByName("sid"), "/") {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "sid cannot contains /"})
		return
	}

	filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid"))
	if strings.HasPrefix(c.Params.ByName("sid"), "preset-") {
		filename = fmt.Sprintf("%s.json", c.Params.ByName("sid"))
	}

	if _, err := os.Stat(path.Join(DashboardDir, filename)); !os.IsNotExist(err) {
		p, err := readPublic(path.Join(DashboardDir, filename))
		if err != nil {
			log.Println("Unable to readPublic in getPublic:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene retrieval."})
			return
		}

		c.JSON(http.StatusOK, p)
		return
	}

	c.JSON(http.StatusOK, FICPublicDisplay{Scenes: []FICPublicScene{}, Side: []FICPublicScene{}})
}

func deletePublic(c *gin.Context) {
	if strings.Contains(c.Params.ByName("sid"), "/") {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "sid cannot contains /"})
		return
	}

	filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid"))
	if strings.HasPrefix(c.Params.ByName("sid"), "preset-") {
		filename = fmt.Sprintf("%s.json", c.Params.ByName("sid"))
	}

	if len(filename) == 12 {
		if err := savePublicTo(path.Join(DashboardDir, filename), FICPublicDisplay{}); err != nil {
			log.Println("Unable to deletePublic:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene deletion."})
			return
		}
	} else {
		if err := os.Remove(path.Join(DashboardDir, filename)); err != nil {
			log.Println("Unable to deletePublic:", err.Error())
			c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene deletion."})
			return
		}
	}

	c.JSON(http.StatusOK, FICPublicDisplay{Scenes: []FICPublicScene{}, Side: []FICPublicScene{}})
}

func savePublic(c *gin.Context) {
	if strings.Contains(c.Params.ByName("sid"), "/") {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "sid cannot contains /"})
		return
	}

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

	filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid"))
	if c.Request.URL.Query().Has("t") {
		t, err := time.Parse(time.RFC3339, c.Request.URL.Query().Get("t"))
		if err != nil {
			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
			return
		}

		filename = fmt.Sprintf("public%s-%d.json", c.Params.ByName("sid"), t.Unix())
	} else if c.Request.URL.Query().Has("p") {
		filename = fmt.Sprintf("preset-%s.json", c.Request.URL.Query().Get("p"))
	}

	if err := savePublicTo(path.Join(DashboardDir, filename), scenes); err != nil {
		log.Println("Unable to savePublicTo:", err.Error())
		c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene saving."})
		return
	}

	c.JSON(http.StatusOK, scenes)
}
