<?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
 *
 */

use Adaptation\Database\Connection\Collection\QueryParameters;
use Adaptation\Database\Connection\Enum\QueryParameterTypeEnum;
use Adaptation\Database\Connection\ValueObject\QueryParameter;

require_once _CENTREON_PATH_ . '/www/class/centreonDB.class.php';
require_once _CENTREON_PATH_ . '/www/class/centreonGraphNg.class.php';
require_once _CENTREON_PATH_ . '/www/class/centreonGraphService.class.php';
require_once _CENTREON_PATH_ . '/www/class/centreonGraphPoller.class.php';
require_once _CENTREON_PATH_ . '/www/class/centreonGraphStatus.class.php';
require_once _CENTREON_PATH_ . '/www/include/common/sqlCommonFunction.php';
require_once __DIR__ . '/webService.class.php';

/**
 * Class
 *
 * @class CentreonMetric
 */
class CentreonMetric extends CentreonWebService
{
    /** @var CentreonDB */
    protected $pearDBMonitoring;

    /** @var string[] */
    protected $statusColors = [
        'ok' => '#88b917',
        'warning' => '#ff9a13',
        'critical' => '#e00b3d',
        'unknown' => 'gray',
    ];

    /**
     * CentreonMetric constructor
     */
    public function __construct()
    {
        parent::__construct();
        $this->pearDBMonitoring = new CentreonDB('centstorage');
    }

    /**
     * Get metric list
     *
     * @throws Exception
     * @return array
     */
    public function getList()
    {
        global $centreon;

        $queryValues = [];
        $queryValues['name'] = isset($this->arguments['q']) ? '%' . (string) $this->arguments['q'] . '%' : '%%';

        $query = 'SELECT DISTINCT(`metric_name`)
            COLLATE utf8_bin as "metric_name", index_id FROM `metrics` as m, index_data i
            WHERE metric_name LIKE :name ';

        /**
         * If ACLs on, then only return metrics linked to services that the user can see.
         */
        if (! $centreon->user->admin) {
            $acl = new CentreonACL($centreon->user->user_id, $centreon->user->admin);
            $query .= ' AND m.index_id = i.id AND i.service_id IN ('
                . $acl->getServicesString('ID', $this->pearDBMonitoring) . ') ';
        }

        $query .= ' ORDER BY `metric_name` COLLATE utf8_general_ci ';
        $stmt = $this->pearDBMonitoring->prepare($query);
        $stmt->bindParam(':name', $queryValues['name'], PDO::PARAM_STR);
        $dbResult = $stmt->execute();
        if (! $dbResult) {
            throw new Exception('An error occured');
        }

        $metrics = [];
        while ($row = $stmt->fetch()) {
            $metrics[] = ['id' => $row['metric_name'], 'text' => $row['metric_name']];
        }

        return $metrics;
    }

    /**
     * Get last metrics value by services or/and metrics
     *
     * @throws RestBadRequestException
     * @throws RestForbiddenException
     * @throws RestNotFoundException
     * @return array | null if arguments are not set
     */
    public function getLastMetricsData()
    {
        if (! isset($this->arguments['services']) && ! isset($this->arguments['metrics'])) {
            self::sendResult([]);
        }

        return $this->lastMetricsData(
            $this->arguments['services'] ?? '',
            $this->arguments['metrics'] ?? ''
        );
    }

    /**
     * Get metrics data by service or/and metrics
     *
     * @throws RestBadRequestException
     * @throws RestForbiddenException
     * @throws RestNotFoundException
     * @return array | null if arguments are not set
     */
    public function getMetricsData()
    {
        if (! isset($this->arguments['services']) && ! isset($this->arguments['metrics'])) {
            self::sendResult([]);
        }

        return $this->metricsData(
            $this->arguments['services'] ?? '',
            $this->arguments['metrics'] ?? ''
        );
    }

    /**
     * Get metrics datas for a service
     *
     * @throws RestBadRequestException
     * @throws RestForbiddenException
     * @throws RestNotFoundException
     * @return array
     */
    public function getMetricsDataByService()
    {
        if (! isset($this->arguments['ids'])) {
            self::sendResult([]);
        }

        // Get the list of service ID
        $ids = explode(',', $this->arguments['ids']);
        $result = [];

        if (isset($this->arguments['type']) && $this->arguments['type'] === 'ng') {
            foreach ($ids as $id) {
                $result[] = $this->serviceDatasNg($id);
            }
        } else {
            foreach ($ids as $id) {
                $result[] = $this->serviceDatas($id);
            }
        }

        return $result;
    }

