<#
.SYNOPSIS
    Converts any kind of Icinga threshold with provided units
    to the lowest base of the unit which makes sense. It does
    support the Icinga plugin language, like ~:30, @10:40, 15:30,
    ...

    The conversion does currently support the following units:

    Size: B, KB, MB, GB, TB, PT, KiB, MiB, GiB, TiB, PiB
    Time: ms, s, m, h, d w, M, y
.DESCRIPTION
    Converts any kind of Icinga threshold with provided units
    to the lowest base of the unit. It does support the Icinga
    plugin language, like ~:30, @10:40, 15:30, ...

    You can also provide date time values in the format of "yyyy/MM/dd HH:mm:ss"
    and use Icinga for Windows plugin thresholds in combination. You have to escape
    the ':' inside the date time value with a '`' to ensure the correct conversion.

    Example:
    2024/08/19 12`:42`:00

    The conversion does currently support the following units:

    Size: B, KB, MB, GB, TB, PT, KiB, MiB, GiB, TiB, PiB
    Time: ms, s, m, h, d w, M, y
.FUNCTIONALITY
    Converts values with units to the lowest unit of this category.
    Accepts Icinga Thresholds.
.PARAMETER Threshold
    The threshold value to convert. Supports Icinga plugin threshold
    syntax.
.PARAMETER RawValue
    If set, the function will return the raw value without any conversion.
    This is required for match/not match checks as example
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '20d';

    Name                           Value
    ----                           -----
    EndRange
    Unit                           s
    StartRange
    Threshold                      1728000
    Mode                           0
    Raw                            20d
    IsDateTime                     False
    Value                          1728000
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '5GB';

    Name                           Value
    ----                           -----
    EndRange
    Unit                           B
    StartRange
    Threshold                      5000000000
    Mode                           0
    Raw                            5GB
    IsDateTime                     False
    Value                          5000000000
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '10MB:20MB';

    Name                           Value
    ----                           -----
    EndRange                       20000000
    Unit                           B
    StartRange                     10000000
    Threshold                      10000000:20000000
    Mode                           3
    Raw                            10MB:20MB
    IsDateTime                     False
    Value
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '10m:1h';

    Name                           Value
    ----                           -----
    EndRange                       3600
    Unit                           s
    StartRange                     600
    Threshold                      600:3600
    Mode                           3
    Raw                            10m:1h
    Value
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '@10m:1h';

    Name                           Value
    ----                           -----
    EndRange                       3600
    Unit                           s
    StartRange                     600
    Threshold                      @600:3600
    Mode                           4
    Raw                            @10m:1h
    IsDateTime                     False
    Value
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '~:1M';

    Name                           Value
    ----                           -----
    EndRange
    Unit                           s
    StartRange
    Threshold                      ~:2592000
    Mode                           2
    Raw                            ~:1M
    IsDateTime                     False
    Value                          2592000
.EXAMPLE
    PS>Convert-IcingaPluginThresholds -Threshold '@2024/08/19 12`:42`:00:2024/08/19 12`:42`:00';
    Name                           Value
    ----                           -----
    EndRange                       133685377200000000
    Unit                           s
    StartRange                     133685377200000000
    Threshold                      @133685377200000000:133685377200000000
    Mode                           4
    Raw                            @2024/08/19 12`:42`:00:2024/08/19 12`:42`:00
    IsDateTime                     True
    Value
.INPUTS
   System.String
.OUTPUTS
    System.Hashtable
.LINK
   https://github.com/Icinga/icinga-powershell-framework
#>

