package define

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/containers/image/v5/manifest"
)

const (
	// HealthCheckHealthy describes a healthy container
	HealthCheckHealthy string = "healthy"
	// HealthCheckUnhealthy describes an unhealthy container
	HealthCheckUnhealthy string = "unhealthy"
	// HealthCheckStarting describes the time between when the container starts
	// and the start-period (time allowed for the container to start and application
	// to be running) expires.
	HealthCheckStarting string = "starting"
	// HealthCheckReset describes reset of HealthCheck logs
	HealthCheckReset string = "reset"
)

// HealthCheckStatus represents the current state of a container
type HealthCheckStatus int

const (
	// HealthCheckSuccess means the health worked
	HealthCheckSuccess HealthCheckStatus = iota
	// HealthCheckFailure means the health ran and failed
	HealthCheckFailure HealthCheckStatus = iota
	// HealthCheckContainerStopped means the health check cannot
	// be run because the container is stopped
	HealthCheckContainerStopped HealthCheckStatus = iota
	// HealthCheckContainerNotFound means the container could
	// not be found in local store
	HealthCheckContainerNotFound HealthCheckStatus = iota
	// HealthCheckNotDefined means the container has no health
	// check defined in it
	HealthCheckNotDefined HealthCheckStatus = iota
	// HealthCheckInternalError means some something failed obtaining or running
	// a given health check
	HealthCheckInternalError HealthCheckStatus = iota
	// HealthCheckDefined means the healthcheck was found on the container
	HealthCheckDefined HealthCheckStatus = iota
	// HealthCheckStartup means the healthcheck was unhealthy, but is still
	// either within the startup HC or the startup period of the healthcheck
	HealthCheckStartup HealthCheckStatus = iota
)

// Healthcheck defaults.  These are used both in the cli as well in
// libpod and were moved from cmd/podman/common
const (
	// DefaultHealthCheckInterval default value
	DefaultHealthCheckInterval = "30s"
	// DefaultHealthCheckRetries default value
	DefaultHealthCheckRetries uint = 3
	// DefaultHealthCheckStartPeriod default value
	DefaultHealthCheckStartPeriod = "0s"
	// DefaultHealthCheckTimeout default value
	DefaultHealthCheckTimeout = "30s"
	// DefaultHealthMaxLogCount default value
	DefaultHealthMaxLogCount uint = 5
	// DefaultHealthMaxLogSize default value
	DefaultHealthMaxLogSize uint = 500
	// DefaultHealthCheckLocalDestination default value
	DefaultHealthCheckLocalDestination string = "local"
)

const HealthCheckEventsLoggerDestination string = "events_logger"

// HealthConfig.Test options
const (
	// HealthConfigTestNone disables healthcheck
	HealthConfigTestNone = "NONE"
	// HealthConfigTestCmd execs arguments directly
	HealthConfigTestCmd = "CMD"
	// HealthConfigTestCmdShell runs commands with the system's default shell
	HealthConfigTestCmdShell = "CMD-SHELL"
)

// HealthCheckOnFailureAction defines how Podman reacts when a container's health
// status turns unhealthy.
type HealthCheckOnFailureAction int

// Healthcheck on-failure actions.
const (
	// HealthCheckOnFailureActionNonce instructs Podman to not react on an unhealthy status.
	HealthCheckOnFailureActionNone = iota // Must be first iota for backwards compatibility
	// HealthCheckOnFailureActionInvalid denotes an invalid on-failure policy.
	HealthCheckOnFailureActionInvalid = iota
	// HealthCheckOnFailureActionNonce instructs Podman to kill the container on an unhealthy status.
	HealthCheckOnFailureActionKill = iota
	// HealthCheckOnFailureActionNonce instructs Podman to restart the container on an unhealthy status.
	HealthCheckOnFailureActionRestart = iota
	// HealthCheckOnFailureActionNonce instructs Podman to stop the container on an unhealthy status.
	HealthCheckOnFailureActionStop = iota
)

// String representations for on-failure actions.
const (
	strHealthCheckOnFailureActionNone    = "none"
	strHealthCheckOnFailureActionInvalid = "invalid"
	strHealthCheckOnFailureActionKill    = "kill"
	strHealthCheckOnFailureActionRestart = "restart"
	strHealthCheckOnFailureActionStop    = "stop"
)

// SupportedHealthCheckOnFailureActions lists all supported healthcheck restart policies.
var SupportedHealthCheckOnFailureActions = []string{
	strHealthCheckOnFailureActionNone,
	strHealthCheckOnFailureActionKill,
	strHealthCheckOnFailureActionRestart,
	strHealthCheckOnFailureActionStop,
}