    /**
     * Get metrics datas for a metric
     *
     * @throws RestBadRequestException
     * @throws RestForbiddenException
     * @throws RestNotFoundException
     * @return array
     */
    public function getMetricsDataByMetric()
    {
        if (! isset($this->arguments['ids'])) {
            self::sendResult([]);
        }

        // Get the list of service ID
        $ids = explode(',', $this->arguments['ids']);
        $result = [];

        if (isset($this->arguments['type']) && $this->arguments['type'] === 'ng') {
            foreach ($ids as $id) {
                [$hostId, $serviceId, $metricId] = explode('_', $id);
                $hostId = (int) $hostId;
                $serviceId = (int) $serviceId;
                $metricId = (int) $metricId;

                $result[] = $this->serviceDatasNg($hostId . '_' . $serviceId, $metricId);
            }
        } else {
            foreach ($ids as $id) {
                [$hostId, $serviceId, $metricId] = explode('_', $id);
                $hostId = (int) $hostId;
                $serviceId = (int) $serviceId;
                $metricId = (int) $metricId;

                $result[] = $this->serviceDatas($hostId . '_' . $serviceId, $metricId);
            }
        }

        return $result;
    }

    /**
     * Get the status for a service
     *
     * @throws PDOException
     * @throws RestBadRequestException
     * @throws RestForbiddenException
     * @throws RestNotFoundException
     * @throws RuntimeException
     * @return array
     */
    public function getStatusByService()
    {
        global $centreon;

        $userId = $centreon->user->user_id;
        $isAdmin = $centreon->user->admin;

        // Get ACL if user is not admin
        if (! $isAdmin) {
            $acl = new CentreonACL($userId, $isAdmin);
            $aclGroups = $acl->getAccessGroupsString();
        }

        // Validate options
        if (
            ! isset($this->arguments['start']) || ! is_numeric($this->arguments['start'])
            || ! isset($this->arguments['end']) || ! is_numeric($this->arguments['end'])
        ) {
            throw new RestBadRequestException('Bad parameters');
        }

        $start = $this->arguments['start'];
        $end = $this->arguments['end'];

        // Get the numbers of points
        $rows = 200;
        if (isset($this->arguments['rows'])) {
            if (! is_numeric($this->arguments['rows'])) {
                throw new RestBadRequestException('Bad parameters');
            }
            $rows = $this->arguments['rows'];
        }
        if ($rows < 10) {
            throw new RestBadRequestException('The rows must be greater as 10');
        }

        if (! isset($this->arguments['ids'])) {
            self::sendResult([]);
        }

        // Get the list of service ID
        $ids = explode(',', $this->arguments['ids']);
        $result = [];

        foreach ($ids as $id) {
            [$hostId, $serviceId] = explode('_', $id);
            if (
                ! is_numeric($hostId)
                || ! is_numeric($serviceId)
            ) {
                throw new RestBadRequestException('Bad parameters');
            }

            // Check ACL is not admin
            if (! $isAdmin) {
                $query = 'SELECT service_id '
                    . 'FROM centreon_acl '
                    . 'WHERE host_id = :hostId '
                    . 'AND service_id = :serviceId '
                    . 'AND group_id IN (' . $aclGroups . ')';

                $stmt = $this->pearDBMonitoring->prepare($query);
                $stmt->bindParam(':hostId', $hostId, PDO::PARAM_INT);
                $stmt->bindParam(':serviceId', $serviceId, PDO::PARAM_INT);
                $dbResult = $stmt->execute();
                if (! $dbResult) {
                    throw new Exception('An error occured');
                }

                if ($stmt->rowCount() === 0) {
                    throw new RestForbiddenException('Access denied');
                }
            }

            // Prepare graph
            try {
                // Get index data
                $indexData = CentreonGraphStatus::getIndexId($hostId, $serviceId, $this->pearDBMonitoring);
                $graph = new CentreonGraphStatus($indexData, $start, $end);
            } catch (Exception $e) {
                throw new RestNotFoundException('Graph not found');
            }

            $statusData = $graph->getData();

            // Get comments for this services
            $comments = [];
            $query = 'SELECT `value` FROM `options` WHERE `key` = "display_comment_chart"';
            $res = $this->pearDB->query($query);
            $row = $res->fetch();
            if ($row && $row['value'] === '1') {
                $queryComment = 'SELECT `entry_time`, `author`, `data` '
                    . 'FROM comments '
                    . 'WHERE host_id = :hostId '
                    . 'AND service_id = :serviceId '
                    . 'AND type = 2 '
                    . 'AND entry_type = 1 '
                    . 'AND deletion_time IS NULL '
                    . 'AND :start < entry_time '
                    . 'AND :end > entry_time';
                $stmt = $this->pearDBMonitoring->prepare($queryComment);
                $stmt->bindParam(':hostId', $hostId, PDO::PARAM_INT);
                $stmt->bindParam(':serviceId', $serviceId, PDO::PARAM_INT);
                $stmt->bindParam(':start', $start, PDO::PARAM_INT);
                $stmt->bindParam(':end', $end, PDO::PARAM_INT);
                $dbResult = $stmt->execute();
                if (! $dbResult) {
                    throw new Exception('An error occured');
                }

                while ($row = $stmt->fetch()) {
                    $comments[] = ['author' => $row['author'], 'comment' => $row['data'], 'time' => $row['entry_time']];
                }
            }
            $result[] = ['service_id' => $id, 'data' => ['status' => $statusData, 'comments' => $comments], 'size' => $rows];
        }

        return $result;
    }

