<?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\ValueObject\QueryParameter;
use Core\Common\Application\UseCase\VaultTrait;
use Core\Macro\Domain\Model\Macro as MacroDomain;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;

require_once __DIR__ . '/object.class.php';

/**
 * Class
 *
 * @class AbstractHost
 */
abstract class AbstractHost extends AbstractObject
{
    use VaultTrait;
    public const TYPE_HOST = 1;
    public const TYPE_TEMPLATE = 0;
    public const TYPE_VIRTUAL_HOST = 2;

    /** @var array */
    protected $hosts;

    /** @var string */
    protected $attributes_select = '
        host_id,
        command_command_id as check_command_id,
        command_command_id_arg1 as check_command_arg,
        timeperiod_tp_id as check_period_id,
        timeperiod_tp_id2 as notification_period_id,
        command_command_id2 as event_handler_id,
        command_command_id_arg2 as event_handler_arg,
        host_name,
        host_alias as alias,
        host_address as address,
        display_name,
        host_max_check_attempts as max_check_attempts,
        host_check_interval as check_interval,
        host_retry_check_interval as retry_interval,
        host_active_checks_enabled as active_checks_enabled,
        host_passive_checks_enabled as passive_checks_enabled,
        initial_state,
        host_obsess_over_host as obsess_over_host,
        host_check_freshness as check_freshness,
        host_freshness_threshold as freshness_threshold,
        host_event_handler_enabled as event_handler_enabled,
        host_low_flap_threshold as low_flap_threshold,
        host_high_flap_threshold as high_flap_threshold,
        host_flap_detection_enabled as flap_detection_enabled,
        flap_detection_options,
        host_process_perf_data as process_perf_data,
        host_retain_status_information as retain_status_information,
        host_retain_nonstatus_information as retain_nonstatus_information,
        host_notification_interval as notification_interval,
        host_notification_options as notification_options,
        host_notifications_enabled as notifications_enabled,
        contact_additive_inheritance,
        cg_additive_inheritance,
        host_first_notification_delay as first_notification_delay,
        host_recovery_notification_delay as recovery_notification_delay,
        host_stalking_options as stalking_options,
        host_snmp_community,
        host_snmp_version,
        host_register as register,
        ehi_notes as notes,
        ehi_notes_url as notes_url,
        ehi_action_url as action_url,
        ehi_icon_image as icon_image_id,
        ehi_icon_image_alt as icon_image_alt,
        ehi_statusmap_image as statusmap_image_id,
        host_location,
        host_acknowledgement_timeout as acknowledgement_timeout
    ';

    /** @var string[] */
    protected $attributes_write = ['host_name', 'alias', 'address', 'display_name', 'contacts', 'contact_groups', 'check_command', 'check_period', 'notification_period', 'event_handler', 'max_check_attempts', 'check_interval', 'retry_interval', 'initial_state', 'freshness_threshold', 'low_flap_threshold', 'high_flap_threshold', 'flap_detection_options', 'notification_interval', 'notification_options', 'first_notification_delay', 'recovery_notification_delay', 'stalking_options', 'register', 'notes', 'notes_url', 'action_url', 'icon_image', 'icon_id', 'icon_image_alt', 'statusmap_image', 'timezone', 'acknowledgement_timeout'];

    /** @var string[] */
    protected $attributes_default = ['active_checks_enabled', 'passive_checks_enabled', 'event_handler_enabled', 'flap_detection_enabled', 'notifications_enabled', 'obsess_over_host', 'check_freshness', 'process_perf_data', 'retain_status_information', 'retain_nonstatus_information'];

    /** @var string[] */
    protected $attributes_array = [
        'use',
        'parents',
        'category_tags',
        'group_tags',
    ];

    /** @var string[] */
    protected $attributes_hash = ['macros'];

    /** @var array */
    protected $loop_htpl = []; // To be reset

    /** @var null */
    protected $stmt_macro = null;

    /** @var null */
    protected $stmt_htpl = null;

    /** @var null */
    protected $stmt_contact = null;

    /** @var null */
    protected $stmt_cg = null;

    /**
     * @param $host_id
     * @param $host_tpl_id
     *
     * @return int
     */
    public function isHostTemplate($host_id, $host_tpl_id)
    {
        $loop = [];
        $stack = [];

        $hosts_tpl = HostTemplate::getInstance($this->dependencyInjector)->hosts;
        $stack = $this->hosts[$host_id]['htpl'];
        while (($host_id = array_shift($stack))) {
            if (isset($loop[$host_id])) {
                continue;
            }
            $loop[$host_id] = 1;
            if ($host_id == $host_tpl_id) {
                return 1;
            }
            $stack = array_merge($hosts_tpl[$host_id]['htpl'], $stack);
        }

        return 0;
    }

