<?php

/*
 * Copyright 2005 - 2025 Centreon (https://www.centreon.com/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * For more information : contact@centreon.com
 *
 */

require_once _CENTREON_PATH_ . '/www/class/centreonDB.class.php';
require_once __DIR__ . '/webService.class.php';

/**
 * Class
 *
 * @class CentreonSubmitResults
 * @description Class for provide the webservice for submit a status for a service or host
 */
class CentreonSubmitResults extends CentreonWebService
{
    /** @var string The path to the centcore pipe file */
    protected $centcoreFile;

    /** @var bool If the file pipe is open */
    protected $pipeOpened = false;

    /** @var mixed The file descriptor of the centcore pipe */
    protected $fh;

    /** @var CentreonDB The database connection to centreon_storage (realtime database) */
    protected $pearDBC;

    /** @var array The cache for relation between hosts and pollers */
    protected $pollerHosts = [];

    /** @var array The cache for relation between hosts and services */
    protected $hostServices;

    /** @var array The list of accepted status */
    protected $acceptedStatus = ['host' => [0, 1, 2, 'up', 'down', 'unknown'], 'service' => [0, 1, 2, 3, 'ok', 'warning', 'critical', 'unknown']];

    /** @var array The match between status string and number */
    protected $convertStatus = ['host' => ['up' => 0, 'down' => 1, 'unknown' => 2], 'service' => ['ok' => 0, 'warning' => 1, 'critical' => 2, 'unknown' => 3]];

    /** @var string The rejex for validate perfdata */
    protected $perfDataRegex = "/(('([^'=]+)'|([^'= ]+))=[0-9\.-]+[a-zA-Z%\/]*(;[0-9\.-]*){0,4}[ ]?)+/";

    /**
     * CentreonSubmitResults constructor
     */
    public function __construct()
    {
        parent::__construct();
        if (is_dir(_CENTREON_VARLIB_ . '/centcore')) {
            $this->centcoreFile = _CENTREON_VARLIB_ . '/centcore/' . microtime(true) . '-externalcommand.cmd';
        } else {
            $this->centcoreFile = _CENTREON_VARLIB_ . '/centcore.cmd';
        }
        $this->pearDBC = new CentreonDB('centstorage');
        $this->getPollers();
    }

    /**
     * Entry point for submit a passive check result
     *
     * @throws PDOException
     * @throws RestBadRequestException
     * @throws RestPartialContent
     * @return array[]
     */
    public function postSubmit()
    {
        $this->getHostServiceInfo();

        $results = [];
        $hasError = false;

        if (isset($this->arguments['results']) && is_array($this->arguments['results'])) {
            if ($this->arguments['results'] !== []) {
                if ($this->pipeOpened === false) {
                    $this->openPipe();
                }
                foreach ($this->arguments['results'] as $data) {
                    try {
                        // Validate the list of arguments
                        // Required fields
                        if (! isset($data['host']) || $data['host'] === ''
                            || ! isset($data['status']) || ! isset($data['updatetime']) || ! isset($data['output'])) {
                            throw new RestBadRequestException('Missing argument.');
                        }

                        // Validate is the host and service exists in poller
                        if (! isset($this->pollerHosts['name'][$data['host']])) {
                            throw new RestNotFoundException('The host is not present.');
                        }
                        if (isset($data['service']) && $data['service'] !== ''
                            && ! $this->hostServices[$data['host']][$data['service']]) {
                            throw new RestNotFoundException('The service is not present.');
                        }

                        // Validate status format
                        $status = strtolower($data['status']);
                        if (is_numeric($status)) {
                            $status = (int) $status;
                        }
                        if (isset($data['service']) && $data['service'] !== '') {
                            if (! in_array($status, $this->acceptedStatus['service'], true)) {
                                throw new RestBadRequestException('Bad status word.');
                            }
                            if (! is_numeric($status)) {
                                $status = $this->convertStatus['service'][$status];
                            }
                        } else {
                            if (! in_array($status, $this->acceptedStatus['host'], true)) {
                                throw new RestBadRequestException('Bad status word.');
                            }
                            if (! is_numeric($status)) {
                                $status = $this->convertStatus['host'][$status];
                            }
                        }
                        $data['status'] = $status;

                        // Validate timestamp format
                        if (! is_numeric($data['updatetime'])) {
                            throw new RestBadRequestException('The timestamp is not a integer.');
                        }

                        if (isset($data['perfdata'])) {
                            if ($data['perfdata'] !== '' && ! preg_match($this->perfDataRegex, $data['perfdata'])) {
                                throw new RestBadRequestException('The format of performance data is not valid.');
                            }
                        } else {
                            $data['perfdata'] = '';
                        }

                        // Execute the command
                        if (! $this->sendResults($data)) {
                            throw new RestInternalServerErrorException('Error during send command to CentCore.');
                        }
                        $results[] = [
                            'code' => 202,
                            'message' => 'The status send to the engine',
                        ];
                    } catch (Exception $error) {
                        $hasError = true;
                        $results[] = ['code' => $error->getCode(), 'message' => $error->getMessage()];
                    }
                }
                $this->closePipe();
            }
            if ($hasError) {
                throw new RestPartialContent(json_encode(['results' => $results]));
            }

            return ['results' => $results];
        }

        throw new RestBadRequestException('Bad arguments - Cannot find result list');
    }

