package fingerprint

import (
	"time"

	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/nomad/client/lib/cgutil"
)

const (
	cgroupAvailable   = "available"
	cgroupUnavailable = "unavailable"

	cgroupMountPointAttribute = "unique.cgroup.mountpoint"
	cgroupVersionAttribute    = "unique.cgroup.version"

	cgroupDetectInterval = 15 * time.Second
)

type CGroupFingerprint struct {
	logger             hclog.Logger
	lastState          string
	mountPointDetector MountPointDetector
	versionDetector    CgroupVersionDetector
}

// MountPointDetector isolates calls to the cgroup library.
//
// This facilitates testing where we can implement fake mount points to test
// various code paths.
type MountPointDetector interface {
	// MountPoint returns a cgroup mount-point.
	//
	// In v1, this is one arbitrary subsystem (e.g. /sys/fs/cgroup/cpu).
	//
	// In v2, this is the actual root mount point (i.e. /sys/fs/cgroup).
	MountPoint() (string, error)
}

// DefaultMountPointDetector implements the interface detector which calls the cgroups
// library directly
type DefaultMountPointDetector struct {
}

// MountPoint calls out to the default cgroup library.
func (*DefaultMountPointDetector) MountPoint() (string, error) {
	return cgutil.FindCgroupMountpointDir()
}

// CgroupVersionDetector isolates calls to the cgroup library.
type CgroupVersionDetector interface {
	// CgroupVersion returns v1 or v2 depending on the cgroups version in use.
	CgroupVersion() string
}

// DefaultCgroupVersionDetector implements the version detector which calls the
// cgroups library directly.
type DefaultCgroupVersionDetector struct {
}

func (*DefaultCgroupVersionDetector) CgroupVersion() string {
	if cgutil.UseV2 {
		return "v2"
	}
	return "v1"
}

// NewCGroupFingerprint returns a new cgroup fingerprinter
func NewCGroupFingerprint(logger hclog.Logger) Fingerprint {
	return &CGroupFingerprint{
		logger:             logger.Named("cgroup"),
		lastState:          cgroupUnavailable,
		mountPointDetector: new(DefaultMountPointDetector),
		versionDetector:    new(DefaultCgroupVersionDetector),
	}
}

// clearCGroupAttributes clears any node attributes related to cgroups that might
// have been set in a previous fingerprint run.
func (f *CGroupFingerprint) clearCGroupAttributes(r *FingerprintResponse) {
	r.RemoveAttribute(cgroupMountPointAttribute)
	r.RemoveAttribute(cgroupVersionAttribute)
}

// Periodic determines the interval at which the periodic fingerprinter will run.
func (f *CGroupFingerprint) Periodic() (bool, time.Duration) {
	return true, cgroupDetectInterval
}