function Convert-IcingaPluginThresholds()
{
    param (
        [string]$Threshold = $null,
        [switch]$RawValue  = $false
    );

    [hashtable]$RetValue = @{
        'Raw'        = $Threshold;
        'Unit'       = '';
        'Threshold'  = $null;
        'Value'      = $null;
        'StartRange' = $null;
        'EndRange'   = $null;
        'Mode'       = $IcingaEnums.IcingaThresholdMethod.Default;
        'IsDateTime' = $FALSE;
    };

    # Always ensure Value is set, because we just want the raw value
    if ([string]::IsNullOrEmpty($Threshold) -or $RawValue) {
        $RetValue.Value = $Threshold;

        return $RetValue;
    }

    # Always ensure we are using correct digits
    $Threshold      = $Threshold.Replace(',', '.');
    [array]$Content = @();

    if ($Threshold.Contains(':')) {
        # If we have more than one ':' inside our string, lets check if this is a date time value
        # In case it is convert it properly to a FileTime we can work with later on
        if ([Regex]::Matches($Threshold, '`:').Count -gt 1) {
            [bool]$HasTilde = $FALSE;
            [bool]$HasAt    = $FALSE;

            if ($Threshold.Contains('@')) {
                $HasAt = $TRUE;
            } elseif ($Threshold.Contains('~')) {
                $HasTilde = $TRUE;
            }

            $Threshold               = $Threshold.Replace('`:', '!').Replace('~', '').Replace('@', '');
            [array]$DatimeValueArray = $Threshold.Split(':');

            try {
                [array]$DateTimeValue = @();
                if ([string]::IsNullOrEmpty($DatimeValueArray[0]) -eq $FALSE) {
                    [array]$DateTimeValue += ([DateTime]::ParseExact($DatimeValueArray[0].Replace('!', ':'), 'yyyy\/MM\/dd HH:mm:ss', $null)).ToFileTime();
                }
                if ([string]::IsNullOrEmpty($DatimeValueArray[1]) -eq $FALSE) {
                    [array]$DateTimeValue += ([DateTime]::ParseExact($DatimeValueArray[1].Replace('!', ':'), 'yyyy\/MM\/dd HH:mm:ss', $null)).ToFileTime();
                }

                if ($DateTimeValue.Count -gt 1) {
                    $Threshold     = [string]::Join(':', $DateTimeValue);
                    $RetValue.Mode = $IcingaEnums.IcingaThresholdMethod.Between;

                    if ($HasAt) {
                        $Threshold = [string]::Format('@{0}', $Threshold);
                    }
                } elseif ($DatimeValueArray.Count -gt 1) {
                    if ($HasTilde) {
                        $Threshold = [string]::Format('~:{0}', $DateTimeValue[0]);
                    } else {
                        $Threshold = [string]::Format('{0}:', $DateTimeValue[0]);
                    }
                } else {
                    $Threshold = $DateTimeValue[0];
                }
                $RetValue.Unit = 's';
                $RetValue.IsDateTime = $TRUE;
            } catch {
                $RetValue.Threshold = $Threshold.Replace('!', '`:');
                Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', $RetValue.Raw)) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force;
                return $RetValue;
            }
        } else {
            $RetValue.Mode  = $IcingaEnums.IcingaThresholdMethod.Between;
        }

        $Content = $Threshold.Split(':');
        if ($Content.Count -eq 2 -And ([string]::IsNullOrEmpty($Content[1]))) {
            $RetValue.Mode  = $IcingaEnums.IcingaThresholdMethod.Lower;
        }
    } else {
        $Content += $Threshold;
    }

    [array]$ConvertedValue = @();

    foreach ($ThresholdValue in $Content) {

        [bool]$HasTilde = $FALSE;
        [bool]$HasAt    = $FALSE;
        [bool]$Negate   = $FALSE;
        $Value          = '';
        $WorkUnit       = '';

        if ($ThresholdValue.Contains('~')) {
            $ThresholdValue = $ThresholdValue.Replace('~', '');
            $HasTilde       = $TRUE;
            $RetValue.Mode  = $IcingaEnums.IcingaThresholdMethod.Greater;
        } elseif ($ThresholdValue.Contains('@')) {
            $HasAt          = $TRUE;
            $ThresholdValue = $ThresholdValue.Replace('@', '');
            $RetValue.Mode  = $IcingaEnums.IcingaThresholdMethod.Outside;
        }

        if ($ThresholdValue[0] -eq '-' -And $ThresholdValue.Length -ge 1) {
            $Negate         = $TRUE;
            $ThresholdValue = $ThresholdValue.Substring(1, $ThresholdValue.Length - 1);
        }

        if (($ThresholdValue -Match "(^-?[0-9]+)((\.|\,)[0-9]+)?(B|KB|MB|GB|TB|PT|KiB|MiB|GiB|TiB|PiB)$")) {
            $WorkUnit = 'B';
            if ([string]::IsNullOrEmpty($RetValue.Unit) -eq $FALSE -And $RetValue.Unit -ne $WorkUnit) {
                Exit-IcingaThrowException -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.MultipleUnitUsage -Force;
            }
            $Value         = (Convert-Bytes -Value $ThresholdValue -Unit $WorkUnit).Value;
            $RetValue.Unit = $WorkUnit;
        } elseif (($ThresholdValue -Match "(^-?[0-9]+)((\.|\,)[0-9]+)?(ms|s|m|h|d|w|M|y)$")) {
            $WorkUnit = 's';
            if ([string]::IsNullOrEmpty($RetValue.Unit) -eq $FALSE -And $RetValue.Unit -ne $WorkUnit) {
                Exit-IcingaThrowException -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.MultipleUnitUsage -Force;
            }
            $Value         = (ConvertTo-Seconds -Value $ThresholdValue);
            $RetValue.Unit = $WorkUnit;
        } elseif (($ThresholdValue -Match "(^[\d\.]*) ?(%)")) {
            $WorkUnit      = '%';
            $Value         = ([string]$ThresholdValue).Replace(' ', '').Replace('%', '');
            $RetValue.Unit = $WorkUnit;
        } elseif (($ThresholdValue -Match "(^-?[0-9]+)((\.|\,)[0-9]+)?(Kbit|Mbit|Gbit|Tbit|Pbit|Ebit|Zbit|Ybit)$")) {
            $Value         = $Matches[1];
            $RetValue.Unit = $Matches[4];
        } else {
            # Load all other units/values generically
            [string]$StrNumeric = '';
            [bool]$FirstChar    = $TRUE;
            [bool]$Delimiter    = $FALSE;
            foreach ($entry in ([string]($ThresholdValue)).ToCharArray()) {
                if ((Test-Numeric $entry) -Or ($entry -eq '.' -And $Delimiter -eq $FALSE)) {
                    $StrNumeric += $entry;
                    $FirstChar   = $FALSE;
                    if ($entry -eq '.') {
                        $Delimiter = $TRUE;
                    }
                } else {
                    if ([string]::IsNullOrEmpty($RetValue.Unit) -And $FirstChar -eq $FALSE) {
                        $RetValue.Unit = $entry;
                    } else {
                        $StrNumeric    = '';
                        $RetValue.Unit = '';
                        break;
                    }
                }
            }
            if ([string]::IsNullOrEmpty($StrNumeric)) {
                $Value = $ThresholdValue;
            } else {
                $Value = [decimal]$StrNumeric;
            }
        }

        if ((Test-Numeric $Value) -And $Negate) {
            $Value = $Value * -1;
        } elseif ($Negate) {
            $Value = [string]::Format('-{0}', $Value);
        }

        if ($HasTilde) {
            $ConvertedValue += [string]::Format('~{0}', $Value);
        } elseif ($HasAt) {
            $ConvertedValue += [string]::Format('@{0}', $Value);
        } else {
            $ConvertedValue += $Value;
        }
    }

    [string]$Value = [string]::Join(':', $ConvertedValue);

    switch ($RetValue.Mode) {
        $IcingaEnums.IcingaThresholdMethod.Default {
            $RetValue.Value = $ConvertedValue[0];

            if ([string]::IsNullOrEmpty($RetValue.Value)) {
                Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', $RetValue.Raw)) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force;
                return $RetValue;
            }
            break;
        };
        $IcingaEnums.IcingaThresholdMethod.Lower {
            $RetValue.Value = $ConvertedValue[0];

            if ([string]::IsNullOrEmpty($RetValue.Value)) {
                Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', $RetValue.Raw)) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force;
                return $RetValue;
            }
            break;
        };
        $IcingaEnums.IcingaThresholdMethod.Greater {
            $RetValue.Value = $ConvertedValue[1];

            if ([string]::IsNullOrEmpty($RetValue.Value)) {
                Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', $RetValue.Raw)) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force;
                return $RetValue;
            }
            break;
        };
        $IcingaEnums.IcingaThresholdMethod.Between {
            $RetValue.StartRange = [decimal]$ConvertedValue[0];
            $RetValue.EndRange   = [decimal]$ConvertedValue[1];

            if ([string]::IsNullOrEmpty($RetValue.StartRange) -Or [string]::IsNullOrEmpty($RetValue.EndRange)) {
                Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', $RetValue.Raw)) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force;
                return $RetValue;
            }
            break;
        };
        $IcingaEnums.IcingaThresholdMethod.Outside {
            $RetValue.StartRange = [decimal]($ConvertedValue[0].Replace('@', ''));
            $RetValue.EndRange   = [decimal]$ConvertedValue[1];

            if ([string]::IsNullOrEmpty($RetValue.StartRange) -Or [string]::IsNullOrEmpty($RetValue.EndRange)) {
                Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', ($RetValue.Raw))) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force;
                return $RetValue;
            }
            break;
        };
    }

    if ([string]::IsNullOrEmpty($Value) -eq $FALSE -And $Value.Contains(':') -eq $FALSE) {
        if ((Test-Numeric $Value)) {
            $RetValue.Threshold = [decimal]$Value;
            $RetValue.Value     = [decimal]$Value;
            return $RetValue;
        }
    }

    # Always ensure we are using correct digits
    $Value              = ([string]$Value).Replace(',', '.');
    $RetValue.Threshold = $Value;

    return $RetValue;
}