    /**
     * Get metrics Data by poller
     *
     * @throws RestBadRequestException
     * @throws RestNotFoundException
     * @throws RuntimeException
     * @return array
     */
    public function getMetricsDataByPoller()
    {
        global $centreon;

        $userId = $centreon->user->user_id;
        $isAdmin = $centreon->user->admin;

        // Get ACL if user is not admin
        if (! $isAdmin) {
            $acl = new CentreonACL($userId, $isAdmin);
        }

        // Validate options
        if (
            ! isset($this->arguments['ids'])
            || ! isset($this->arguments['start'])
            || ! is_numeric($this->arguments['start'])
            || ! isset($this->arguments['end'])
            || ! is_numeric($this->arguments['end'])
        ) {
            throw new RestBadRequestException('Bad parameters');
        }

        $explodedId = explode('-', $this->arguments['ids']);
        $id = $explodedId[0];
        $graphName = $explodedId[1];

        $start = $this->arguments['start'];
        $end = $this->arguments['end'];

        // Get the numbers of points
        $rows = 200;
        if (isset($this->arguments['rows'])) {
            if (! is_numeric($this->arguments['rows'])) {
                throw new RestBadRequestException('Bad parameters');
            }
            $rows = $this->arguments['rows'];
        }
        if ($rows < 10) {
            throw new RestBadRequestException('The rows must be greater as 10');
        }

        // Init graph object
        try {
            $graphPollerObject = new CentreonGraphPoller(
                $this->pearDB,
                $this->pearDBMonitoring
            );
            $graphPollerObject->setPoller($id, $graphName);
        } catch (Exception $e) {
            throw new RestNotFoundException('Graph not found');
        }

        $result = $graphPollerObject->getGraph($start, $end);

        return [$result];
    }

    /**
     * Authorize to access to the action
     *
     * @param string $action The action name
     * @param array $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())
        );
    }

    /**
     * @throws PDOException
     * @throws RestBadRequestException
     * @return array
     */
    protected function getListByService()
    {
        global $centreon;

        $queryValues = [];
        $queryValues['name'] = isset($this->arguments['q']) ? '%' . (string) $this->arguments['q'] . '%' : '%%';

        $query = 'SELECT SQL_CALC_FOUND_ROWS m.metric_id,
            CONCAT(h.name," - ", s.description, " - ",  m.metric_name) AS fullname
            FROM metrics m, hosts h, services s, index_data i
            WHERE m.index_id = i.id
            AND h.host_id = i.host_id
            AND s.service_id = i.service_id
            AND h.enabled = 1
            AND s.enabled = 1
            AND CONCAT(h.name," - ", s.description, " - ",  m.metric_name) LIKE :name ';

        if (! $centreon->user->admin) {
            $acl = new CentreonACL($centreon->user->user_id, $centreon->user->admin);
            $query .= 'AND s.service_id IN (' . $acl->getServicesString('ID', $this->pearDBMonitoring) . ') ';
        }

        $query .= ' ORDER BY fullname COLLATE utf8_general_ci ';

        if (isset($this->arguments['page_limit'], $this->arguments['page'])) {
            if (
                ! is_numeric($this->arguments['page'])
                || ! is_numeric($this->arguments['page_limit'])
                || $this->arguments['page_limit'] < 1
            ) {
                throw new RestBadRequestException('400 Bad Request, limit error');
            }
            $offset = ($this->arguments['page'] - 1) * $this->arguments['page_limit'];
            $query .= 'LIMIT :offset,:limit';
            $queryValues['offset'] = (int) $offset;
            $queryValues['limit'] = (int) $this->arguments['page_limit'];
        }
        $stmt = $this->pearDBMonitoring->prepare($query);
        $stmt->bindParam(':name', $queryValues['name'], PDO::PARAM_STR);
        if (isset($queryValues['offset'])) {
            $stmt->bindParam(':offset', $queryValues['offset'], PDO::PARAM_INT);
            $stmt->bindParam(':limit', $queryValues['limit'], PDO::PARAM_INT);
        }
        $stmt->execute();
        $metrics = [];
        while ($row = $stmt->fetch()) {
            $metrics[] = ['id' => $row['metric_id'], 'text' => $row['fullname']];
        }

        return ['items' => $metrics, 'total' => (int) $this->pearDBMonitoring->numberRows()];
    }

