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

declare(strict_types=1);

namespace Centreon\Legacy\EventLogs\Export;

use CentreonACL;
use CentreonDB;
use PDOStatement;

class QueryGenerator
{
    /** @var array<array<int,mixed>> */
    private array $queryValues = [];

    private ?int $is_admin = null;

    private string $openid = '';

    private string $output = '';

    private CentreonACL $access;

    private int $start;

    private int $end;

    private string $up;

    private string $down;

    private string $unreachable;

    private string $ok;

    private string $warning;

    private string $critical;

    private string $unknown;

    private string $acknowledgement;

    private string $notification;

    private string $alert;

    private string $error;

    private string $oh;

    /** @var string[] */
    private array $hostMsgStatusSet = [];

    /** @var string[] */
    private array $svcMsgStatusSet = [];

    /** @var string[] */
    private array $tabHostIds = [];

    private string $searchHost = '';

    /** @var array<mixed> */
    private array $tabSvc;

    private string $searchService = '';

    private string $engine;

    private string|int $export;

    private int $num = 0;

    private ?int $limit = null;

    public function __construct(private CentreonDB $pearDBO)
    {
    }

    /**
     * @param string $up
     * @return void
     */
    public function setUp(string $up): void
    {
        $this->up = $up;
    }

    /**
     * @param int|null $is_admin
     * @return void
     */
    public function setIsAdmin(?int $is_admin): void
    {
        $this->is_admin = $is_admin;
    }

    /**
     * @param string $openId
     * @return void
     */
    public function setOpenId(string $openId): void
    {
        $this->openid = $openId;
    }

    /**
     * @param string $output
     * @return void
     */
    public function setOutput(string $output): void
    {
        $this->output = $output;
    }

    /**
     * @param CentreonACL $access
     * @return void
     */
    public function setAccess(CentreonACL $access): void
    {
        $this->access = $access;
    }

    /**
     * @param int $start
     * @return void
     */
    public function setStart(int $start): void
    {
        $this->start = $start;
    }

    /**
     * @param int $end
     * @return void
     */
    public function setEnd(int $end): void
    {
        $this->end = $end;
    }

    /**
     * @param string $down
     * @return void
     */
    public function setDown(string $down): void
    {
        $this->down = $down;
    }

    /**
     * @param string $unreachable
     * @return void
     */
    public function setUnreachable(string $unreachable): void
    {
        $this->unreachable = $unreachable;
    }

    /**
     * @param string $ok
     * @return void
     */
    public function setOk(string $ok): void
    {
        $this->ok = $ok;
    }

    /**
     * @param string $warning
     * @return void
     */
    public function setWarning(string $warning): void
    {
        $this->warning = $warning;
    }

    /**
     * @param string $critical
     * @return void
     */
    public function setCritical(string $critical): void
    {
        $this->critical = $critical;
    }

    /**
     * @param string $unknown
     * @return void
     */
    public function setUnknown(string $unknown): void
    {
        $this->unknown = $unknown;
    }

    /**
     * @param string $notification
     * @return void
     */
    public function setNotification(string $notification): void
    {
        $this->notification = $notification;
    }

    /**
     * @param string $alert
     * @return void
     */
    public function setAlert(string $alert): void
    {
        $this->alert = $alert;
    }

    /**
     * @param string $error
     * @return void
     */
    public function setError(string $error): void
    {
        $this->error = $error;
    }

    /**
     * @param string $oh
     * @return void
     */
    public function setOh(string $oh): void
    {
        $this->oh = $oh;
    }

    /**
     * @param string[] $hostMsgStatusSet
     * @return void
     */
    public function setHostMsgStatusSet(array $hostMsgStatusSet): void
    {
        $this->hostMsgStatusSet = $hostMsgStatusSet;
    }

    /***
     * @param String[] $svcMsgStatusSet
     * @return void
     */
    public function setSvcMsgStatusSet(array $svcMsgStatusSet): void
    {
        $this->svcMsgStatusSet = $svcMsgStatusSet;
    }

    /**
     * @param string[] $tabHostIds
     * @return void
     */
    public function setTabHostIds(array $tabHostIds): void
    {
        $this->tabHostIds = $tabHostIds;
    }

    /**
     * @param string $searchHost
     * @return void
     */
    public function setSearchHost(string $searchHost): void
    {
        $this->searchHost = $searchHost;
    }

    /**
     * @param string[] $tabSvc
     * @return void
     */
    public function setTabSvc(array $tabSvc): void
    {
        $this->tabSvc = $tabSvc;
    }

    /**
     * @param string $searchService
     * @return void
     */
    public function setSearchService(string $searchService): void
    {
        $this->searchService = $searchService;
    }

    /**
     * @param string $engine
     * @return void
     */
    public function setEngine(string $engine): void
    {
        $this->engine = $engine;
    }

    /**
     * @param int|string $export
     * @return void
     */
    public function setExport(int|string $export): void
    {
        $this->export = $export;
    }

