//go:build darwin

package diagnostic

import (
	"context"
	"fmt"
	"os/exec"
	"strconv"
)

type SystemCollectorImpl struct {
	version string
}

func NewSystemCollectorImpl(
	version string,
) *SystemCollectorImpl {
	return &SystemCollectorImpl{
		version,
	}
}

func (collector *SystemCollectorImpl) Collect(ctx context.Context) (*SystemInformation, string, error) {
	memoryInfo, memoryInfoRaw, memoryInfoErr := collectMemoryInformation(ctx)
	fdInfo, fdInfoRaw, fdInfoErr := collectFileDescriptorInformation(ctx)
	disks, disksRaw, diskErr := collectDiskVolumeInformationUnix(ctx)
	osInfo, osInfoRaw, osInfoErr := collectOSInformationUnix(ctx)

	if memoryInfoErr != nil {
		return nil, RawSystemInformation(osInfoRaw, memoryInfoRaw, fdInfoRaw, disksRaw), memoryInfoErr
	}

	if fdInfoErr != nil {
		return nil, RawSystemInformation(osInfoRaw, memoryInfoRaw, fdInfoRaw, disksRaw), fdInfoErr
	}

	if diskErr != nil {
		return nil, RawSystemInformation(osInfoRaw, memoryInfoRaw, fdInfoRaw, disksRaw), diskErr
	}

	if osInfoErr != nil {
		return nil, RawSystemInformation(osInfoRaw, memoryInfoRaw, fdInfoRaw, disksRaw), osInfoErr
	}

	return NewSystemInformation(
		memoryInfo.MemoryMaximum,
		memoryInfo.MemoryCurrent,
		fdInfo.FileDescriptorMaximum,
		fdInfo.FileDescriptorCurrent,
		osInfo.OsSystem,
		osInfo.Name,
		osInfo.OsVersion,
		osInfo.OsRelease,
		osInfo.Architecture,
		collector.version,
		disks,
	), "", nil
}

func collectFileDescriptorInformation(ctx context.Context) (
	*FileDescriptorInformation,
	string,
	error,
) {
	const (
		fileDescriptorMaximumKey = "kern.maxfiles"
		fileDescriptorCurrentKey = "kern.num_files"
	)

	command := exec.CommandContext(ctx, "sysctl", fileDescriptorMaximumKey, fileDescriptorCurrentKey)

	stdout, err := command.Output()
	if err != nil {
		return nil, "", fmt.Errorf("error retrieving output from command '%s': %w", command.String(), err)
	}

	output := string(stdout)

	fileDescriptorInfo, err := ParseFileDescriptorInformationFromKV(
		output,
		fileDescriptorMaximumKey,
		fileDescriptorCurrentKey,
	)
	if err != nil {
		return nil, output, err
	}

	// returning raw output in case other collected information
	// resulted in errors
	return fileDescriptorInfo, output, nil
}

func collectMemoryInformation(ctx context.Context) (
	*MemoryInformation,
	string,
	error,
) {
	const (
		memoryMaximumKey   = "hw.memsize"
		memoryAvailableKey = "hw.memsize_usable"
	)

	command := exec.CommandContext(
		ctx,
		"sysctl",
		memoryMaximumKey,
		memoryAvailableKey,
	)

	stdout, err := command.Output()
	if err != nil {
		return nil, "", fmt.Errorf("error retrieving output from command '%s': %w", command.String(), err)
	}

	output := string(stdout)

	mapper := func(field string) (uint64, error) {
		const kiloBytes = 1024
		value, err := strconv.ParseUint(field, 10, 64)
		return value / kiloBytes, err
	}

	memoryInfo, err := ParseMemoryInformationFromKV(output, memoryMaximumKey, memoryAvailableKey, mapper)
	if err != nil {
		return nil, output, err
	}

	// returning raw output in case other collected information
	// resulted in errors
	return memoryInfo, output, nil
}