    /**
     * @throws PDOException
     * @throws RestBadRequestException
     * @return array
     */
    protected function getListOfMetricsByService()
    {
        $queryValues = [];
        if (isset($this->arguments['id'])) {
            $tmp = explode('-', $this->arguments['id']);
            $queryValues['host_id'] = (int) $tmp[0];
            $queryValues['service_id'] = (int) $tmp[1];
        } else {
            throw new RestBadRequestException('400 Bad Request, invalid service id');
        }
        $nameArg = HtmlAnalyzer::sanitizeAndRemoveTags($this->arguments['q'] ?? false);
        $queryValues['name'] = $nameArg !== false ? '%' . $nameArg . '%' : '%%';

        $query = 'SELECT SQL_CALC_FOUND_ROWS m.metric_id, '
            . 'm.metric_name AS name '
            . 'FROM metrics m, hosts h, services s, index_data i '
            . 'WHERE m.index_id = i.id '
            . 'AND h.host_id = i.host_id '
            . 'AND s.service_id = i.service_id '
            . 'AND h.enabled = 1 '
            . 'AND s.enabled = 1 '
            . 'AND m.metric_name LIKE :name '
            . 'AND h.host_id = :host_id AND s.service_id = :service_id '
            . 'ORDER BY m.metric_name ';

        if (isset($this->arguments['page_limit'], $this->arguments['page'])) {
            if (
                ! is_numeric($this->arguments['page'])
                || ! is_numeric($this->arguments['page_limit'])
                || $this->arguments['page_limit'] < 1
            ) {
                throw new RestBadRequestException('400 Bad Request, limit error');
            }
            $offset = ($this->arguments['page'] - 1) * $this->arguments['page_limit'];
            $query .= 'LIMIT :offset,:limit';
            $queryValues['offset'] = (int) $offset;
            $queryValues['limit'] = (int) $this->arguments['page_limit'];
        }
        $stmt = $this->pearDBMonitoring->prepare($query);
        $stmt->bindParam(':name', $queryValues['name'], PDO::PARAM_STR);
        $stmt->bindParam(':host_id', $queryValues['host_id'], PDO::PARAM_INT);
        $stmt->bindParam(':service_id', $queryValues['service_id'], PDO::PARAM_INT);
        if (isset($queryValues['offset'])) {
            $stmt->bindParam(':offset', $queryValues['offset'], PDO::PARAM_INT);
            $stmt->bindParam(':limit', $queryValues['limit'], PDO::PARAM_INT);
        }
        $stmt->execute();
        $metrics = [];
        while ($row = $stmt->fetch()) {
            $metrics[] = ['id' => $row['metric_id'], 'text' => $row['name']];
        }

        return ['items' => $metrics, 'total' => (int) $this->pearDBMonitoring->numberRows()];
    }

