package sdnotify

import (
	"errors"
	"fmt"
	"net"
	"os"
	"strings"
)

const (
	ReadyEnabled     = "READY=1"
	StoppingEnabled  = "STOPPING=1"
	ReloadingEnabled = "RELOADING=1"
)

var (
	socket *net.UnixAddr

	errSocketVariableIsNotPresent = errors.New("\"NOTIFY_SOCKET\" environment variable is not present")
	errSocketIsNotInitialized     = errors.New("socket is not initialized")
)

// Initializes socket with provided name of
// environment variable.
func InitSocket() error {
	notifySocket := os.Getenv("NOTIFY_SOCKET")
	if notifySocket == "" {
		return errSocketVariableIsNotPresent
	}
	socket = &net.UnixAddr{
		Name: notifySocket,
		Net:  "unixgram",
	}
	return nil
}

// FlagAndStatus sends systemd a combination of a
// well-known status and STATUS=%s{status}, separated by newline.
func FlagAndStatus(status string) error {
	if status == ReloadingEnabled {
		// From https://www.man7.org/linux/man-pages/man5/systemd.service.5.html
		//
		//    When initiating the reload process the service is
		//    expected to reply with a notification message via
		//    sd_notify(3) that contains the "RELOADING=1" field in
		//    combination with "MONOTONIC_USEC=" set to the current
		//    monotonic time (i.e.  CLOCK_MONOTONIC in
		//    clock_gettime(2)) in μs, formatted as decimal string.
		//    Once reloading is complete another notification message
		//    must be sent, containing "READY=1".
		//
		// For MONOTONIC_USEC format refer to https://www.man7.org/linux/man-pages/man3/sd_notify.3.html
		status += fmt.Sprintf("\nMONOTONIC_USEC=%d", uint64(nanotime())/1e3 /* microseconds in nanoseconds */)
	}
	status += "\nSTATUS=" + strings.TrimSuffix(status, "=1")
	return Send(status)
}

// Status sends systemd notify STATUS=%s{status}.
func Status(status string) error {
	return Send("STATUS=" + status)
}

// Send state through the notify socket if any.
// If the notify socket was not detected, it returns an error.
func Send(state string) error {
	if socket == nil {
		return errSocketIsNotInitialized
	}
	conn, err := net.DialUnix(socket.Net, nil, socket)
	if err != nil {
		return fmt.Errorf("can't open unix socket: %v", err)
	}
	defer conn.Close()
	if _, err = conn.Write([]byte(state)); err != nil {
		return fmt.Errorf("can't write into the unix socket: %v", err)
	}
	return nil
}