    /**
     * @param int $num
     * @return void
     */
    public function setNum(int $num): void
    {
        $this->num = $num;
    }

    /**
     * @param int|null $limit
     * @return void
     */
    public function setLimit(?int $limit): void
    {
        $this->limit = $limit;
    }

    /**
     * @param string $acknowledgement
     */
    public function setAcknowledgement(string $acknowledgement): void
    {
        $this->acknowledgement = $acknowledgement;
    }

    /**
     * Generates executable PROStatement for all database records
     * @return PDOStatement
     */
    public function getStatement(): PDOStatement
    {
        $req = $this->generateQuery();
        $stmt = $this->pearDBO->prepare($req);

        foreach ($this->queryValues as $bindId => $bindData) {
            foreach ($bindData as $bindType => $bindValue) {
                $stmt->bindValue($bindId, $bindValue, $bindType);
            }
        }

        return $stmt;
    }

    /**
     * Generates SQL statement with placeholders for all database records
     * @return string
     */
    private function generateQuery(): string
    {
        $whereOutput = $this->generateWhere();
        $msg_req = $this->generateMsgHost();

        // Build final request
        $req = 'SELECT SQL_CALC_FOUND_ROWS ' . (! $this->is_admin ? 'DISTINCT' : '') . '
            1 AS REALTIME,
            logs.ctime,
            logs.host_id,
            logs.host_name,
            logs.service_id,
            logs.service_description,
            logs.msg_type,
            logs.notification_cmd,
            logs.notification_contact,
            logs.output,
            logs.retry,
            logs.status,
            logs.type,
            logs.instance_name
            FROM logs ' . $this->generateInnerJoinQuery()
            . (
                ! $this->is_admin
                ? ' INNER JOIN centreon_acl acl ON (logs.host_id = acl.host_id AND (acl.service_id IS NULL OR '
                . ' acl.service_id = logs.service_id)) '
                . ' WHERE acl.group_id IN (' . $this->access->getAccessGroupsString() . ') AND '
                : 'WHERE '
            )
            . " logs.ctime > '{$this->start}' AND logs.ctime <= '{$this->end}' {$whereOutput} {$msg_req}";

        // Add Host
        $str_unitH = '';
        $str_unitH_append = '';
        $host_search_sql = '';
        if (count($this->tabHostIds) == 0 && count($this->tabSvc) == 0) {
            if ($this->engine == 'false') {
                $req .= " AND `msg_type` NOT IN ('4','5') ";
                $req .= " AND logs.host_name NOT LIKE '\\_Module\\_BAM%' ";
            }
        } else {
            foreach ($this->tabHostIds as $host_id) {
                if ($host_id != '') {
                    $str_unitH .= $str_unitH_append . "'{$host_id}'";
                    $str_unitH_append = ', ';
                }
            }
            if ($str_unitH != '') {
                $str_unitH = "(logs.host_id IN ({$str_unitH}) AND (logs.service_id IS NULL OR logs.service_id = 0))";
                if (isset($this->searchHost) && $this->searchHost != '') {
                    $host_search_sql
                        = " AND logs.host_name LIKE '%" . $this->pearDBO->escape($this->searchHost) . "%' ";
                }
            }

            // Add services
            $flag = 0;
            $str_unitSVC = '';
            $service_search_sql = '';
            if (
                (count($this->tabSvc) || count($this->tabHostIds))
                && (
                    $this->up == 'true'
                    || $this->down == 'true'
                    || $this->unreachable == 'true'
                    || $this->ok == 'true' || $this->warning == 'true'
                    || $this->critical == 'true'
                    || $this->unknown == 'true'
                    || $this->acknowledgement == 'true'
                )
            ) {
                $req_append = '';
                foreach ($this->tabSvc as $host_id => $services) {
                    $str = '';
                    $str_append = '';
                    foreach ($services as $svc_id => $svc_name) {
                        if ($svc_id != '') {
                            $str .= $str_append . $svc_id;
                            $str_append = ', ';
                        }
                    }
                    if ($str != '') {
                        if ($host_id === '_Module_Meta') {
                            $str_unitSVC .= $req_append . " (logs.host_name = '" . $host_id . "' "
                                . 'AND logs.service_id IN (' . $str . ')) ';
                        } else {
                            $str_unitSVC .= $req_append
                                . " (logs.host_id = '" . $host_id . "' AND logs.service_id IN ({$str})) ";
                        }
                        $req_append = ' OR';
                    }
                }
                if (isset($this->searchService) && $this->searchService != '') {
                    $service_search_sql
                        = " AND logs.service_description LIKE '%" . $this->pearDBO->escape($this->searchService) . "%' ";
                }
                if ($str_unitH != '' && $str_unitSVC != '') {
                    $str_unitSVC = ' OR ' . $str_unitSVC;
                }
                if ($str_unitH != '' || $str_unitSVC != '') {
                    $req .= ' AND (' . $str_unitH . $str_unitSVC . ')';
                }
            } else {
                $req .= 'AND 0 ';
            }
            $req .= " AND logs.host_name NOT LIKE '\\_Module\\_BAM%' ";
            $req .= $host_search_sql . $service_search_sql;
        }

        $limit = ($this->export !== '1' && $this->num) ? $this->generateLimit() : '';

        $req .= ' ORDER BY ctime DESC ' . $limit;

        return $req;
    }

