<?php

/**
 * Class CFHostByClass
 */
class CFHostByClass
{
    private $userName;
    private $hardClassesOnly;
    private $limit = 10000;
    private $inventoryLimit = 500;
    private $hostsCount;
    private $hostsKeyWrapper = 'hosts';
    private $withInventory;
    private $inventoryFile;

    public function __construct($userName, $inventoryFile = false, $withInventory = false, $hardClassesOnly = true)
    {
        $this->userName = $userName;
        $this->hardClassesOnly = $hardClassesOnly;
        $this->inventoryFile = $inventoryFile;
        $this->withInventory = $withInventory;
    }

    public function getHosts($classExpression = null)
    {
        $where = $this->hardClassesOnly === true ? "WHERE contexts.metatags && '{hardclass}'::text[]" : '';
        if ($this->inventoryFile && $this->withInventory) { // put inventory directly to hosts when inventoryFile and withInventory
            $select = "SELECT contexts.contextname, 
                              json_agg(json_build_object(hosts.hostname, 
                              json_build_object('CFEngineInventory', inventory_new.values))) AS hostnames
                       FROM contexts
                       LEFT JOIN hosts ON hosts.hostkey = contexts.hostkey
                       LEFT JOIN inventory_new ON inventory_new.hostkey = contexts.hostkey";
        } elseif ($this->inventoryFile) { // return hosts with an empty object value in case of inventoryFile and no inventories
            $select = "SELECT contexts.contextname, 
                              json_agg(json_build_object(hosts.hostname, '{}'::json)) AS hostnames
                       FROM contexts
                       LEFT JOIN hosts ON hosts.hostkey = contexts.hostkey
                       LEFT JOIN inventory_new ON inventory_new.hostkey = contexts.hostkey";
        } else { // return hosts as a list by default when no inventoryFile
            $select = "SELECT contexts.contextname, array_agg(hosts.hostname) AS hostnames
                       FROM contexts
                       LEFT JOIN hosts ON hosts.hostkey = contexts.hostkey";
        }

        $sql = "$select $where GROUP BY contexts.contextname";

        $iterations = intdiv($this->getClassCount($classExpression), $this->limit) + 1;

        $result = [];
        while ($iterations != 0) {
            $offset = $this->limit * ($iterations - 1);
            $apiResult = json_decode(
                cfapi_query_post($this->userName, $sql, '', false, $offset, $this->limit, [$classExpression], []),
                JSON_OBJECT_AS_ARRAY
            );

            $this->prepareResult($result, $apiResult, $classExpression);
            $iterations--;
        }

        return $result;
    }

    protected function getClassCount($classExpression = null)
    {
        $where = $this->hardClassesOnly === true ? "WHERE contexts.metatags && '{hardclass}'::text[]" : '';
        $sql = "SELECT COUNT(DISTINCT contexts.contextname) FROM contexts $where";

        $apiResult = json_decode(
            cfapi_query_post($this->userName, $sql, '', false, -1, 1, [$classExpression], []),
            JSON_OBJECT_AS_ARRAY
        );

        if (!isset($apiResult['data'][0]['rows'][0][0])) {
            throw new Exception('Error while getting classes count CFHostByClass::getClassCount');
        }

        $this->hostsCount = (int) $apiResult['data'][0]['rows'][0][0];
        return $this->hostsCount;
    }

    protected function prepareResult(&$result, $data, $classExpression)
    {
        if (!isset($data['data'][0]['rows'])) {
            throw new Exception('Error while getting preparing result CFHostByClass::prepareResult');
        }

        $rows = $data['data'][0]['rows'];
        foreach ($rows as $row) {
            $value = $this->inventoryFile ?
                // convert array of objects to object
                array_reduce(json_decode($row[1], $associative = true), function ($reducer, $item) {
                    $key = array_key_first($item);
                    $reducer[$key] = $item[$key];
                    return $reducer;
                }, []) :
                $row[1];
            $result[$row[0]][$this->hostsKeyWrapper] = $value;
        }
    }

    public function getHostsInventories($classExpression = null)
    {
        $sql = 'SELECT "values" FROM inventory_new';
        $iterations = intdiv($this->hostsCount, $this->inventoryLimit) + 1;

        $result = [];
        while ($iterations != 0) {
            $offset = $this->limit * ($iterations - 1);
            $apiResult = json_decode(
                cfapi_query_post($this->userName, $sql, '', false, $offset, $this->limit, [$classExpression], []),
                JSON_OBJECT_AS_ARRAY
            );

            $this->prepareInventoryResult($result, $apiResult);
            $iterations--;
        }

        return $result;
    }

    public function prepareInventoryResult(&$result, $data)
    {
        if (!isset($data['data'][0]['rows'])) {
            throw new Exception('Error while preparing result CFHostByClass::prepareInventoryResult');
        }

        $rows = $data['data'][0]['rows'];
        foreach ($rows as $row) {
            $inventories = json_decode($row[0], true);
            if (isset($inventories['Host name'])) {
                $result[$inventories['Host name']]['CFEngine Inventory'] = $inventories;
            }
        }
    }
}
