<?php declare(strict_types = 0);
/*
** Copyright (C) 2001-2026 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/


abstract class CControllerServiceListGeneral extends CController {

	protected const WITHOUT_PARENTS_SERVICEID = 0;

	protected const FILTER_DEFAULT_NAME = '';
	protected const FILTER_DEFAULT_STATUS = SERVICE_STATUS_ANY;
	protected const FILTER_DEFAULT_WITHOUT_CHILDREN = false;
	protected const FILTER_DEFAULT_WITHOUT_PROBLEM_TAGS = false;
	protected const FILTER_DEFAULT_TAG_SOURCE = ZBX_SERVICE_FILTER_TAGS_SERVICE;
	protected const FILTER_DEFAULT_EVALTYPE = TAG_EVAL_TYPE_AND_OR;

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

	/**
	 * @var bool
	 */
	protected $is_inaccessible = false;

	protected function loadService(): void {
		if ($this->hasInput('serviceid')) {
			$db_service = API::Service()->get([
				'output' => ['serviceid', 'name', 'status', 'readonly'],
				'serviceids' => $this->getInput('serviceid'),
				'selectParents' => ['serviceid'],
				'selectTags' => ['tag', 'value']
			]);

			if ($db_service) {
				$this->service = $db_service[0];
			}
			else {
				$this->is_inaccessible = true;
			}
		}
	}

	/**
	 * @throws APIException
	 *
	 * @return bool
	 */
	protected function canEdit(): bool {
		$db_roles = API::Role()->get([
			'output' => [],
			'selectRules' => ['services.write.mode', 'services.write.list', 'services.write.tag'],
			'roleids' => CWebUser::$data['roleid']
		]);

		if ($db_roles) {
			return ($db_roles[0]['rules']['services.write.mode'] == ZBX_ROLE_RULE_SERVICES_ACCESS_ALL
				|| $db_roles[0]['rules']['services.write.list']
				|| $db_roles[0]['rules']['services.write.tag']['tag'] !== '');
		}

		return false;
	}

	/**
	 * @throws APIException
	 */
	protected function doAction(): void {
		$this->loadService();

		if ($this->service !== null) {
			$this->service['tags'] = makeTags([$this->service], true, 'serviceid');
			$this->service['parents'] = API::Service()->get([
				'output' => ['serviceid', 'name'],
				'serviceids' => array_column($this->service['parents'], 'serviceid'),
				'selectChildren' => API_OUTPUT_COUNT
			]);
		}
	}

	/**
	 * @throws APIException
	 *
	 * @return array
	 */
	protected function getPath(): array {
		if ($this->service === null) {
			return $this->is_inaccessible ? [$this->getInput('serviceid')] : [];
		}

		$path_serviceids = $this->getInput('path', []);

		$path = [];
		$db_service = $this->service;

		while ($db_service['parents']) {
			$db_services  = API::Service()->get([
				'output' => ['serviceid'],
				'serviceids' => array_column($db_service['parents'], 'serviceid'),
				'selectParents' => ['serviceid'],
				'preservekeys' => true
			]);

			if (!$db_services) {
				break;
			}

			$path_serviceid = array_pop($path_serviceids);

			if ($path_serviceid !== null && array_key_exists($path_serviceid, $db_services)) {
				$path[] = $path_serviceid;
				$db_service = $db_services[$path_serviceid];
			}
			else {
				$db_service = reset($db_services);
				$path_serviceids = [];
				$path[] = $db_service['serviceid'];
			}
		}

		return array_reverse($path);
	}

	/**
	 * @param array $path
	 * @param bool  $is_filtered
	 *
	 * @throws APIException
	 *
	 * @return array
	 */
	protected function getBreadcrumbs(array $path, bool $is_filtered): array {
		$breadcrumbs = [[
			'name' => _('All services'),
			'curl' => (new CUrl('zabbix.php'))->setArgument('action', $this->getAction())
		]];

		$db_services = API::Service()->get([
			'output' => ['name'],
			'serviceids' => $path,
			'preservekeys' => true
		]);

		$parent_serviceids = [];

		foreach ($path as $serviceid) {
			if (array_key_exists($serviceid, $db_services)) {
				$breadcrumbs[] = [
					'name' => $db_services[$serviceid]['name'],
					'curl' => (new CUrl('zabbix.php'))
						->setArgument('action', $this->getAction())
						->setArgument('path', $parent_serviceids)
						->setArgument('serviceid', $serviceid)
				];
			}
			else {
				$breadcrumbs[] = ['name' => _('Inaccessible service')];
			}

			$parent_serviceids[] = $serviceid;
		}

		if ($this->service !== null) {
			$breadcrumbs[] = [
				'name' => $this->service['name'],
				'curl' => (new CUrl('zabbix.php'))
					->setArgument('action', $this->getAction())
					->setArgument('path', $parent_serviceids)
					->setArgument('serviceid', $this->service['serviceid'])
			];
		}

		if ($is_filtered) {
			$breadcrumbs[] = [
				'name' => _('Filter results')
			];
		}

		return $breadcrumbs;
	}

	/**
	 * @throws APIException
	 *
	 * @return array
	 */
	protected function getSlas(): array {
		$limit = CSettingsHelper::get(CSettingsHelper::MAX_IN_TABLE);

		$slas = API::Sla()->get([
			'output' => ['slaid', 'name', 'period', 'slo', 'timezone'],
			'serviceids' => $this->service['serviceid'],
			'filter' => [
				'status' => ZBX_SLA_STATUS_ENABLED
			],
			'sortfield' => 'name',
			'sortorder' => ZBX_SORT_UP,
			'limit' => $limit + 1
		]);

		$slas_count = count($slas);
		$slas = array_slice($slas, 0, $limit);

		foreach ($slas as &$sla) {
			$sla['sli'] = API::Sla()->getSli([
				'slaid' => $sla['slaid'],
				'periods' => 1,
				'serviceids' => $this->service['serviceid']
			]);
		}
		unset($sla);

		return [
			'slas' => $slas,
			'slas_count' => $slas_count
		];
	}

	/**
	 * @param array  $filter
	 * @param bool   $is_filtered
	 *
	 * @throws APIException
	 *
	 * @return array
	 */
	protected static function getServiceIds(array $filter, bool $is_filtered): array {
		if ($filter['status'] == SERVICE_STATUS_OK) {
			$filter_status = ZBX_SEVERITY_OK;
		}
		elseif ($filter['status'] == SERVICE_STATUS_PROBLEM) {
			$filter_status = array_column(CSeverityHelper::getSeverities(), 'value');
		}
		else {
			$filter_status = null;
		}

		$options = [
			'output' => ['name'],
			'parentids' => $filter['serviceid'],
			'deep_parentids' => $is_filtered,
			'childids' => $filter['without_children'] ? 0 : null,
			'without_problem_tags' => $filter['without_problem_tags'],
			'search' => ($filter['name'] === '')
				? null
				: ['name' => $filter['name']],
			'filter' => [
				'status' => $filter_status
			],
			'evaltype' => $filter['evaltype'],
			'sortfield' => ['sortorder', 'name'],
			'sortorder' => ZBX_SORT_UP,
			'preservekeys' => true
		];

		$db_services = [];

		if ($filter['tags']) {
			if (in_array($filter['tag_source'], [ZBX_SERVICE_FILTER_TAGS_ANY, ZBX_SERVICE_FILTER_TAGS_SERVICE])) {
				$db_services += API::Service()->get($options + ['tags' => $filter['tags']]);
			}

			if (!$filter['without_problem_tags']
					&& ($filter['tag_source'] == ZBX_SERVICE_FILTER_TAGS_ANY
						|| $filter['tag_source'] == ZBX_SERVICE_FILTER_TAGS_PROBLEM)) {
				$db_services += API::Service()->get($options + ['problem_tags' => $filter['tags']]);
			}
		}
		else {
			$db_services += API::Service()->get($options);
		}

		return array_keys($db_services);
	}

	/**
	 * @param array $services
	 */
	protected static function extendProblemEvents(array &$services): void {
		$eventids = [];

		foreach (array_column($services, 'problem_events') as $problem_events) {
			$eventids += array_column($problem_events, 'eventid', 'eventid');
		}

		$events = API::Event()->get([
			'output' => ['objectid'],
			'eventids' => $eventids,
			'source' => EVENT_SOURCE_TRIGGERS,
			'object' => EVENT_OBJECT_TRIGGER,
			'value' => TRIGGER_VALUE_TRUE,
			'preservekeys' => true
		]);

		foreach ($services as &$service) {
			$problem_events = [];

			foreach ($service['problem_events'] as $problem_event) {
				$triggerid = array_key_exists($problem_event['eventid'], $events)
					? $events[$problem_event['eventid']]['objectid']
					: 0;

				if (!array_key_exists($triggerid, $problem_events)) {
					$problem_events[$triggerid] = [];
				}

				if (!array_key_exists($problem_event['name'], $problem_events[$triggerid])) {
					$problem_events[$triggerid][$problem_event['name']] = $problem_event['severity'];
				}
				else {
					$problem_events[$triggerid][$problem_event['name']] = max($problem_event['severity'],
						$problem_events[$triggerid][$problem_event['name']]
					);
				}
			}

			$service['problem_events'] = [];

			foreach ($problem_events as $triggerid => $problem_events) {
				foreach ($problem_events as $name => $severity) {
					$service['problem_events'][] = [
						'triggerid' => $triggerid != 0 ? $triggerid : null,
						'name' => $name,
						'severity' => $severity
					];
				}
			}

			CArrayHelper::sort($service['problem_events'], [
				['field' => 'severity', 'order' => ZBX_SORT_DOWN],
				['field' => 'name', 'order' => ZBX_SORT_UP]
			]);
		}
		unset($service);
	}
}
