package nginx

import (
	"fmt"
	"os"

	"npm/internal/config"
	"npm/internal/entity/certificate"
	"npm/internal/entity/host"
	"npm/internal/entity/upstream"
	"npm/internal/logger"
	"npm/internal/status"
)

const (
	DeletedSuffix  = ".deleted"
	DisabledSuffix = ".disabled"
	ErrorSuffix    = ".error"
)

// ConfigureHost will attempt to write nginx conf and reload nginx
// When a host is disabled or deleted, it will name the file with a suffix
// that won't be used by nginx.
func ConfigureHost(h host.Model) error {
	// nolint: errcheck, gosec
	h.Expand([]string{"certificate", "nginxtemplate", "upstream"})

	var certificateTemplate certificate.Template
	if h.Certificate != nil {
		certificateTemplate = h.Certificate.GetTemplate()
	}

	var ups upstream.Model
	if h.Upstream != nil {
		ups = *h.Upstream
	}

	data := TemplateData{
		Certificate: certificateTemplate,
		ConfDir:     fmt.Sprintf("%s/nginx/hosts", config.Configuration.DataFolder),
		Config: Config{ // todo
			Ipv4: true,
			Ipv6: false,
		},
		DataDir:  config.Configuration.DataFolder,
		Host:     h.GetTemplate(),
		Upstream: ups,
	}

	removeHostFiles(h)
	filename := getHostFilename(h, "")
	if h.IsDeleted {
		filename = getHostFilename(h, DeletedSuffix)
	} else if h.IsDisabled {
		filename = getHostFilename(h, DisabledSuffix)
	}

	// Write the config to disk
	err := writeTemplate(filename, h.NginxTemplate.Template, data, "")
	if err != nil {
		// this configuration failed somehow
		h.Status = status.StatusError
		h.ErrorMessage = fmt.Sprintf("Template generation failed: %s", err.Error())
		logger.Debug(h.ErrorMessage)
		return h.Save(true)
	}

	// Reload Nginx and check for errors
	if output, err := reloadNginx(); err != nil {
		// reloading nginx failed, likely due to this host having a problem
		h.Status = status.StatusError
		h.ErrorMessage = fmt.Sprintf("Nginx configuation error: %s - %s", err.Error(), output)

		// Write the .error file, if this isn't a deleted or disabled host
		// as the reload will only fail because of this host, if it's enabled
		if !h.IsDeleted && !h.IsDisabled {
			filename = getHostFilename(h, ErrorSuffix)
			// Clear existing file(s) again
			removeHostFiles(h)
			// Write the template again, but with an error message at the end of the file
			// nolint: errcheck, gosec
			writeTemplate(filename, h.NginxTemplate.Template, data, h.ErrorMessage)
		}

		logger.Debug(h.ErrorMessage)
	} else {
		// All good
		h.Status = status.StatusOK
		h.ErrorMessage = ""
		logger.Debug("ConfigureHost OK: %+v", h)
	}

	return h.Save(true)
}

// ConfigureUpstream will attempt to write nginx conf and reload nginx
func ConfigureUpstream(u upstream.Model) error {
	logger.Debug("ConfigureUpstream: %+v)", u)

	// nolint: errcheck, gosec
	u.Expand([]string{"nginxtemplate"})

	data := TemplateData{
		ConfDir:  fmt.Sprintf("%s/nginx/upstreams", config.Configuration.DataFolder),
		DataDir:  config.Configuration.DataFolder,
		Upstream: u,
	}

	removeUpstreamFiles(u)
	filename := getUpstreamFilename(u, "")
	if u.IsDeleted {
		filename = getUpstreamFilename(u, DeletedSuffix)
	}

	// Write the config to disk
	err := writeTemplate(filename, u.NginxTemplate.Template, data, "")
	if err != nil {
		// this configuration failed somehow
		u.Status = status.StatusError
		u.ErrorMessage = fmt.Sprintf("Template generation failed: %s", err.Error())
		logger.Debug(u.ErrorMessage)
		return u.Save(true)
	}

	// nolint: errcheck, gosec
	if output, err := reloadNginx(); err != nil {
		// reloading nginx failed, likely due to this host having a problem
		u.Status = status.StatusError
		u.ErrorMessage = fmt.Sprintf("Nginx configuation error: %s - %s", err.Error(), output)

		// Write the .error file, if this isn't a deleted upstream
		// as the reload will only fail because of this upstream
		if !u.IsDeleted {
			filename = getUpstreamFilename(u, ErrorSuffix)
			// Clear existing file(s) again
			removeUpstreamFiles(u)
			// Write the template again, but with an error message at the end of the file
			// nolint: errcheck, gosec
			writeTemplate(filename, u.NginxTemplate.Template, data, u.ErrorMessage)
		}

		logger.Debug(u.ErrorMessage)
	} else {
		// All good
		u.Status = status.StatusOK
		u.ErrorMessage = ""
		logger.Debug("ConfigureUpstream OK: %+v", u)
	}

	return u.Save(true)
}

func getHostFilename(h host.Model, append string) string {
	confDir := fmt.Sprintf("%s/nginx/hosts", config.Configuration.DataFolder)
	return fmt.Sprintf("%s/host_%d.conf%s", confDir, h.ID, append)
}

func getUpstreamFilename(u upstream.Model, append string) string {
	confDir := fmt.Sprintf("%s/nginx/upstreams", config.Configuration.DataFolder)
	return fmt.Sprintf("%s/upstream_%d.conf%s", confDir, u.ID, append)
}

func removeHostFiles(h host.Model) {
	removeFiles([]string{
		getHostFilename(h, ""),
		getHostFilename(h, DeletedSuffix),
		getHostFilename(h, DisabledSuffix),
		getHostFilename(h, ErrorSuffix),
	})
}

func removeUpstreamFiles(u upstream.Model) {
	removeFiles([]string{
		getUpstreamFilename(u, ""),
		getUpstreamFilename(u, DeletedSuffix),
		getUpstreamFilename(u, ErrorSuffix),
	})
}

func removeFiles(files []string) {
	for _, file := range files {
		if _, err := os.Stat(file); err == nil {
			// nolint: errcheck, gosec
			os.Remove(file)
		}
	}
}

// GetHostConfigContent returns nginx config as it exists on disk
func GetHostConfigContent(h host.Model) (string, error) {
	filename := getHostFilename(h, "")
	if h.ErrorMessage != "" {
		filename = getHostFilename(h, ErrorSuffix)
	}
	if h.IsDisabled {
		filename = getHostFilename(h, DisabledSuffix)
	}
	if h.IsDeleted {
		filename = getHostFilename(h, DeletedSuffix)
	}

	// nolint: gosec
	cnt, err := os.ReadFile(filename)
	if err != nil {
		return "", err
	}
	return string(cnt), nil
}

// GetUpstreamConfigContent returns nginx config as it exists on disk
func GetUpstreamConfigContent(u upstream.Model) (string, error) {
	filename := getUpstreamFilename(u, "")
	if u.ErrorMessage != "" {
		filename = getUpstreamFilename(u, ErrorSuffix)
	}
	if u.IsDeleted {
		filename = getUpstreamFilename(u, DeletedSuffix)
	}

	// nolint: gosec
	cnt, err := os.ReadFile(filename)
	if err != nil {
		return "", err
	}
	return string(cnt), nil
}