// String returns the string representation of the HealthCheckOnFailureAction.
func (h HealthCheckOnFailureAction) String() string {
	switch h {
	case HealthCheckOnFailureActionNone:
		return strHealthCheckOnFailureActionNone
	case HealthCheckOnFailureActionKill:
		return strHealthCheckOnFailureActionKill
	case HealthCheckOnFailureActionRestart:
		return strHealthCheckOnFailureActionRestart
	case HealthCheckOnFailureActionStop:
		return strHealthCheckOnFailureActionStop
	default:
		return strHealthCheckOnFailureActionInvalid
	}
}

// ParseHealthCheckOnFailureAction parses the specified string into a HealthCheckOnFailureAction.
// An error is returned for an invalid input.
func ParseHealthCheckOnFailureAction(s string) (HealthCheckOnFailureAction, error) {
	switch s {
	case "", strHealthCheckOnFailureActionNone:
		return HealthCheckOnFailureActionNone, nil
	case strHealthCheckOnFailureActionKill:
		return HealthCheckOnFailureActionKill, nil
	case strHealthCheckOnFailureActionRestart:
		return HealthCheckOnFailureActionRestart, nil
	case strHealthCheckOnFailureActionStop:
		return HealthCheckOnFailureActionStop, nil
	default:
		err := fmt.Errorf("invalid on-failure action %q for health check: supported actions are %s", s, strings.Join(SupportedHealthCheckOnFailureActions, ","))
		return HealthCheckOnFailureActionInvalid, err
	}
}

// StartupHealthCheck is the configuration of a startup healthcheck.
type StartupHealthCheck struct {
	manifest.Schema2HealthConfig
	// Successes are the number of successes required to mark the startup HC
	// as passed.
	// If set to 0, a single success will mark the HC as passed.
	Successes int `json:",omitempty"`
}

type UpdateHealthCheckConfig struct {
	// HealthLogDestination set the destination of the HealthCheck log.
	// Directory path, local or events_logger (local use container state file)
	// Warning: Changing this setting may cause the loss of previous logs!
	HealthLogDestination *string `json:"health_log_destination,omitempty"`
	// HealthMaxLogSize set maximum length in characters of stored HealthCheck log.
	// ('0' value means an infinite log length)
	HealthMaxLogSize *uint `json:"health_max_log_size,omitempty"`
	// HealthMaxLogCount set maximum number of attempts in the HealthCheck log file.
	// ('0' value means an infinite number of attempts in the log file)
	HealthMaxLogCount *uint `json:"health_max_log_count,omitempty"`
	// HealthOnFailure set the action to take once the container turns unhealthy.
	HealthOnFailure *string `json:"health_on_failure,omitempty"`
	// Disable healthchecks on container.
	NoHealthCheck *bool `json:"no_healthcheck,omitempty"`
	// HealthCmd set a healthcheck command for the container. ('none' disables the existing healthcheck)
	HealthCmd *string `json:"health_cmd,omitempty"`
	// HealthInterval set an interval for the healthcheck.
	// (a value of disable results in no automatic timer setup) Changing this setting resets timer.
	HealthInterval *string `json:"health_interval,omitempty"`
	// HealthRetries set the number of retries allowed before a healthcheck is considered to be unhealthy.
	HealthRetries *uint `json:"health_retries,omitempty"`
	// HealthTimeout set the maximum time allowed to complete the healthcheck before an interval is considered failed.
	HealthTimeout *string `json:"health_timeout,omitempty"`
	// HealthStartPeriod set the initialization time needed for a container to bootstrap.
	HealthStartPeriod *string `json:"health_start_period,omitempty"`
	// HealthStartupCmd set a startup healthcheck command for the container.
	HealthStartupCmd *string `json:"health_startup_cmd,omitempty"`
	// HealthStartupInterval set an interval for the startup healthcheck.
	// Changing this setting resets the timer, depending on the state of the container.
	HealthStartupInterval *string `json:"health_startup_interval,omitempty"`
	// HealthStartupRetries set the maximum number of retries before the startup healthcheck will restart the container.
	HealthStartupRetries *uint `json:"health_startup_retries,omitempty"`
	// HealthStartupTimeout set the maximum amount of time that the startup healthcheck may take before it is considered failed.
	HealthStartupTimeout *string `json:"health_startup_timeout,omitempty"`
	// HealthStartupSuccess set the number of consecutive successes before the startup healthcheck is marked as successful
	// and the normal healthcheck begins (0 indicates any success will start the regular healthcheck)
	HealthStartupSuccess *uint `json:"health_startup_success,omitempty"`
}

