<?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 realpath(__DIR__ . '/../../../config/centreon.config.php');
require_once _CENTREON_PATH_ . '/www/class/centreonDB.class.php';
require_once _CENTREON_PATH_ . '/www/class/centreon-knowledge/wiki.class.php';

/**
 * Class
 *
 * @class WikiApi
 */
class WikiApi
{
    public const PROXY_URL = './include/configuration/configKnowledge/proxy/proxy.php';

    /** @var never[] */
    public $cookies = [];

    /** @var CentreonDB */
    private $db;

    /** @var Wiki */
    private $wikiObj;

    /** @var string */
    private $url;

    /** @var string */
    private $username;

    /** @var string */
    private $password;

    /** @var float */
    private $version;

    /** @var CurlHandle|false */
    private $curl;

    /** @var bool */
    private $loggedIn;

    /** @var array */
    private $tokens;

    /** @var mixed */
    private $noSslCertificate;

    /**
     * WikiApi constructor.
     */
    public function __construct()
    {
        $this->db = new CentreonDB();
        $this->wikiObj = new Wiki();
        $config = $this->wikiObj->getWikiConfig();
        $this->url = $config['kb_wiki_url'] . '/api.php';
        $this->username = $config['kb_wiki_account'];
        $this->password = $config['kb_wiki_password'];
        $this->noSslCertificate = $config['kb_wiki_certificate'];
        $this->curl = $this->getCurl();
        $this->version = $this->getWikiVersion();
    }

    /**
     * @throws Exception
     * @return float
     */
    public function getWikiVersion()
    {
        $postfields = ['action' => 'query', 'meta' => 'siteinfo', 'format' => 'json'];

        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postfields);
        $result = curl_exec($this->curl);
        $result = json_decode($result, true);
        $version = $result['query']['general']['generator'];
        $version = explode(' ', $version);
        if (isset($version[1])) {
            return (float) $version[1];
        }