    /**
     * Authorize to access to the action
     *
     * @param string $action The action name
     * @param CentreonUser $user The current user
     * @param bool $isInternal If the api is call in internal
     * @return bool If the user has access to the action
     */
    public function authorize($action, $user, $isInternal = false)
    {
        return (bool) (
            parent::authorize($action, $user, $isInternal)
            || ($user && $user->hasAccessRestApiRealtime())
        );
    }

    /**
     * Load the cache for pollers/hosts
     *
     * @throws PDOException
     * @return void
     */
    private function getPollers(): void
    {
        if (! isset($this->pollerHosts) || count($this->pollerHosts) === 0) {
            $query = 'SELECT h.host_id, h.host_name, ns.nagios_server_id AS poller_id '
                . 'FROM host h, ns_host_relation ns '
                . 'WHERE host_host_id = host_id '
                . 'AND h.host_activate = "1" '
                . 'AND h.host_register = "1"';
            $dbResult = $this->pearDB->query($query);
            $this->pollerHosts = ['name' => [], 'id' => []];
            while ($row = $dbResult->fetchRow()) {
                $this->pollerHosts['id'][$row['host_id']] = $row['poller_id'];
                $this->pollerHosts['name'][$row['host_name']] = $row['poller_id'];
            }
            $dbResult->free();
        }
    }

    /**
     * Load the cache for hosts/services
     *
     * @throws PDOException
     * @return void
     */
    private function getHostServiceInfo(): void
    {
        if (! isset($this->hostServices)) {
            $query = 'SELECT name, description '
                . 'FROM hosts h, services s '
                . 'WHERE h.host_id = s.host_id '
                    . 'AND h.enabled = 1 '
                    . 'AND s.enabled = 1 ';
            $dbResult = $this->pearDBC->query($query);
            $this->hostServices = [];
            while ($row = $dbResult->fetchRow()) {
                if (! isset($this->hostServices[$row['name']])) {
                    $this->hostServices[$row['name']] = [];
                }
                $this->hostServices[$row['name']][$row['description']] = 1;
            }
            $dbResult->free();
        }
    }

    /**
     * Open the centcore pipe file
     *
     * @throws RestBadRequestException
     * @return void
     */
    private function openPipe(): void
    {
        if ($this->fh = @fopen($this->centcoreFile, 'a+')) {
            $this->pipeOpened = true;
        } else {
            throw new RestBadRequestException("Can't open centcore pipe");
        }
    }

    /**
     * Close the centcore pipe file
     *
     * @return void
     */
    private function closePipe(): void
    {
        fclose($this->fh);
        $this->pipeOpened = false;
    }

    /**
     * Write into the centcore pipe filr
     *
     * @param $string
     *
     * @return bool
     */
    private function writeInPipe($string)
    {
        if ($this->pipeOpened === false) {
            return false;
        }

        if ($string != '') {
            fwrite($this->fh, $string . "\n");
        }

        return true;
    }

    /**
     * Send the data to CentCore
     *
     * @param array $data
     *
     * @throws RestBadRequestException
     * @return bool
     */
    private function sendResults($data)
    {
        if (! isset($this->pollerHosts['name'][$data['host']])) {
            throw new RestBadRequestException("Can't find poller_id for host: " . $data['host']);
        }

        if (isset($data['service']) && $data['service'] !== '') {
            // Services update
            $command = $data['host'] . ';' . $data['service'] . ';' . $data['status'] . ';'
                . $data['output'] . '|' . $data['perfdata'];

            // send data
            return $this->writeInPipe('EXTERNALCMD:' . $this->pollerHosts['name'][$data['host']]
                . ':[' . $data['updatetime'] . '] PROCESS_SERVICE_CHECK_RESULT;' . $command);
        }
        // Host Update
        $command = $data['host'] . ';' . $data['status'] . ';' . $data['output'] . '|' . $data['perfdata'];

        // send data
        return $this->writeInPipe('EXTERNALCMD:' . $this->pollerHosts['name'][$data['host']]
            . ':[' . $data['updatetime'] . '] PROCESS_HOST_CHECK_RESULT;' . $command);

    }
}
