package services

import (
	"backend/models"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"

	"gitea.tecamino.com/paadi/statusServer"
	"github.com/gin-gonic/gin"
)

type ServicesHandler struct {
	cfgDir   string
	cfgFile  string
	config   models.Configuration
	statusWs *statusServer.StatusWebsocket
}

func NewServicesHandler(cfgDir, cfgFile string) *ServicesHandler {
	return &ServicesHandler{cfgDir: cfgDir, cfgFile: cfgFile}
}

func (s *ServicesHandler) LinkStatusServer(statusServer *statusServer.StatusWebsocket) {
	s.statusWs = statusServer
}

func (s *ServicesHandler) GetAllAvaiableServices(c *gin.Context) {
	var services []models.Service

	defaultCfg, err := s.readDefaultConfig()
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}

	configFile := filepath.Join(s.cfgDir, defaultCfg.CfgFileName)
	if _, err := os.Stat(configFile); err != nil {
		c.JSON(http.StatusOK, gin.H{"services": defaultCfg.Services})
		return
	}

	content, err := os.ReadFile(configFile)
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}

	var cfg models.Configuration
	err = json.Unmarshal(content, &cfg)
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}

	// check wether service was already initiated then skip
next:
	for _, new := range defaultCfg.Services {
		for _, old := range cfg.Services {
			if new.Name == old.Name {
				continue next
			}
		}
		services = append(services, new)
	}
	c.JSON(http.StatusOK, gin.H{"services": services})
}

func (s *ServicesHandler) LoadAllServices(c *gin.Context) {

	defaultCfg, err := s.readDefaultConfig()
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}

	configFile := filepath.Join(s.cfgDir, defaultCfg.CfgFileName)
	if _, err := os.Stat(configFile); err != nil {
		c.JSON(http.StatusOK, gin.H{"message": "no services"})
		return
	}

	content, err := os.ReadFile(configFile)
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}

	err = json.Unmarshal(content, &s.config)
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}

	if len(s.config.Services) == 0 {
		c.JSON(http.StatusOK, gin.H{"message": "no services"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"services": s.config.Services})
}

func (s *ServicesHandler) AddNewService(c *gin.Context) {
	body, err := io.ReadAll(c.Request.Body)
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}

	var newService struct {
		Name string `json:"name"`
	}

	err = json.Unmarshal(body, &newService)
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}

	defaultCfg, err := s.readDefaultConfig()
	if err != nil {
		c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
		return
	}

	var config models.Configuration

	if _, err := os.Stat(filepath.Join(s.cfgDir, defaultCfg.CfgFileName)); err == nil {
		content, err := os.ReadFile(filepath.Join(s.cfgDir, defaultCfg.CfgFileName))
		if err != nil {
			c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
			return
		}
		err = json.Unmarshal(content, &config)
		if err != nil {
			c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
			return
		}
	}

	for _, service := range defaultCfg.Services {
		if service.Name == newService.Name {
			config.Services = append(config.Services, service)

			f, err := os.OpenFile(filepath.Join(s.cfgDir, defaultCfg.CfgFileName), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0774)
			if err != nil {
				c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
				return
			}
			defer f.Close()

			output, err := json.Marshal(config)
			if err != nil {
				c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
				return
			}

			_, err = f.Write(output)
			if err != nil {
				c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
				return
			}

			c.JSON(http.StatusOK, gin.H{"message": "service '" + newService.Name + "' added"})
			return
		}
	}
	c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(errors.New("no service '"+newService.Name+"' found")))
}

func (s *ServicesHandler) StartService(c *gin.Context) {
	service := c.Param("service")

	s.statusWs.Publish("status/"+service, gin.H{
		"state": "Starting",
	})

	for i := range s.config.Services {
		if s.config.Services[i].Name == service {
			if err := s.config.Services[i].Start(func(e string) {
				s.statusWs.Publish("status/"+service, gin.H{"state": "Error", "message": e})
			}); err != nil {
				c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
				s.statusWs.Publish("status/"+service, gin.H{
					"state":   "Error",
					"message": err.Error(),
				})
				return
			}
			c.JSON(http.StatusOK, gin.H{
				"message": fmt.Sprintf("service '%s' started", service),
			})
			s.statusWs.Publish("status/"+service, gin.H{
				"state": "Started",
			})
			return
		}
	}

	c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse(fmt.Sprintf("no service '%s' found", service)))
	s.statusWs.Publish("status/"+service, gin.H{
		"state":   "Error",
		"message": fmt.Sprintf("no service '%s' found", service),
	})
}

func (s *ServicesHandler) StopService(c *gin.Context) {
	service := c.Param("service")
	s.statusWs.Publish("status/"+service, gin.H{
		"state": "Stopping",
	})

	for i := range s.config.Services {
		if s.config.Services[i].Name == service {
			if err := s.config.Services[i].Stop(); err != nil {
				c.JSON(http.StatusBadGateway, gin.H{
					"error":   true,
					"message": fmt.Sprintf("service '%s' error; %v", service, err),
				})

				s.statusWs.Publish("status/"+service, gin.H{
					"state":   "Error",
					"message": fmt.Sprintf("service '%s' error; %v", service, err),
				})
				return
			}
			c.JSON(http.StatusOK, gin.H{
				"message": fmt.Sprintf("service '%s' stopped", service),
			})

			s.statusWs.Publish("status/"+service, gin.H{
				"state": "Stopped",
			})
			return
		}
	}

	c.JSON(http.StatusBadRequest, gin.H{
		"error":   true,
		"message": fmt.Sprintf("no service '%s' found", service),
	})

	s.statusWs.Publish("status/"+service, gin.H{
		"state":   "Stopped",
		"message": fmt.Sprintf("no service '%s' found", service),
	})
}

func (s *ServicesHandler) readDefaultConfig() (data models.Configuration, err error) {
	content, err := os.ReadFile(filepath.Join(s.cfgDir, s.cfgFile))
	if err != nil {
		return
	}
	err = json.Unmarshal(content, &data)
	return
}