        throw new Exception('An error occured, please check your Knowledge base configuration');
    }

    /**
     * @throws Exception
     * @return bool
     */
    public function login()
    {
        if ($this->loggedIn) {
            return $this->loggedIn;
        }

        // Get Connection Cookie/Token
        $postfields = ['action' => 'query', 'meta' => 'tokens', 'format' => 'json', 'type' => 'login'];

        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postfields);
        $result = curl_exec($this->curl);
        if ($result === false) {
            throw new Exception('curl error');
        }
        $decoded = json_decode($result, true);

        $token = $decoded['query']['tokens']['logintoken'];

        // Launch Connection
        $postfields = [
            'action' => 'login',
            'lgname' => $this->username,
            'format' => 'json',
        ];

        $postfields['lgpassword'] = $this->password;
        $postfields['lgtoken'] = $token;

        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postfields);
        $result = curl_exec($this->curl);
        if ($result === false) {
            throw new Exception('curl error');
        }

        $decoded = json_decode($result, true);
        $resultLogin = $decoded['login']['result'];

        $this->loggedIn = false;
        if ($resultLogin == 'Success') {
            $this->loggedIn = true;
        }

        return $this->loggedIn;
    }

    /**
     * @return void
     */
    public function logout(): void
    {
        $postfields = ['action' => 'logout'];

        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postfields);
        curl_exec($this->curl);
    }

    /**
     * @param $method
     * @param $title
     *
     * @return mixed
     */
    public function getMethodToken($method = 'delete', $title = '')
    {
        if ($this->version >= 1.24) {
            $postfields = ['action' => 'query', 'meta' => 'tokens', 'type' => 'csrf', 'format' => 'json'];
        } elseif ($this->version >= 1.20) {
            $postfields = ['action' => 'tokens', 'type' => $method, 'format' => 'json'];
        } else {
            $postfields = ['action' => 'query', 'prop' => 'info', 'intoken' => $method, 'format' => 'json', 'titles' => $title];
        }
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postfields);
        $result = curl_exec($this->curl);

        $result = json_decode($result, true);

        if ($this->version >= 1.24) {
            $this->tokens[$method] = $result['query']['tokens']['csrftoken'];
        } elseif ($this->version >= 1.20) {
            $this->tokens[$method] = $result['tokens'][$method . 'token'];
        } else {
            $page = array_pop($result['query']['pages']);
            $this->tokens[$method] = $page[$method . 'token'];
        }

        return $this->tokens[$method];
    }

    /**
     * @param $oldTitle
     * @param $newTitle
     *
     * @throws Exception
     * @return true
     */
    public function movePage($oldTitle = '', $newTitle = '')
    {
        $this->login();
        $token = $this->getMethodToken('move', $oldTitle);

        $postfields = ['action' => 'move', 'from' => $oldTitle, 'to' => $newTitle, 'token' => $token];

        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postfields);
        curl_exec($this->curl);

        return true;
    }

    /**
     * API Endpoint for deleting Knowledgebase Page
     *
     * @param string $title
     *
     * @throws Exception
     * @return bool
     */
    public function deletePage($title = '')
    {
        $tries = 0;
        $deleteResult = $this->deleteMWPage($title);
        while ($tries < 5 && isset($deleteResult->error)) {
            $deleteResult = $this->deleteMWPage($title);
            $tries++;
        }

        if (isset($deleteResult->error)) {
            return false;
        }

        return (bool) (isset($deleteResult->delete));
    }

    /**
     * @return array
     */
    public function getAllPages()
    {
        $postfields = ['format' => 'json', 'action' => 'query', 'list' => 'allpages', 'aplimit' => '200'];

        $pages = [];
        do {
            curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postfields);
            $result = curl_exec($this->curl);
            $result = json_decode($result);

            foreach ($result->query->allpages as $page) {
                $pages[] = $page->title;
            }

            // Get next page if exists
            $continue = false;
            if ($this->version >= 1.31) {
                if (isset($result->{'continue'}->apcontinue)) {
                    $postfields['apfrom'] = $result->{'continue'}->apcontinue;
                    $continue = true;
                }
            } elseif (isset($result->{'query-continue'}->allpages->apcontinue)) {
                $postfields['apfrom'] = $result->{'query-continue'}->allpages->apcontinue;
                $continue = true;
            }
        } while ($continue === true);

        return $pages;
    }

    /**
     * @param int $count
     * @return mixed
     */
    public function getChangedPages($count = 50)
    {
        // Connecting to Mediawiki API
        $postfields = ['format' => 'json', 'action' => 'query', 'list' => 'recentchanges', 'rclimit' => $count, 'rcprop' => 'title', 'rctype' => 'new|edit'];

        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postfields);
        $result = curl_exec($this->curl);
        $result = json_decode($result);

        return $result->query->recentchanges;
    }

    /**
     * @return array
     */
    public function detectCentreonObjects()
    {
        $pages = $this->getChangedPages();

        $hosts = [];
        $hostsTemplates = [];
        $services = [];
        $servicesTemplates = [];

        $count = count($pages);
        for ($i = 0; $i < $count; $i++) {
            $objectFlag = explode(':', $pages[$i]->title);
            $type = trim($objectFlag[0]);
            switch ($type) {
                case 'Host':
                    if (! in_array($pages[$i]->title, $hosts)) {
                        $hosts[] = $pages[$i]->title;
                    }
                    break;
                case 'Host-Template':
                    if (! in_array($pages[$i]->title, $hostsTemplates)) {
                        $hostsTemplates[] = $pages[$i]->title;
                    }
                    break;
                case 'Service':
                    if (! in_array($pages[$i]->title, $services)) {
                        $services[] = $pages[$i]->title;
                    }
                    break;
                case 'Service-Template':
                    if (! in_array($pages[$i]->title, $servicesTemplates)) {
                        $servicesTemplates[] = $pages[$i]->title;
                    }
                    break;
            }
        }

        return ['hosts' => $hosts, 'hostTemplates' => $hostsTemplates, 'services' => $services, 'serviceTemplates' => $servicesTemplates];
    }

    /**
     * @throws PDOException
     * @return void
     */
    public function synchronize(): void
    {
        // Get all pages title that where changed
        $listOfObjects = $this->detectCentreonObjects();

        foreach ($listOfObjects as $categorie => $object) {
            switch ($categorie) {
                case 'hosts':
                    foreach ($object as $entity) {
                        $objName = str_replace('Host : ', '', $entity);
                        $objName = str_replace(' ', '_', $objName);
                        $this->updateLinkForHost($objName);
                    }
                    break;
                case 'hostTemplates':
                    foreach ($object as $entity) {
                        $objName = str_replace('Host-Template : ', '', $entity);
                        $objName = str_replace(' ', '_', $objName);
                        $this->updateLinkForHost($objName);
                    }
                    break;
                case 'services':
                    foreach ($object as $entity) {
                        $objName = str_replace('Service : ', '', $entity);
                        $objName = str_replace(' ', '_', $objName);
                        if (preg_match('#(.+)_/_(.+)#', $objName, $matches)) {
                            $this->updateLinkForService($matches[1], $matches[2]);
                        }
                    }
                    break;
                case 'serviceTemplates':
                    foreach ($object as $entity) {
                        $objName = str_replace('Service-Template : ', '', $entity);
                        $objName = str_replace(' ', '_', $objName);
                        $this->updateLinkForServiceTemplate($objName);
                    }
                    break;
            }
        }
    }

    /**
     * @param $hostName
     *
     * @throws PDOException
     */
    public function updateLinkForHost($hostName): void
    {
        $resHost = $this->db->query(
            "SELECT host_id FROM host WHERE host_name LIKE '" . $hostName . "'"
        );

        $hostRow = $resHost->fetch();

        if ($hostRow !== false) {
            $url = self::PROXY_URL . '?host_name=$HOSTNAME$';
            $statement = $this->db->prepare(
                'UPDATE extended_host_information '
                . 'SET ehi_notes_url = :url '
                . 'WHERE host_host_id = :hostId'
            );
            $statement->bindValue(':url', $url, PDO::PARAM_STR);
            $statement->bindValue(':hostId', $hostRow['host_id'], PDO::PARAM_INT);
            $statement->execute();
        }
    }

    /**
     * @param $hostName
     * @param $serviceDescription
     *
     * @throws PDOException
     */
    public function updateLinkForService($hostName, $serviceDescription): void
    {
        $resService = $this->db->query(
            'SELECT service_id '
            . 'FROM service, host, host_service_relation '
            . "WHERE host.host_name LIKE '" . $hostName . "' "
            . "AND service.service_description LIKE '" . $serviceDescription . "' "
            . 'AND host_service_relation.host_host_id = host.host_id '
            . 'AND host_service_relation.service_service_id = service.service_id '
        );
        $serviceRow = $resService->fetch();

        if ($serviceRow !== false) {
            $url = self::PROXY_URL . '?host_name=$HOSTNAME$&service_description=$SERVICEDESC$';
            $statement = $this->db->prepare(
                'UPDATE extended_service_information '
                . 'SET esi_notes_url = :url '
                . 'WHERE service_service_id = :serviceId'
            );
            $statement->bindValue(':url', $url, PDO::PARAM_STR);
            $statement->bindValue(':serviceId', $serviceRow['service_id'], PDO::PARAM_INT);
            $statement->execute();
        }
    }

    /**
     * @param $serviceName
     *
     * @throws PDOException
     */
    public function updateLinkForServiceTemplate($serviceName): void
    {
        $resService = $this->db->query(
            'SELECT service_id FROM service '
            . "WHERE service_description LIKE '" . $serviceName . "' "
        );
        $serviceTemplateRow = $resService->fetch();

        if ($serviceTemplateRow !== false) {
            $url = self::PROXY_URL . '?host_name=$HOSTNAME$&service_description=$SERVICEDESC$';
            $statement = $this->db->prepare(
                'UPDATE extended_service_information '
                . 'SET esi_notes_url = :url '
                . 'WHERE service_service_id = :serviceId'
            );
            $statement->bindValue(':url', $url, PDO::PARAM_STR);
            $statement->bindValue(':serviceId', $serviceTemplateRow['service_id'], PDO::PARAM_INT);
            $statement->execute();
        }
    }

    /**
     * @return CurlHandle|false
     */
    private function getCurl()
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $this->url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_COOKIEFILE, '');
        if ($this->noSslCertificate == 1) {
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        }

        return $curl;
    }

    /**
     * make a call to mediawiki api to delete a page
     *
     * @param string $title
     *
     * @throws Exception
     * @return object
     */
    private function deleteMWPage($title = '')
    {
        $this->login();

        $token = $this->getMethodToken('delete', $title);

        $postfields = ['action' => 'delete', 'title' => $title, 'token' => $token, 'format' => 'json'];
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postfields);
        $result = curl_exec($this->curl);

        return json_decode($result);
    }
}