    /**
     * Generates limit statement
     * @return string
     */
    private function generateLimit(): string
    {
        if ($this->num < 0) {
            $this->num = 0;
        }

        $offset = $this->num * $this->limit;
        $this->queryValues['offset'] = [\PDO::PARAM_INT => $offset];
        $this->queryValues['limit'] = [\PDO::PARAM_INT => $this->limit];

        return ' LIMIT :offset, :limit';
    }

    /**
     * Creates join statement with instances and filters on poller IDs
     * @return string
     */
    private function generateInnerJoinQuery(): string
    {
        $innerJoinEngineLog = '';
        if ($this->engine == 'true' && isset($this->openid) && $this->openid != '') {
            // filtering poller ids and keeping only real ids
            $pollerIds = explode(',', $this->openid);
            $filteredIds = array_filter($pollerIds, function ($id) {
                return is_numeric($id);
            });

            if ($filteredIds !== []) {
                foreach ($filteredIds as $index => $filteredId) {
                    $key = ':pollerId' . $index;
                    $this->queryValues[$key] = [\PDO::PARAM_INT => $filteredId];
                    $pollerIds[] = $key;
                }
                $innerJoinEngineLog = ' INNER JOIN instances i ON i.name = logs.instance_name'
                    . ' AND i.instance_id IN ( ' . implode(',', array_values($pollerIds)) . ')';
            }
        }

        return $innerJoinEngineLog;
    }

    /**
     * Generates Where statement
     * @return string
     */
    private function generateWhere(): string
    {
        $whereOutput = '';
        if (isset($this->output) && $this->output != '') {
            $this->queryValues[':output'] = [\PDO::PARAM_STR => '%' . $this->output . '%'];
            $whereOutput = ' AND logs.output like :output ';
        }

        return $whereOutput;
    }

    /**
     * Generates sub request with filters
     * @return string
     */
    private function generateMsgHost(): string
    {
        $msg_req = '';

        $flag_begin = 0;

        if ($this->notification == 'true') {
            if ($this->hostMsgStatusSet !== []) {
                $msg_req .= '(';
                $flag_begin = 1;
                $msg_req .= " (`msg_type` = '3' ";
                $msg_req .= ' AND `status` IN (' . implode(',', $this->hostMsgStatusSet) . '))';
                $msg_req .= ') ';
            }
            if ($this->svcMsgStatusSet !== []) {
                if ($flag_begin == 0) {
                    $msg_req .= '(';
                } else {
                    $msg_req .= ' OR ';
                }
                $msg_req .= " (`msg_type` = '2' ";
                $msg_req .= ' AND `status` IN (' . implode(',', $this->svcMsgStatusSet) . '))';
                if ($flag_begin == 0) {
                    $msg_req .= ') ';
                }
                $flag_begin = 1;
            }
        }

        if ($this->alert == 'true') {
            if ($this->hostMsgStatusSet !== []) {
                if ($flag_begin) {
                    $msg_req .= ' OR ';
                }
                if ($this->oh == true) {
                    $msg_req .= ' ( ';
                    $flag_oh = true;
                }
                $flag_begin = 1;
                $msg_req .= " ((`msg_type` IN ('1', '10', '11') ";
                $msg_req .= ' AND `status` IN (' . implode(',', $this->hostMsgStatusSet) . ')) ';
                $msg_req .= ') ';
            }
            if ($this->svcMsgStatusSet !== []) {
                if ($flag_begin) {
                    $msg_req .= ' OR ';
                }
                if ($this->oh == true && ! isset($flag_oh)) {
                    $msg_req .= ' ( ';
                }
                $flag_begin = 1;
                $msg_req .= " ((`msg_type` IN ('0', '10', '11') ";
                $msg_req .= ' AND `status` IN (' . implode(',', $this->svcMsgStatusSet) . ')) ';
                $msg_req .= ') ';
            }
            if ($flag_begin) {
                $msg_req .= ')';
            }
            if ((count($this->hostMsgStatusSet) || count($this->svcMsgStatusSet)) && $this->oh == 'true') {
                $msg_req .= ' AND ';
            }
            if ($this->oh == 'true') {
                $flag_begin = 1;
                $msg_req .= " `type` = '1' ";
            }
        }
        // Error filter is only used in the engine log page.
        if ($this->error == 'true') {
            if ($flag_begin == 0) {
                $msg_req .= 'AND ';
            } else {
                $msg_req .= ' OR ';
            }
            $msg_req .= " `msg_type` IN ('4','5') ";
        }
        if ($flag_begin) {
            $msg_req = ' AND (' . $msg_req . ') ';
        }

        return $msg_req;
    }
}
