<?php

class host_model extends Cf_Model
{
    private $hostResource = 'host';
    private $hostsResource = 'hosts';
    public $hostTimeInterval = 3600;
    public const AGENT_STATUS_FAIL = 'FAIL';

    private $cacheTTL;

    public function __construct()
    {
        $this->load->driver('cache', ['adapter' => 'file']);
        $this->cacheTTL = $this->config->item('five_minutes_cache_ttl');
    }

    // this is a copy of getHostByColorFromPromiseStatus, but we receive on
    public function getHostByColorFromPromiseStatusCount(
        $color,
        $username,
        $includes = [],
        $excludes = [],
        $rows = null,
        $page = null,
    ) {
        try {
            $this->load->model('advancedreports_model');
            $this->load->model('settings_rest_model');

            $settings = $this->settings_rest_model->get_app_settings();

            $timeCheckForBlueHost = getUtcTimestamp() - $settings['blueHostHorizon'];

            if ($color == 'red') {
                $sql =
                    "SELECT count(hostkey) as CountValue from (SELECT HostKey  FROM Promiseexecutions WHERE Promiseexecutions.promiseoutcome = 'NOTKEPT' GROUP BY hostkey) as tmp";
            } elseif ($color == 'green') {
                $sql = "SELECT count(hostkey) as countValue from
                         (
                         SELECT Promiseexecutions.HostKey
                                 FROM   Promiseexecutions
                                 WHERE hostkey  NOT IN (select hostkey from Promiseexecutions Where Promiseexecutions.promiseoutcome = 'NOTKEPT')
                                 GROUP BY HostKey
                         ) as tmp";
            } elseif ($color === 'blue') {
                $sql = sprintf(
                    "SELECT count(Hosts_view.HostKey) AS CountValue
                                FROM v_hosts Hosts_view
                                WHERE Hosts_view.lastreporttimestamp < to_timestamp('%s')",
                    $timeCheckForBlueHost,
                );
            } elseif ($color === 'black') {
                $sql = sprintf(
                    "SELECT count(HostKey) AS CountValue
                                FROM agentstatus
                                where agentstatus.lastagentexecutionstatus = '%s'",
                    self::AGENT_STATUS_FAIL,
                );
            } elseif ($color == 'blackblue') {
                $sql = sprintf(
                    "SELECT count(*) AS CountValue  FROM (
                                                SELECT Hosts_view.HostKey
                                                FROM v_hosts Hosts_view
                                                WHERE Hosts_view.lastreporttimestamp < to_timestamp('%s')
                                            UNION
                                                SELECT HostKey
                                                FROM agentstatus
                                                WHERE agentstatus.lastagentexecutionstatus = '%s'
                                            ) as blackblue ",
                    $timeCheckForBlueHost,
                    self::AGENT_STATUS_FAIL,
                );
            } elseif ($color == 'all') {
                $sql = 'SELECT count(Hosts.HostKey)  AS CountValue FROM Hosts';
            }

            if ($sql == '') {
                return [];
            }

            //$skip = ($page - 1) * $rows;
            $data = $this->advancedreports_model->runQuery($username, $sql, '', '', 0, 0, $includes, $excludes);
            return (int) $data['rows'][0][0];
        } catch (Exception $e) {
            generate_errormessage($e);
            throw $e;
        }
    }

    public function getCombinedStatusForRedAndGreen($username, $includes = [], $excludes = [])
    {
        try {
            $this->load->model('advancedreports_model');
            $sql = "SELECT red.count as red, total.count as total from (
                SELECT count(hostkey)
                        FROM (
                        SELECT HostKey  FROM Promiseexecutions WHERE Promiseexecutions.promiseoutcome = 'NOTKEPT' GROUP BY hostkey
                        ) as tmp
                ) as red,
                (
                SELECT count(hostkey)
                        FROM v_hosts
                ) as total
                ";
            $data = $this->advancedreports_model->runQuery($username, $sql, '', '', 0, 0, $includes, $excludes);
            $red = (int) $data['rows'][0][0];
            $total = (int) $data['rows'][0][1];
            $green = $total - $red;
            $result = ['red' => $red, 'green' => $green, 'total' => $total];
            return $result;
        } catch (Exception $e) {
            throw $e;
        }
    }

    public function getCombinedStatusForBlacKAndBlue($username, $includes = [], $excludes = [])
    {
        $this->load->model('advancedreports_model');
        $this->load->model('settings_rest_model');

        $settings = $this->settings_rest_model->get_app_settings();

        $timeCheckForBlueHost = getUtcTimestamp() - $settings['blueHostHorizon'];

        try {
            $this->load->model('advancedreports_model');

            $blueSql = sprintf(
                "SELECT count(Hosts_view.HostKey)
                                FROM v_hosts Hosts_view
                                WHERE Hosts_view.lastreporttimestamp < to_timestamp('%s')",
                $timeCheckForBlueHost,
            );

            $blackSql = sprintf(
                "SELECT count(HostKey)
                                FROM agentstatus
                                where agentstatus.lastagentexecutionstatus = '%s'",
                self::AGENT_STATUS_FAIL,
            );

            $sql = sprintf(
                'SELECT blue.count as blue, black.count as black, total.count as total from (
                      %s
                    ) as blue,
                (
                    %s
                ) as black,
                (
                SELECT count(hostkey)
                        FROM v_hosts
                ) as total
                ',
                $blueSql,
                $blackSql,
            );
            $data = $this->advancedreports_model->runQuery($username, $sql, '', '', 0, 0, $includes, $excludes);
            return $data['rows'][0];
        } catch (Exception $e) {
            throw $e;
        }
    }

    public function getHostByColorFromPromiseStatus(
        $color,
        $username,
        $includes = [],
        $excludes = [],
        $rows = null,
        $page = null,
    ) {
        try {
            $this->load->model('advancedreports_model');
            $this->load->model('settings_rest_model');

            $settings = $this->settings_rest_model->get_app_settings();

            $timeCheckForBlueHost = getUtcTimestamp() - $settings['blueHostHorizon'];

            if ($color == 'red') {
                $sql = "SELECT DISTINCT Promiseexecutions.HostKey, Hosts.HostName, Hosts.IPAddress
                                FROM Promiseexecutions
                                        INNER JOIN HOSTS on HOSTS.hostkey = Promiseexecutions.hostkey
                                WHERE Promiseexecutions.promiseoutcome = 'NOTKEPT'";
            } elseif ($color == 'green') {
                $sql = "SELECT
                DISTINCT Hosts.HostKey,Hosts.HostName, Hosts.IPAddress
                FROM Hosts
                WHERE
                Hosts.hostkey NOT IN (
                    select distinct Promiseexecutions.hostkey
                    FROM Promiseexecutions
                    WHERE Promiseexecutions.promiseoutcome = 'NOTKEPT'
                    )";
            } elseif ($color === 'blue') {
                $sql = sprintf(
                    "SELECT DISTINCT Hosts.HostKey,Hosts.HostName, Extract(epoch from Hosts.lastreporttimestamp AT TIME ZONE 'UTC')::bigint
                                FROM Hosts
                                where Hosts.lastreporttimestamp < to_timestamp('%s')",
                    $timeCheckForBlueHost,
                );
            } elseif ($color === 'black') {
                $sql = sprintf(
                    "SELECT DISTINCT Hosts.HostKey,Hosts.HostName, Extract(epoch from Hosts.lastreporttimestamp AT TIME ZONE 'UTC')::bigint
                                FROM agentstatus INNER JOIN Hosts on Hosts.hostkey = agentstatus.hostkey
                                where agentstatus.lastagentexecutionstatus = '%s'",
                    self::AGENT_STATUS_FAIL,
                );
            } elseif ($color == 'all') {
                $sql = 'SELECT DISTINCT Hosts.HostKey,Hosts.HostName, Hosts.IPAddress FROM Hosts';
            }

            if ($sql == '') {
                return [];
            }

            $skip = ($page - 1) * $rows;
            $data = $this->advancedreports_model->runQuery($username, $sql, '', '', $skip, $rows, $includes, $excludes);
            return $data;
        } catch (Exception $e) {
            generate_errormessage($e);
            throw $e;
        }
    }

    /**
     * Returns count for a given host include/exclude context
     *
     * !!!! WARNING !!!!!!!!
     *  this function uses hidden view - v_hosts
     *
     * @param <array> $includes
     * @param <array> $excludes
     * @return <int>
     */

    public function getHostCountByContext(
        $username,
        $includes = [],
        $excludes = [],
        $fetchDataViaAPI = true,
        $inventoryFilter = [],
        $hostFilter = [],
    ) {
        $sql = 'SELECT count(DISTINCT hostkey) AS countVal FROM v_hosts';

        if (sizeof($inventoryFilter) || sizeof($hostFilter)) {
            $sql .= ' INNER JOIN inventory_cte ON inventory_cte.filtered_hostkey = v_hosts.hostkey';
        }

        $this->load->model('advancedreports_model');

        $result = $this->advancedreports_model->runQuery(
            $username,
            $sql,
            '',
            '',
            0,
            0,
            $includes,
            $excludes,
            inventoryFilter: $inventoryFilter,
            hostFilter: $hostFilter,
            fetchDataViaAPI: $fetchDataViaAPI,
        );

        return $result['rows'][0][0];
    }

    public function getSharedGroupsByHostkey($hostkey)
    {
        return json_decode($this->getRestClient()->get('/host-groups/shared/by-hostkey/' . $hostkey));
    }

    /**
     * @param array $includes
     * @param array $excludes
     * @param bool $returnCountOnly
     * @param int $page
     * @param int $count
     * @return mixed
     * @throws Exception
     */
    public function getHostListByContext(
        $includes = [],
        $excludes = [],
        $returnCountOnly = false,
        $page = 1,
        $count = 50,
    ) {
        try {
            $contextinclude = implode(',', $includes);
            $contextexclude = implode(',', $excludes);

            if ($returnCountOnly) {
                // we will get the current count from the meta data (total) returned from API.
                $count = 1;
            }

            $url = sprintf('/%s/?count=%s&page=%s&', $this->hostResource, $count, $page);

            $data = $this->getRestClient()->post($url, [
                'context-include' => $contextinclude,
                'context-exclude' => $contextexclude,
            ]);

            $hostsList = json_decode($data, true);

            if ($returnCountOnly) {
                return $hostsList['meta']['total'];
            }

            return $hostsList['data'];
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                $e->getMessage() . ' File: ' . $e->getFile() . ' line:' . $e->getLine(),
            );
            throw $e;
        }
    }

    /**
     *
     * @param type $username
     * @param type $hostkey
     */
    public function getReverseIpLookUpName($username, $hostkey)
    {
        try {
            return cfpr_get_reverse_ip_lookup_name($username, $hostkey);
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                $e->getMessage() . ' File:' . $e->getFile() . ' line:' > $e->getLine(),
            );
            throw $e;
        }
    }

    /**
     * Get the host name for the supplied host key
     * @param  string $key SHA HOSTKEY
     * @return string Name of the host
     */
    public function getHostName($username, $key)
    {
        try {
            $info = $this->getHostInfo($username, $key);
            return $info['hostname'];
        } catch (Exception $e) {
            log_message(log_level_for_exception($e), $e->getMessage());
            throw $e;
        }
    }

    /**
     *
     * @param type $username
     * @param type $hostkey
     * @return type String Ip address for given hostkey
     */
    public function getHostIp($username, $key)
    {
        try {
            $info = $this->getHostInfo($username, $key);
            return $info['ip'];
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                $e->getMessage() . ' File:' . $e->getFile() . ' line:' . $e->getLine(),
            );
            throw $e;
        }
    }

    /**
     * Get host found
     * @param type $username
     * @param type $hostkey
     * @return type
     * @throws Exception
     */
    public function getHostFound($username, $hostkey)
    {
        try {
            $this->getRestClient()->get('/host/' . $hostkey);
        } catch (Exception $e) {
            return false;
        }
        return true;
    }

    /**
     * Get host info
     * @param type $username
     * @param type $hostkey
     * @return type
     * @throws Exception
     */
    public function getHostInfo($username, $key, $extraData = false)
    {
        try {
            $returnData = [];
            $queryToGetHostData = "SELECT lastseenhosts.remotehostip as ip,
                                  Extract(epoch from v_hosts.lastreporttimestamp)::bigint as lastseen,
                                  Extract(epoch from v_hosts.firstreporttimestamp)::bigint as firstseen,
                                  v_hosts.iscallcollected,
                                  Extract(epoch from agentstatus.lastagentlocalexecutiontimestamp)::bigint as lastagentlocalexecutiontimestamp,
                                  agentstatus.agentexecutioninterval,
                                  (inv.\"values\" ->> 'Host name'::text) AS hostname
                                  FROM v_hosts
                                  LEFT JOIN lastseenhosts ON lastseenhosts.remotehostkey = v_hosts.hostkey
                                  LEFT JOIN agentstatus ON agentstatus.hostkey = v_hosts.hostkey
                                  LEFT JOIN inventory_new inv ON inv.hostkey = v_hosts.hostkey
                                  WHERE v_hosts.hostkey = '%s'
                                  ORDER BY lastseentimestamp
                                  DESC LIMIT 1";

            $hostData = json_decode(
                $this->getRestClient()->post('/query', [
                    'query' => vsprintf($queryToGetHostData, is_array($key) ? $key : [$key]),
                ]),
                JSON_OBJECT_AS_ARRAY,
            );
            if ($hostData && is_array($hostData)) {
                $hostData = $hostData['data'][0]['rows'][0];
                if ($hostData && is_array($hostData)) {
                    $returnData = [
                        'ip' => $hostData[0],
                        'lastreport' => $hostData[1],
                        'firstreport' => $hostData[2],
                        'iscallcollected' => $hostData[3] == 't',
                        'lastAgentExecutionTime' => $hostData[4],
                        'agentexecutioninterval' => $hostData[5],
                        'hostname' => $hostData[6],
                    ];
                }
            }

            if (is_array($returnData)) {
                $additionalData = [];
                // cache key contains hostkey and last report timestampt
                // this means that data will be automatically refreshed when last report time changed
                // otherwise cache will live for 5 minutes
                $cacheKey = "host_additional_data_{$key}_{$returnData['lastreport']}";
                if ($extraData === true) {
                    if (!($additionalData = $this->cache->get($cacheKey))) {
                        $additionalData = $this->getHostDetailFromSystemVariableReport($username, $key);
                        $this->cache->save($cacheKey, $additionalData, $this->cacheTTL);
                    }
                }
                return array_merge($additionalData, $returnData);
            }
        } catch (Exception $e) {
            log_message(log_level_for_exception($e), $e->getMessage());
            throw $e;
        }
    }

    /**
     *
     * @param type $username
     * @param type $hostkey
     * @param type $scope
     * @param type $lval
     * @return type depends on the variable type
     */
    public function getHostVariable($username, $hostkey, $scope, $lval)
    {
        try {
            $promisesfilter_mode = 'ALL';
            $skip = null;
            $rows = null;
            $includes = [];
            $excludes = [];

            $this->load->model('report_model');
            $inclist = ['PK_' . str_replace('=', '_', $hostkey)];
            $detailData = $this->report_model->getVariablesReport(
                $username,
                $scope,
                $lval,
                null,
                null,
                $inclist,
                [],
                $rows = 1,
                $page_number = 1,
                false,
            );
            $data = [];
            if (is_array($detailData) && !empty($detailData) && !empty($detailData['rows'])) {
                $row = $detailData['rows'][0];
                return $row[2];
            }
            return null;
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                $e->getMessage() . ' File:' . $e->getFile() . ' line:' . $e->getLine(),
            );
            throw $e;
        }
    }

    /**
     *
     * @param type $username
     * @param type $hostkey
     * @return type string
     */
    public function getNetworkSpeed($username, $hostkey)
    {
        return false;
    }

    /**
     *
     * @param type $key
     * deletes the host for supplied hostkey
     */
    public function deleteHost($key)
    {
        try {
            $data = $this->getRestClient()->delete('/' . $this->hostResource . '/' . $key);
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                $e->getMessage() . ' File: ' . $e->getFile() . ' line:' . $e->getLine(),
            );
            throw $e;
        }
    }

    public function getHostDetailFromSystemVariableReport($username, $hostkey)
    {
        $this->load->model('report_model');

        // TODO: OS type and Kernel Release attributes should be renamed to Kernel and Kernel Release respectively:
        $select = [
            'OS',
            'OS type',
            'Kernel Release',
            'Architecture',
            'CPU logical cores',
            'CFEngine version',
            'Host name',
            'MAC addresses',
            'Interfaces',
            'IPv4 addresses',
        ];
        $detailData = $this->report_model->getHostVariablesData($hostkey, $select);

        if ($detailData && is_array($detailData)) {
            $data = [
                'System' => [
                    'OS' => $detailData[0],
                    'Kernel' => $detailData[1],
                    'Kernel Release' => $detailData[2],
                    'Architecture' => $detailData[3],
                    'CPUs' => $detailData[4],
                ],

                'CFEngine' => [
                    'Version' => $detailData[5],
                ],

                'Hostnames' => [
                    'Fqhost' => $detailData[6],
                ],

                'Network' => [
                    'mac' => '{' . $detailData[7] . '}',
                    'interface' => '{' . $detailData[8] . '}',
                    'address' => '{' . $detailData[9] . '}',
                ],
            ];
        } else {
            $data = [];
        }

        // Remove empty values
        foreach ($data as $key => $array) {
            $data[$key] = array_filter($array, function ($element) {
                return $element != null;
            });
        }

        return $data;
    }

    public function getHostVariableDataByName($hostkey, $variableName)
    {
        try {
            $this->load->model('report_model');
            $detailData = $this->report_model->getHostVariablesData($hostkey, [$variableName]);

            return $detailData[0];
        } catch (Exception $e) {
            log_message(
                log_level_for_exception($e),
                $e->getMessage() . ' File:' . $e->getFile() . ' line:' . $e->getLine(),
            );
            throw $e;
        }
    }

    public function searchHost($username, $query = '', $limit = 7, $offset = 0)
    {
        try {
            if (!$username) {
                throw new InvalidArgumentException('Wrong username');
            }

            $this->load->model('advancedreports_model');
            $sql = "SELECT 
                        hosts.hostkey,
                        hosts.hostname,
                        hosts.ipaddress,
                        hosts.lastreporttimestamp,
                        hosts.firstreporttimestamp,
                        inventory_new.values->>'MAC addresses' AS macs,
                        inventory_new.values->>'OS' AS os
                    FROM hosts 
                    LEFT JOIN inventory_new ON hosts.hostkey = inventory_new.hostkey
                    INNER JOIN inventory_cte ON inventory_cte.filtered_hostkey = hosts.hostkey";

            return $this->advancedreports_model->runQuery(
                username: $username,
                SQLString: $sql,
                skip: $offset,
                limit: $limit,
                inventoryFilter: [advancedreports_model::INVENTORY_SEARCH_QUERY_ATTRIBUTE => ['matches' => $query]],
            );
        } catch (Exception $e) {
            log_message(log_level_for_exception($e), $e->getMessage());
            throw $e;
        }
    }

    public function searchHostByInventory($username, $inventoryFilters = [], $limit = 7, $offset = 0)
    {
        try {
            if (!$username) {
                throw new InvalidArgumentException('Missing username');
            }

            $this->load->model('advancedreports_model');
            $sql = "SELECT hostkey, values->>'Host name' as hostname, values->>'IPv4 addresses' as ip, values->>'MAC addresses' as mac
                    FROM inventory_new
                    INNER JOIN inventory_cte ON inventory_cte.filtered_hostkey = inventory_new.hostkey";

            return $this->advancedreports_model->runQuery(
                $username,
                $sql,
                '',
                '',
                $offset,
                $limit,
                [],
                [],
                $inventoryFilters,
            );
        } catch (Exception $e) {
            log_message(log_level_for_exception($e), $e->getTraceAsString());
            throw $e;
        }
    }

    public function getPolicyExecutionData($username, $hostKey)
    {
        try {
            $this->load->model('advancedreports_model');
            $sql =
                "WITH subquery
                         AS (SELECT eventname, Max(checktimestamp) AS mt
                             FROM   benchmarkslog
                             GROUP  BY eventname
                             )
                    SELECT DISTINCT ON (benchmarkslog.eventname)
                        substring(benchmarkslog.eventname, '''(.*?)''') as \"Policy file\",
                        round(benchmarkslog.lastvalue, 2)  as \"Last (sec.)\",
                        round(benchmarkslog.averagevalue, 2)  as \"Average (sec.) \",
                        round(benchmarkslog.standarddeviation, 2)  as \"Deviation (sec.)\",
                        benchmarkslog.checktimestamp as \"Last execution time\"
                    FROM   benchmarkslog
                    INNER JOIN subquery ON subquery.mt = benchmarkslog.checktimestamp
                    WHERE  hostkey = '" .
                $this->db->escape_str($hostKey) .
                "'
                    AND benchmarkslog.eventname LIKE 'CFEngine Execution%'";

            return $this->advancedreports_model->runQuery($username, $sql);
        } catch (Exception $e) {
            log_message(log_level_for_exception($e), $e->getMessage());
            return null;
        }
    }

    public function getUnfilteredHostsCount()
    {
        $data = $this->getRestClient()->get('/unfiltered-hosts-count');
        return json_decode($data, JSON_OBJECT_AS_ARRAY);
    }

    public function restoreDeletedHost($hostkey)
    {
        $data = $this->getRestClient()->post("{$this->hostsResource}/restore-deleted/$hostkey");
        return json_decode($data, JSON_OBJECT_AS_ARRAY);
    }

    public function deletePermanently($hostkey)
    {
        $data = $this->getRestClient()->delete("{$this->hostsResource}/delete-permanently/$hostkey");
        return json_decode($data, JSON_OBJECT_AS_ARRAY);
    }

    public function getTotalHostsCount($username)
    {
        $this->load->model('advancedreports_model');
        $data = $this->advancedreports_model->runQuery($username, 'SELECT count(hostkey) FROM hosts');
        [[$total]] = $data['rows'];
        return intval($total);
    }
}