func (u *UpdateHealthCheckConfig) IsStartupHealthCheckCommandSet(startupHealthCheck *StartupHealthCheck) bool {
	containsStartupHealthCheckCmd := u.HealthStartupCmd != nil
	containsFlags := (u.HealthStartupInterval != nil || u.HealthStartupRetries != nil ||
		u.HealthStartupTimeout != nil || u.HealthStartupSuccess != nil)
	return startupHealthCheck == nil && !containsStartupHealthCheckCmd && containsFlags
}

func (u *UpdateHealthCheckConfig) IsHealthCheckCommandSet(healthCheck *manifest.Schema2HealthConfig) bool {
	containsStartupHealthCheckCmd := u.HealthCmd != nil
	containsFlags := (u.HealthInterval != nil || u.HealthRetries != nil ||
		u.HealthTimeout != nil || u.HealthStartPeriod != nil)
	return healthCheck == nil && !containsStartupHealthCheckCmd && containsFlags
}

func (u *UpdateHealthCheckConfig) SetNewStartupHealthCheckConfigTo(healthCheckOptions *HealthCheckOptions) bool {
	changed := false

	if u.HealthStartupCmd != nil {
		healthCheckOptions.Cmd = *u.HealthStartupCmd
		changed = true
	}

	if u.HealthStartupInterval != nil {
		healthCheckOptions.Interval = *u.HealthStartupInterval
		changed = true
	}

	if u.HealthStartupRetries != nil {
		healthCheckOptions.Retries = int(*u.HealthStartupRetries)
		changed = true
	}

	if u.HealthStartupTimeout != nil {
		healthCheckOptions.Timeout = *u.HealthStartupTimeout
		changed = true
	}

	if u.HealthStartupSuccess != nil {
		healthCheckOptions.Successes = int(*u.HealthStartupSuccess)
		changed = true
	}

	healthCheckOptions.StartPeriod = "1s"

	return changed
}

func (u *UpdateHealthCheckConfig) SetNewHealthCheckConfigTo(healthCheckOptions *HealthCheckOptions) bool {
	changed := false

	if u.HealthCmd != nil {
		healthCheckOptions.Cmd = *u.HealthCmd
		changed = true
	}

	if u.HealthInterval != nil {
		healthCheckOptions.Interval = *u.HealthInterval
		changed = true
	}

	if u.HealthRetries != nil {
		healthCheckOptions.Retries = int(*u.HealthRetries)
		changed = true
	}

	if u.HealthTimeout != nil {
		healthCheckOptions.Timeout = *u.HealthTimeout
		changed = true
	}

	if u.HealthStartPeriod != nil {
		healthCheckOptions.StartPeriod = *u.HealthStartPeriod
		changed = true
	}

	return changed
}

func GetValidHealthCheckDestination(destination string) (string, error) {
	if destination == HealthCheckEventsLoggerDestination || destination == DefaultHealthCheckLocalDestination {
		return destination, nil
	}

	fileInfo, err := os.Stat(destination)
	if err != nil {
		return "", fmt.Errorf("HealthCheck Log '%s' destination error: %w", destination, err)
	}
	mode := fileInfo.Mode()
	if !mode.IsDir() {
		return "", fmt.Errorf("HealthCheck Log '%s' destination must be directory", destination)
	}

	absPath, err := filepath.Abs(destination)
	if err != nil {
		return "", err
	}
	return absPath, nil
}

func (u *UpdateHealthCheckConfig) GetNewGlobalHealthCheck() (GlobalHealthCheckOptions, error) {
	globalOptions := GlobalHealthCheckOptions{}

	healthLogDestination := u.HealthLogDestination
	if u.HealthLogDestination != nil {
		dest, err := GetValidHealthCheckDestination(*u.HealthLogDestination)
		if err != nil {
			return GlobalHealthCheckOptions{}, err
		}
		healthLogDestination = &dest
	}
	globalOptions.HealthLogDestination = healthLogDestination

	globalOptions.HealthMaxLogSize = u.HealthMaxLogSize

	globalOptions.HealthMaxLogCount = u.HealthMaxLogCount

	if u.HealthOnFailure != nil {
		val, err := ParseHealthCheckOnFailureAction(*u.HealthOnFailure)
		if err != nil {
			return globalOptions, err
		}
		globalOptions.HealthCheckOnFailureAction = &val
	}

	return globalOptions, nil
}

type HealthCheckOptions struct {
	Cmd         string
	Interval    string
	Retries     int
	Timeout     string
	StartPeriod string
	Successes   int
}

type GlobalHealthCheckOptions struct {
	HealthLogDestination       *string
	HealthMaxLogCount          *uint
	HealthMaxLogSize           *uint
	HealthCheckOnFailureAction *HealthCheckOnFailureAction
}