    /**
     * Get last data for metrics (by services and/or metrics)
     *
     * @param string $services List of services (like hostId_serviceId,hostId2_serviceId2,...)
     * @param string $metrics List of metrics (like metricId,metricId2,...)
     *
     * @throws PDOException
     * @return array
     */
    protected function lastMetricsData($services, $metrics)
    {
        global $centreon;

        $userId = $centreon->user->user_id;
        $isAdmin = $centreon->user->admin;
        $aclGroups = null;

        $query = '';
        $filterHostIds = [];
        $filterServiceIds = [];
        $filterMetricIds = [];

        if (is_string($services) && strlen($services) > 0) {
            foreach (explode(',', $services) as $service) {
                [$hostId, $serviceId] = explode('_', $service);
                if (! is_numeric($hostId) || ! is_numeric($serviceId)) {
                    continue;
                }
                $filterHostIds[':host' . $hostId] = $hostId;
                $filterServiceIds[':service' . $serviceId] = $serviceId;
            }

            if ($filterHostIds !== [] && $filterServiceIds !== []) {
                $query = '
                    SELECT i.host_id, i.service_id, m.*
                    FROM index_data i, metrics m
                    WHERE i.host_id IN (' . implode(',', array_keys($filterHostIds)) . ')
                    AND i.service_id IN (' . implode(',', array_keys($filterServiceIds)) . ')
                    AND i.id = m.index_id';
            }
        }

        if (is_string($metrics) && strlen($metrics) > 0) {
            foreach (explode(',', $metrics) as $metricId) {
                if (! is_numeric($metricId)) {
                    continue;
                }
                $filterMetricIds[':metric' . $metricId] = $metricId;
            }

            if ($filterMetricIds !== []) {
                if ($query !== '') {
                    $query .= ' UNION ';
                }
                $query .= '
                    SELECT i.host_id, i.service_id, m.*
                    FROM metrics m, index_data i
                    WHERE m.metric_id IN (' . implode(',', array_keys($filterMetricIds)) . ')
                    AND m.index_id = i.id';
            }
        }

        if ($query === '') {
            throw new Exception('No metrics found');
        }

        // Get ACL if user is not admin
        if (! $isAdmin) {
            $acl = new CentreonACL($userId, $isAdmin);
            $aclGroups = $acl->getAccessGroupsString();
            $query = '
                SELECT ms.* FROM (' . $query . ') as ms, centreon_acl ca
                WHERE EXISTS (
                    SELECT 1
                    FROM centreon_acl ca
                    WHERE ca.host_id = ms.host_id
                    AND ca.service_id = ms.service_id
                    AND ca.group_id IN (' . $aclGroups . '))';
        }

        $stmt = $this->pearDBMonitoring->prepare($query);
        foreach ([$filterHostIds, $filterServiceIds, $filterMetricIds] as $filterParams) {
            foreach ($filterParams as $param => $value) {
                $stmt->bindValue($param, $value, PDO::PARAM_INT);
            }
        }
        $stmt->execute();

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /**
     * Get data for metrics (by services and/or metrics)
     *
     * @param string $services List of services (like hostId_serviceId,hostId2_serviceId2,...)
     * @param string $metrics List of metrics (like metricId,metricId2,...)
     *
     * @throws PDOException
     * @throws RestBadRequestException
     * @throws RestNotFoundException
     * @return array
     */
    protected function metricsData($services, $metrics)
    {
        global $centreon;

        $selectedMetrics = $this->manageMetricsDataArguments($services, $metrics);
        $multipleServices = count(array_keys($selectedMetrics)) > 1 ? true : false;

        // Prepare graph
        try {
            $graph = new CentreonGraphNg($centreon->user->user_id);
            $graph->setMultipleServices($multipleServices);
            foreach ($selectedMetrics as $service => $metrics) {
                [$hostId, $serviceId] = explode('_', $service);
                if (count(array_keys($metrics)) <= 0) {
                    $graph->addServiceMetrics($hostId, $serviceId);
                } else {
                    $graph->addServiceCustomMetrics($hostId, $serviceId, $metrics);
                }
            }
        } catch (Exception $e) {
            throw new RestNotFoundException('Graph not found');
        }

        $result = $graph->getGraph($this->arguments['start'], $this->arguments['end']);

        if (! $multipleServices && count($selectedMetrics) > 0) {
            // Get extra information (downtime/acknowledgment)
            $result['acknowledge'] = [];
            $result['downtime'] = [];
            [$hostId, $serviceId] = explode('_', array_key_first($selectedMetrics));
            $result['acknowledge'] = $this->getAcknowledgements(
                (int) $hostId,
                (int) $serviceId,
                (int) $this->arguments['start'],
                (int) $this->arguments['end']
            );
            $result['downtime'] = $this->getDowntimePeriods(
                (int) $hostId,
                (int) $serviceId,
                (int) $this->arguments['start'],
                (int) $this->arguments['end']
            );
        }

        return $result;
    }

    /**
     * Get data for a service can be filtered by metric (new backend)
     *
     * @param string $id The service id like hostId_serviceId
     * @param int $metric The metric id
     *
     * @throws Exception
     * @throws RestBadRequestException
     * @throws RestForbiddenException
     * @throws RestNotFoundException
     * @return array
     */
    protected function serviceDatasNg(string $id, ?int $metric = null)
    {
        global $centreon;

        $userId = $centreon->user->user_id;
        $isAdmin = $centreon->user->admin;

        if (
            ! isset($this->arguments['start']) || ! is_numeric($this->arguments['start'])
            || ! isset($this->arguments['end']) || ! is_numeric($this->arguments['end'])
        ) {
            throw new RestBadRequestException('Bad parameters');
        }

        $start = $this->arguments['start'];
        $end = $this->arguments['end'];

        if (! preg_match('/^(\d+)_(\d+)$/', $id, $matches)) {
            throw new RestBadRequestException('Bad parameters');
        }

        [, $hostId, $serviceId] = $matches;

        if (
            ! is_numeric($hostId)
            || ! is_numeric($serviceId)
        ) {
            throw new RestBadRequestException('Bad parameters');
        }

        if (! $isAdmin) {
            try {
                $acl = new CentreonAclLazy($userId);
                $accessGroups = $acl->getAccessGroups();

                if ($accessGroups->isEmpty()) {
                    throw new RestForbiddenException('Access denied');
                }

                $queryParameters = [
                    QueryParameter::int('serviceId', (int) $serviceId),
                    QueryParameter::int('hostId', (int) $hostId),
                ];

                ['parameters' => $aclParameters, 'placeholderList' => $bindQuery] = createMultipleBindParameters(
                    $accessGroups->getIds(),
                    'access_group_id',
                    QueryParameterTypeEnum::INTEGER,
                );

                $request = <<<SQL
                        SELECT
                            1 AS REALTIME,
                            host_id
                        FROM
                            centreon_acl
                        WHERE
                            host_id = :hostId
                            AND service_id = :serviceId
                            AND group_id IN ({$bindQuery})
                    SQL;

                $result = $this->pearDBMonitoring->fetchAllAssociative(
                    $request,
                    QueryParameters::create([...$queryParameters, ...$aclParameters]),
                );

                if ($result === []) {
                    throw new RestForbiddenException(sprintf('You are not allowed to see graphs for service identified by ID %d', $serviceId));
                }
            } catch (Exception $exception) {
                CentreonLog::create()->error(
                    logTypeId: CentreonLog::TYPE_BUSINESS_LOG,
                    message: 'Error fetching metrics data for service: ' . $exception->getMessage(),
                    customContext: [
                        'hostId' => $hostId,
                        'serviceId' => $serviceId,
                    ],
                    exception: $exception
                );

                throw $exception;
            }
        }

        // Prepare graph
        try {
            $graph = new CentreonGraphNg($userId);
            if (is_null($metric)) {
                $graph->addServiceMetrics($hostId, $serviceId);
            } else {
                $graph->addMetric($metric);
            }
        } catch (Exception $exception) {
            CentreonLog::create()->error(
                logTypeId: CentreonLog::TYPE_BUSINESS_LOG,
                message: sprintf('Graph not found for metric %s: %s', $metric ?? '', $exception->getMessage()),
                exception: $exception
            );

            throw new RestNotFoundException('Graph not found');
        }

        $result = $graph->getGraph($this->arguments['start'], $this->arguments['end']);

        // Get extra information (downtime/acknowledgment)
        $result['acknowledge'] = [];
        $result['downtime'] = [];

        $request = <<<'SQL'
                SELECT `value` FROM `options` WHERE `key` = "display_downtime_chart"
            SQL;

        $record = $this->pearDB->fetchOne($request);

        if ((int) $record === 1) {
            $result['acknowledge'] = $this->getAcknowlegePeriods($hostId, $serviceId, $start, $end);
            $result['downtime'] = $this->getDowntimePeriods($hostId, $serviceId, $start, $end);
        }

        return $result;
    }

    /**
     * Get data for a service can be filtered by metric
     *
     * @param string $id The service id like hostId_serviceId
     * @param int $metric The metric id
     *
     * @throws Exception
     * @throws RestBadRequestException
     * @throws RestForbiddenException
     * @throws RestNotFoundException
     * @return array
     */
    protected function serviceDatas($id, $metric = null)
    {
        global $centreon;

        $userId = $centreon->user->user_id;
        $isAdmin = $centreon->user->admin;

        // Get ACL if user is not admin
        if (! $isAdmin) {
            $acl = new CentreonACL($userId, $isAdmin);
            $aclGroups = $acl->getAccessGroupsString();
        }

        if (
            ! isset($this->arguments['start'])
            || ! is_numeric($this->arguments['start'])
            || ! isset($this->arguments['end'])
            || ! is_numeric($this->arguments['end'])
        ) {
            throw new RestBadRequestException('Bad parameters');
        }

        $start = $this->arguments['start'];
        $end = $this->arguments['end'];

        // Get the numbers of points
        $rows = 200;
        if (isset($this->arguments['rows'])) {
            if (! is_numeric($this->arguments['rows'])) {
                throw new RestBadRequestException('Bad parameters');
            }
            $rows = $this->arguments['rows'];
        }
        if ($rows < 10) {
            throw new RestBadRequestException('The rows must be greater as 10');
        }

        [$hostId, $serviceId] = explode('_', $id);
        if (
            ! is_numeric($hostId)
            || ! is_numeric($serviceId)
        ) {
            throw new RestBadRequestException('Bad parameters');
        }

        // Check ACL is not admin
        if (! $isAdmin) {
            $query = 'SELECT service_id '
                . 'FROM centreon_acl '
                . 'WHERE host_id = :hostId '
                . 'AND service_id = :serviceId '
                . 'AND group_id IN (' . $aclGroups . ')';

            $stmt = $this->pearDBMonitoring->prepare($query);
            $stmt->bindParam(':hostId', $hostId, PDO::PARAM_INT);
            $stmt->bindParam(':serviceId', $serviceId, PDO::PARAM_INT);
            $dbResult = $stmt->execute();
            if (! $dbResult) {
                throw new Exception('An error occured');
            }
            if ($stmt->rowCount() === 0) {
                throw new RestForbiddenException('Access denied');
            }
        }

        // Prepare graph
        try {
            // Get index data
            $indexData = CentreonGraphService::getIndexId($hostId, $serviceId, $this->pearDBMonitoring);
            $graph = new CentreonGraphService($indexData, $userId);
        } catch (Exception $e) {
            throw new RestNotFoundException('Graph not found');
        }
        if (! is_null($metric)) {
            $graph->setMetricList($metric);
        }
        $graph->setRRDOption('start', $start);
        $graph->setRRDOption('end', $end);
        $graph->setTemplate();
        $graph->initCurveList();
        $graph->createLegend();
        $serviceData = $graph->getData($rows);
        // Replace NaN
        $counter = count($serviceData);
        for ($i = 0; $i < $counter; $i++) {
            if (isset($serviceData[$i]['data'])) {
                $times = array_keys($serviceData[$i]['data']);
                $values = array_map(
                    [$this, 'convertNaN'],
                    array_values($serviceData[$i]['data'])
                );
            }
            $serviceData[$i]['data'] = $values;
            $serviceData[$i]['label'] = $serviceData[$i]['legend'];
            unset($serviceData[$i]['legend']);
            $serviceData[$i]['type'] = $serviceData[$i]['graph_type'];
            unset($serviceData[$i]['graph_type']);
        }

        // Get extra information (downtime/acknowledgment)
        $acks = [];
        $downtimes = [];
        $query = 'SELECT `value` FROM `options` WHERE `key` = "display_downtime_chart"';
        $res = $this->pearDB->query($query);
        $row = $res->fetch();
        if (! is_null($row) && $row['value'] === '1') {
            $acks = $this->getAcknowlegePeriods($hostId, $serviceId, $start, $end);
            $downtimes = $this->getDowntimePeriods($hostId, $serviceId, $start, $end);
        }

        // Prepare limits
        $limits = $graph->getLimits();

        // Prepare legends
        $legends = $graph->getLegends();

        $result = ['service_id' => $id, 'data' => $serviceData, 'times' => $times, 'size' => $rows, 'acknowledge' => $acks, 'downtime' => $downtimes, 'limits' => $limits, 'legends' => $legends, 'base' => $graph->getBase()];

        return $result;
    }

    /**
     * Function for test is a value is NaN
     *
     * @param mixed $element The element to test
     *
     * @return mixed null if NaN else the element
     */
    protected function convertNaN($element)
    {
        if (strtoupper($element) == 'NAN') {
            return null;
        }

        return $element;
    }

    /**
     * Get the list of a acknowlegments for a service during a period
     *
     * @param int $hostId the host id
     * @param int $serviceId the service id
     * @param int $start the start timestamp
     * @param int $end the end timestamp
     *
     * @throws PDOException
     * @return array The list of acknowledgements
     */
    protected function getAcknowledgements(int $hostId, int $serviceId, int $start, int $end): array
    {
        $query = 'SELECT entry_time as start, deletion_time as end, author, comment_data '
            . 'FROM acknowledgements '
            . 'WHERE host_id = :hostId '
            . 'AND service_id = :serviceId '
            . 'AND (entry_time >= :start AND entry_time <= :end)';
        $stmt = $this->pearDBMonitoring->prepare($query);
        $stmt->bindValue(':hostId', $hostId, PDO::PARAM_INT);
        $stmt->bindValue(':serviceId', $serviceId, PDO::PARAM_INT);
        $stmt->bindValue(':start', $start, PDO::PARAM_INT);
        $stmt->bindValue(':end', $end, PDO::PARAM_INT);
        $stmt->execute();

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    /**
     * Get the list of a acknowlegment for a service during a period
     *
     * @param $hostId
     * @param $serviceId
     * @param $start
     * @param $end
     *
     * @throws Exception
     * @return array The list of ack
     */
    protected function getAcknowlegePeriods($hostId, $serviceId, $start, $end)
    {
        $queryValues = [];

        $query = 'SELECT entry_time as start, deletion_time as end '
            . 'FROM acknowledgements '
            . 'WHERE host_id = :hostId '
            . 'AND service_id = :serviceId '
            . 'AND ( '
            . '(entry_time <= :end AND :end <= deletion_time) '
            . 'OR (entry_time <= :start AND :start <= deletion_time) '
            . 'OR (entry_time >= :start AND :end >= deletion_time) '
            . 'OR(deletion_time IS NULL) '
            . ')';

        $queryValues['hostId'] = (int) $hostId;
        $queryValues['serviceId'] = (int) $serviceId;
        $queryValues['end'] = (int) $end;
        $queryValues['start'] = (int) $start;

        return $this->executeQueryPeriods($query, $start, $end, $queryValues);
    }

    /**
     * Get list of downtime periods by service
     *
     * @param int $hostId the host id
     * @param int $serviceId the service id
     * @param int $start the start timestamp
     * @param int $end the end timestamp
     *
     * @throws Exception
     * @return array
     */
    protected function getDowntimePeriods($hostId, $serviceId, $start, $end): array
    {
        $query = 'SELECT actual_start_time as start, actual_end_time as end '
            . 'FROM downtimes '
            . 'WHERE host_id = :hostId AND service_id = :serviceId '
            . 'AND '
            . 'actual_start_time <= :end AND (actual_end_time >= :start OR actual_end_time is NULL)';
        $queryValues['hostId'] = (int) $hostId;
        $queryValues['serviceId'] = (int) $serviceId;
        $queryValues['end'] = (int) $end;
        $queryValues['start'] = (int) $start;

        return $this->executeQueryPeriods($query, $start, $end, $queryValues);
    }

    /**
     * @param string $query
     * @param int $start
     * @param int $end
     * @param mixed $queryValues
     *
     * @throws PDOException
     * @return array
     */
    protected function executeQueryPeriods($query, $start, $end, $queryValues)
    {
        $periods = [];
        $stmt = $this->pearDBMonitoring->prepare($query);
        foreach ($queryValues as $key => $value) {
            $stmt->bindValue(':' . $key, $value, PDO::PARAM_INT);
        }
        $dbResult = $stmt->execute();
        if (! $dbResult) {
            throw new Exception('An error occured');
        }

        while ($row = $stmt->fetch()) {
            $period = ['start' => $row['start'], 'end' => $row['end']];
            if (
                $start > $row['start']
                || is_null($row['start'])
                || $row['start'] === ''
            ) {
                $period['start'] = $start;
            }
            if (
                $end < $row['end']
                || is_null($row['end'])
                || $row['end'] === ''
            ) {
                $period['end'] = $end;
            }
            $periods[] = $period;
        }

        return $periods;
    }

    /**
     * Check acl for user
     *
     * @param int $hostId Host id to check
     * @param int $serviceId Service id to check
     * @param array|null $aclGroups String with user acl groups
     * @param bool $isAdmin User is admin or not
     *
     * @throws PDOException
     * @return bool if the user is allowed to get service information
     */
    private function checkAcl($hostId, $serviceId, ?array $aclGroups, $isAdmin = true): bool
    {
        if (! $isAdmin) {
            $query = 'SELECT service_id '
                . 'FROM centreon_acl '
                . 'WHERE host_id = :hostId '
                . 'AND service_id = :serviceId '
                . 'AND group_id IN (' . $aclGroups . ')';

            $stmt = $this->pearDBMonitoring->prepare($query);
            $stmt->bindParam(':hostId', $hostId, PDO::PARAM_INT);
            $stmt->bindParam(':serviceId', $serviceId, PDO::PARAM_INT);
            $dbResult = $stmt->execute();
            if (! $dbResult || $stmt->rowCount() === 0) {
                return false;
            }
        }

        return true;
    }

    /**
     * Get array builded from arguments
     *
     * @param string $services List of services (like hostId_serviceId,hostId2_serviceId2,...)
     * @param string $metrics List of metrics (like metricId,metricId2,...)
     *
     * @throws PDOException
     * @throws RestBadRequestException
     * @return array
     */
    private function manageMetricsDataArguments($services, $metrics)
    {
        global $centreon;

        $userId = $centreon->user->user_id;
        $isAdmin = $centreon->user->admin;
        $aclGroups = null;

        // Get ACL if user is not admin
        if (! $isAdmin) {
            $acl = new CentreonACL($userId, $isAdmin);
            $aclGroups = $acl->getAccessGroupsString();
        }

        if (
            ! isset($this->arguments['start']) || ! is_numeric($this->arguments['start'])
            || ! isset($this->arguments['end']) || ! is_numeric($this->arguments['end'])
        ) {
            throw new RestBadRequestException('Bad parameters');
        }

        /*
         * Format:
         *    {
         *      "hostId_serviceId": {}, # no metricsId means: all metrics
         *      "hostId2_serviceId2: { "metricId": 1, "metricId2": 1 }
         *    }
         */
        $selectedMetrics = [];
        if (is_string($services) && strlen($services) > 0) {
            foreach (explode(',', $services) as $service) {
                [$hostId, $serviceId] = explode('_', $service);
                if (! is_numeric($hostId) || ! is_numeric($serviceId)) {
                    continue;
                }
                if (isset($selectedMetrics[$service])) {
                    continue;
                }

                if (! $this->checkAcl($hostId, $serviceId, $aclGroups, $isAdmin)) {
                    continue;
                }
                $selectedMetrics[$service] = [];
            }
        }

        if (is_string($metrics) && strlen($metrics) > 0) {
            $filter = '';
            $filterAppend = '';
            $queryValues = [];
            foreach (explode(',', $metrics) as $metricId) {
                if (! is_numeric($metricId)) {
                    continue;
                }
                $filter .= $filterAppend . ' :metric' . $metricId;
                $queryValues[':metric' . $metricId] = $metricId;
                $filterAppend = ',';
            }

            if ($filter === '') {
                return $selectedMetrics;
            }

            $stmt = $this->pearDBMonitoring->prepare(
                'SELECT metric_id, host_id, service_id
                 FROM metrics, index_data
                 WHERE metrics.metric_id IN (' . $filter . ')
                 AND metrics.index_id = index_data.id'
            );
            foreach ($queryValues as $param => $value) {
                $stmt->bindValue($param, $value, PDO::PARAM_INT);
            }
            $stmt->execute();

            while ($row = $stmt->fetch()) {
                if (
                    isset($selectedMetrics[$row['host_id'] . '_' . $row['service_id']])
                    && count($selectedMetrics[$row['host_id'] . '_' . $row['service_id']]) <= 0
                ) {
                    continue;
                }
                if (! isset($selectedMetrics[$row['host_id'] . '_' . $row['service_id']])) {
                    if (! $this->checkAcl($row['host_id'], $row['service_id'], $aclGroups, $isAdmin)) {
                        continue;
                    }
                    $selectedMetrics[$row['host_id'] . '_' . $row['service_id']] = [];
                }
                $selectedMetrics[$row['host_id'] . '_' . $row['service_id']][$row['metric_id']] = 1;
            }
        }

        return $selectedMetrics;
    }
}
