<?php
require_once realpath(__DIR__) . '/../vendor/autoload.php';

use KubernetesClient\Config;
use KubernetesClient\Client;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use AutoScaler\Kubernetes\Nodes;
use AutoScaler\Scale;
use AutoScaler\Counter;
use AutoScaler\Linode\Lke\Pool;

set_time_limit(0);
declare(ticks = 1);
pcntl_signal(SIGINT, function () {
    exit(0);
});

/** Config **/
$autoscaleTrigger = getenv('AUTOSCALE_TRIGGER'); // memory or cpu for memory percentage or CPU percentage
$autoscaleTriggerType = getenv('AUTOSCALE_TRIGGER_TYPE'); // requested or used
$autoscaleUpAtUtilisationPercent = getenv('AUTOSCALE_UP_PERCENTAGE');
$autoscaleDownAtUtilisationPercent = getenv('AUTOSCALE_DOWN_PERCENTAGE');
$autoscaleUpRequestPercentage = getenv('AUTOSCALE_RESOURCE_REQUEST_UP_PERCENTAGE');
$autoscaleDownRequestPercentage = getenv('AUTOSCALE_RESOURCE_REQUEST_DOWN_PERCENTAGE');
$autoscaleQueryInterval = getenv('AUTOSCALE_QUERY_INTERVAL'); // Seconds. We'll call K8S after every interval to fetch node utilisation metrics
$autoscaleThresholdCount = getenv('AUTOSCALE_THRESHOLD_COUNT'); // Number of consecutive times the utilisation percentage should be greater than $autoscaleAtCpuUtilisationPercent or $autoscaleAtMemoryUtilisationPercent to autoscale.
$autoscaleNodesToAddOrRemovePerBreach = getenv('AUTOSCALE_NUMBER_OF_NODES');
$autoscaleWaitTimeBetweenScaling = getenv('AUTOSCALE_WAIT_TIME_AFTER_SCALING');
$linodePAT = getenv('LINODE_PERSONAL_ACCCESS_TOKEN');
$linodeLkeClusterId = getenv('LINODE_LKE_CLUSTER_ID');
$linodeLkeClusterPoolId = getenv('LINODE_LKE_CLUSTER_POOL_ID');
$linodeClusterPoolMinimumNodeCount = getenv('LINODE_LKE_CLUSTER_POOL_MINIMUM_NODES');
/***/

$logger = new Logger('AUTOSCALER');
$logger->pushHandler(new StreamHandler('php://stdout', Logger::INFO));
$logger->info('Created Logger...');

$logger->info('Building Kubernetes Config...');
$config = Config::BuildConfigFromFile();

$logger->info('Creating Kubernetes Client');
$client = new Client($config);

$logger->info('Create Linode API Client...');
$linode = new Pool($linodePAT, $linodeLkeClusterId, $linodeLkeClusterPoolId);

$logger->info('Start Monitoring Cluster...');
$logger->info('Autoscale Trigger is set to: ' . $autoscaleTrigger);
$logger->info('Autoscale Trigger Type is set to: ' . $autoscaleTriggerType);
$logger->info('Create Counter...');
$counter = new Counter($autoscaleThresholdCount);

while (true) {
    try {
        $logger->info('Get Nodes\' and Metrics details...');
        $nodes = new Nodes($client);
        // Let's see first if we have the minimum number of nodes, if we don't scale up
        $currentNodesInPool = $linode->getNodeCount();
        if ($currentNodesInPool < $linodeClusterPoolMinimumNodeCount) {
            $logger->info('Current Nodes in LKE Pool: ' . $currentNodesInPool);
            $logger->info("Minimum nodes are set to $linodeClusterPoolMinimumNodeCount ...");
            $logger->info("Setting node count to $linodeClusterPoolMinimumNodeCount ...");
            $linode->updateNodeCount($linodeClusterPoolMinimumNodeCount);
            sleep($autoscaleWaitTimeBetweenScaling);
        }

        $usedPercentage = $autoscaleTrigger == 'cpu' ? $nodes->getUsedCpuPercent() : $nodes->getUsedMemoryPercent();
        $requestedPercentage = $autoscaleTrigger == 'cpu' ? $nodes->getRequestedCpuPercent() : $nodes->getRequestedMemoryPercent();
        $scale = new Scale($autoscaleUpAtUtilisationPercent, $autoscaleDownAtUtilisationPercent, $usedPercentage, $requestedPercentage, $autoscaleUpRequestPercentage, $autoscaleDownRequestPercentage, $autoscaleTriggerType);
        $logger->info(strtoupper($autoscaleTrigger) . ' Scale calculated', [
        'usedPercentage' => $usedPercentage,
        'scaleUpPercentage' => $autoscaleUpAtUtilisationPercent,
        'scaleDownPercentage' => $autoscaleDownAtUtilisationPercent,
        'requestedPercentage' => $requestedPercentage,
        'scaleUpRequestPercentage' => $autoscaleUpRequestPercentage,
        'scaleDownRequestPercentage' => $autoscaleDownRequestPercentage
        ]);
        if ($scale->scaleUp()) {
            $logger->info('Scale Count: Up');
            $counter->up();
        } elseif ($scale->scaleDown()) {
            $logger->info('Scale Count: Down');
            $counter->down();
        } else {
            $logger->info('Nothing to count...resetting counter');
            $counter->reset();
        }

        if ($counter->scaleUpCountBreached()) {
            $logger->alert('Counter Scale Up Count Breached. Size UP Cluster...', ['count' => $counter->count, 'threshold' => $counter->thresholdCount]);
            $logger->alert('Current Nodes in LKE Pool: ' . $currentNodesInPool);
            $logger->alert("Adding $autoscaleNodesToAddOrRemovePerBreach more node(s)...");
            $linode->updateNodeCount($currentNodesInPool + $autoscaleNodesToAddOrRemovePerBreach);
            $counter->reset();
            sleep($autoscaleWaitTimeBetweenScaling);
        }

        if ($counter->scaleDownCountBreached()) {
            $logger->alert('Counter Scale Down Count Breached. Size DOWN Cluster...', ['count' => $counter->count, 'threshold' => $counter->thresholdCount]);
            // Scale down if current nodes are greater than the minimum AND if the current nodes - the scale down number is greater than or equal to the minimum number
            if ($currentNodesInPool > $linodeClusterPoolMinimumNodeCount && ($currentNodesInPool - 1) >= $linodeClusterPoolMinimumNodeCount) {
                $logger->alert('Current Nodes in LKE Pool: ' . $currentNodesInPool);
                $logger->alert("Removing 1 node...");
                // Scale down only 1 node at a time, this is much safer than scaling down multiple nodes
                $linode->updateNodeCount($currentNodesInPool - 1);
                sleep($autoscaleWaitTimeBetweenScaling);
            }
            $logger->alert("Skip downsizing cluster because we are already at the minimum number ($linodeClusterPoolMinimumNodeCount) of nodes or scaling down by 1 will put us at less than the minimum number.");
            $counter->reset();
        }
    } catch (Exception $e) {
        $logger->error($e->getMessage());
    }

    sleep($autoscaleQueryInterval);
}