    /**
     * @param $host_id
     * @param $attr
     *
     * @return mixed|null
     */
    public function getString($host_id, $attr)
    {
        return $this->hosts[$host_id][$attr] ?? null;
    }

    /**
     * @param HostCategory $hostCategory
     * @param array<string,mixed> $host
     *
     * @throws PDOException
     */
    public function insertHostInHostCategoryMembers(HostCategory $hostCategory, array &$host): void
    {
        $host['hostCategories'] = $this->getHostCategoriesByHost($host);

        foreach ($host['hostCategories'] as $hostCategoryId) {
            $hostCategory->insertHostToCategoryMembers(
                $hostCategoryId,
                $host['host_id'],
                $host['name'] ?? $host['host_name']
            );
        }
    }

    /**
     * @param int $hostId
     * @param int|null $hostType
     *
     * @throws PDOException
     * @return mixed
     */
    protected function getHostById(int $hostId, ?int $hostType = self::TYPE_HOST)
    {
        $query = "SELECT {$this->attributes_select}
            FROM host
            LEFT JOIN extended_host_information
              ON extended_host_information.host_host_id = host.host_id
            WHERE host.host_id = :host_id
              AND host.host_activate = '1'";
        if (! is_null($hostType)) {
            $query .= ' AND host.host_register = :host_register';
        }
        $stmt = $this->backend_instance->db->prepare($query);
        $stmt->bindParam(':host_id', $hostId, PDO::PARAM_INT);
        if (! is_null($hostType)) {
            // host_register is an enum
            $stmt->bindParam(':host_register', $hostType, PDO::PARAM_STR);
        }
        $stmt->execute();

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

    /**
     * @param $host
     *
     * @throws PDOException
     * @return void
     */
    protected function getImages(&$host)
    {
        $media = Media::getInstance($this->dependencyInjector);
        if (! isset($host['icon_image'])) {
            $host['icon_image'] = $media->getMediaPathFromId($host['icon_image_id']);
            $host['icon_id'] = $host['icon_image_id'];
        }
        if (! isset($host['statusmap_image'])) {
            $host['statusmap_image'] = $media->getMediaPathFromId($host['statusmap_image_id']);
        }
    }

    /**
     * @param array $host
     * @param bool $generate
     * @param Macro[] $hostTemplateMacros
     *
     * @throws LogicException
     * @throws PDOException
     * @throws ServiceCircularReferenceException
     * @throws ServiceNotFoundException
     */
    protected function getHostTemplates(array &$host, bool $generate = true, array $hostTemplateMacros = []): void
    {
        if (! isset($host['htpl'])) {
            if (is_null($this->stmt_htpl)) {
                $this->stmt_htpl = $this->backend_instance->db->prepare('
                SELECT host_tpl_id
                FROM host_template_relation
                WHERE host_host_id = :host_id
                ORDER BY `order` ASC
                ');
            }
            $this->stmt_htpl->bindParam(':host_id', $host['host_id'], PDO::PARAM_INT);
            $this->stmt_htpl->execute();
            $host['htpl'] = $this->stmt_htpl->fetchAll(PDO::FETCH_COLUMN);
        }

        if (! $generate) {
            return;
        }

        $hostTemplate = HostTemplate::getInstance($this->dependencyInjector);
        $host['use'] = [];
        foreach ($host['htpl'] as $templateId) {
            $host['use'][] = $hostTemplate->generateFromHostId($templateId, $hostTemplateMacros);
        }
    }

    /**
     * Format Macros for export.
     * Warning: is to be run BEFORE running getSeverity to not override severity export.
     *
     * @param array<string, mixed> $host
     * @param MacroDomain[] $hostMacros
     */
    protected function formatMacros(array &$host, array $hostMacros)
    {
        $host['macros'] = [];
        if ($this->isVaultEnabled && $this->readVaultRepository !== null) {
            $vaultPathByHosts = $this->getVaultPathByResources(
                $hostMacros,
                $host['host_id'],
                $host['host_snmp_community'] ?? null
            );
            $vaultData = $this->readVaultRepository->findFromPaths($vaultPathByHosts);
            foreach ($vaultData as $hostId => $macros) {
                foreach ($macros as $macroName => $value) {
                    if (str_starts_with($macroName, '_HOST')) {
                        $newName = preg_replace('/^_HOST/', '', $macroName);
                        $vaultData[$hostId][$newName] = $value;
                        unset($vaultData[$hostId][$macroName]);
                    }
                }
            }

            // Set macro values
            foreach ($hostMacros as $hostMacro) {
                $hostId = $hostMacro->getOwnerId();
                $macroName = $hostMacro->getName();
                if (isset($vaultData[$hostId][$macroName])) {
                    $hostMacro->setValue($vaultData[$hostId][$macroName]);
                }
            }
        }

        foreach ($hostMacros as $hostMacro) {
            if ($hostMacro->getOwnerId() === $host['host_id']) {
                if ($hostMacro->shouldBeEncrypted()) {
                    if ($hostMacro->isPassword()) {
                        $host['macros']['_' . $hostMacro->getName()] = 'encrypt::'
                            . $this->engineContextEncryption->crypt($hostMacro->getValue());
                    } else {
                        $host['macros']['_' . $hostMacro->getName()] = 'raw::' . $hostMacro->getValue();
                    }
                } else {
                    $host['macros']['_' . $hostMacro->getName()] = $hostMacro->getValue();
                }
            }
        }
        if (isset($host['host_snmp_community'])) {
            $snmpCommunity = $vaultData[$host['host_id']]['SNMPCOMMUNITY'] ?? $host['host_snmp_community'];
            $shouldEncrypt = $this->backend_instance->db->fetchAssociative(
                <<<'SQL'
                    SELECT 1 FROM nagios_server ns
                    INNER JOIN ns_host_relation nsr
                    ON ns.id = nsr.nagios_server_id
                    WHERE nsr.host_host_id = :hostId
                    AND ns.is_encryption_ready = 1
                    SQL,
                QueryParameters::create([QueryParameter::int('hostId', $host['host_id'])])
            );
            $host['macros']['_SNMPCOMMUNITY'] = $shouldEncrypt !== false
                ? 'encrypt::' . $this->engineContextEncryption->crypt($snmpCommunity)
                : $snmpCommunity;
        }
        if (! is_null($host['host_snmp_version']) && $host['host_snmp_version'] !== '0') {
            $host['macros']['_SNMPVERSION'] = $host['host_snmp_version'];
        }

        $host['macros']['_HOST_ID'] = $host['host_id'];
    }

    /**
     * @param array $host (passing by Reference)
     *
     * @throws PDOException
     */
    protected function getContacts(array &$host): void
    {
        if (! isset($host['contacts_cache'])) {
            if (is_null($this->stmt_contact)) {
                $this->stmt_contact = $this->backend_instance->db->prepare("
                SELECT chr.contact_id
                FROM contact_host_relation chr, contact
                WHERE host_host_id = :host_id
                AND chr.contact_id = contact.contact_id
                AND contact.contact_activate = '1'
                ");
            }
            $this->stmt_contact->bindParam(':host_id', $host['host_id'], PDO::PARAM_INT);
            $this->stmt_contact->execute();
            $host['contacts_cache'] = $this->stmt_contact->fetchAll(PDO::FETCH_COLUMN);
        }
    }

    /**
     * @param array $host (passing by Reference)
     *
     * @throws PDOException
     */
    protected function getContactGroups(array &$host): void
    {
        if (! isset($host['contact_groups_cache'])) {
            if (is_null($this->stmt_cg)) {
                $this->stmt_cg = $this->backend_instance->db->prepare("
                SELECT contactgroup_cg_id
                FROM contactgroup_host_relation, contactgroup
                WHERE host_host_id = :host_id
                AND contactgroup_cg_id = cg_id
                AND cg_activate = '1'
                ");
            }
            $this->stmt_cg->bindParam(':host_id', $host['host_id'], PDO::PARAM_INT);
            $this->stmt_cg->execute();
            $host['contact_groups_cache'] = $this->stmt_cg->fetchAll(PDO::FETCH_COLUMN);
        }
    }

    /**
     * @param $host_id
     * @param $command_label
     *
     * @return mixed|null
     */
    protected function findCommandName($host_id, $command_label)
    {
        $loop = [];
        $stack = [];

        $hosts_tpl = HostTemplate::getInstance($this->dependencyInjector)->hosts;
        $stack = $this->hosts[$host_id]['htpl'];
        while (($host_id = array_shift($stack))) {
            if (isset($loop[$host_id])) {
                continue;
            }
            $loop[$host_id] = 1;
            if (isset($hosts_tpl[$host_id][$command_label]) && ! is_null($hosts_tpl[$host_id][$command_label])) {
                return $hosts_tpl[$host_id][$command_label];
            }
            $stack = array_merge($hosts_tpl[$host_id]['htpl'], $stack);
        }

        return null;
    }

    /**
     * @param $host
     *
     * @throws PDOException
     * @return void
     */
    protected function getHostTimezone(&$host)
    {
        $oTimezone = Timezone::getInstance($this->dependencyInjector);
        $timezone = $oTimezone->getTimezoneFromId($host['host_location']);
        if (! is_null($timezone)) {
            $host['timezone'] = ':' . $timezone;
        }
    }

    /**
     * @param $host
     * @param $result_name
     * @param $command_id_label
     * @param $command_arg_label
     *
     * @throws LogicException
     * @throws PDOException
     * @throws ServiceCircularReferenceException
     * @throws ServiceNotFoundException
     * @return int
     */
    protected function getHostCommand(&$host, $result_name, $command_id_label, $command_arg_label)
    {
        $command_name = Command::getInstance($this->dependencyInjector)
            ->generateFromCommandId($host[$command_id_label]);
        $command_arg = '';

        if (isset($host[$result_name])) {
            return 1;
        }
        $host[$result_name] = $command_name;
        if (isset($host[$command_arg_label])
            && ! is_null($host[$command_arg_label]) && $host[$command_arg_label] != ''
        ) {
            $command_arg = $host[$command_arg_label];
            if (is_null($command_name)) {
                // Find Command Name in templates
                $command_name = $this->findCommandName($host['host_id'], $result_name);
                // Can have 'args after'. We replace
                if (! is_null($command_name)) {
                    $command_name = preg_replace('/!.*/', '', $command_name);
                    $host[$result_name] = $command_name . $command_arg;
                }
            } else {
                $host[$result_name] = $command_name . $command_arg;
            }
        }

        return 0;
    }

    /**
     * @param $host
     *
     * @throws LogicException
     * @throws PDOException
     * @throws ServiceCircularReferenceException
     * @throws ServiceNotFoundException
     * @return void
     */
    protected function getHostCommands(&$host)
    {
        $this->getHostCommand($host, 'check_command', 'check_command_id', 'check_command_arg');
        $this->getHostCommand($host, 'event_handler', 'event_handler_id', 'event_handler_arg');
    }

    /**
     * @param $host
     *
     * @throws PDOException
     * @return void
     */
    protected function getHostPeriods(&$host)
    {
        $period = Timeperiod::getInstance($this->dependencyInjector);
        $host['check_period'] = $period->generateFromTimeperiodId($host['check_period_id']);
        $host['notification_period'] = $period->generateFromTimeperiodId($host['notification_period_id']);
    }

    /**
     * @param array<string,mixed> $host
     *
     * @throws PDOException
     * @return array<string,mixed>
     */
    private function getHostCategoriesByHost(array $host): array
    {
        if (isset($host['hostCategories'])) {
            return $host['hostCategories'];
        }
        $stmt = $this->backend_instance->db->prepare(
            'SELECT hostcategories_hc_id
            FROM hostcategories_relation
            WHERE host_host_id = :host_id'
        );
        $stmt->bindParam(':host_id', $host['host_id'], PDO::PARAM_INT);
        $stmt->execute();

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

    /**
     * Retrieves a mapping of resource IDs to their vault paths from macros and SNMP community.
     *
     * @param MacroDomain[] $macros
     * @param int $hostId
     * @param string|null $snmpCommunity
     * @return array<int, string> Vault path indexed by resource (host) ID
     */
    private function getVaultPathByResources(array $macros, int $hostId, ?string $snmpCommunity = null): array
    {
        $vaultPathByResources = [];

        // Collect vault paths from macros
        foreach ($macros as $macro) {
            $ownerId = $macro->getOwnerId();
            $value = $macro->getValue();

            // Store the vault path if not already stored for this owner
            if ($this->isAVaultPath($value) && ! isset($vaultPathByResources[$ownerId])) {
                $vaultPathByResources[$ownerId] = $value;
            }
        }

        // If no vault path found in macros, check SNMP community
        if ($vaultPathByResources === [] && $snmpCommunity !== null) {
            if ($this->isAVaultPath($snmpCommunity)) {
                $vaultPathByResources[$hostId] = $snmpCommunity;
            }
        }

        return $vaultPathByResources;
    }
}
