<?php
/*
** Copyright (C) 2001-2025 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/>.
**/


require_once dirname(__FILE__).'/../../include/CWebTest.php';

/**
 * Test checks the objects created by LLD and then no more discovered or/and disabled.
 *
 * @backup hstgrp
 *
 * @onBefore prepareLLDData
 */
class testLowLevelDiscoveryDisabledObjects extends CWebTest {

	const HINT_HOST = 'Host for LLD hint';

	protected static $hint_hostid;

	public static function prepareLLDData() {
		// Create hostgroup for hosts with LLD.
		$hostgroupid = CDataHelper::call('hostgroup.create', [['name' => 'Group for LLD hint']])['groupids'][0];

		// Create hosts with low level discovery.
		$hosts = CDataHelper::createHosts([
			[
				'host' => self::HINT_HOST,
				'groups' => [['groupid' => $hostgroupid]],
				'discoveryrules' => [
					[
						'name' => '1 Delete - never, disable - never',
						'key_' => 'rule1',
						'type' => ITEM_TYPE_TRAPPER,
						'lifetime_type' => ZBX_LLD_DELETE_NEVER,
						'enabled_lifetime_type' => ZBX_LLD_DISABLE_NEVER
					],
					[
						'name' => '2 Delete - never, disable - immediately',
						'key_' => 'rule2',
						'type' => ITEM_TYPE_TRAPPER,
						'lifetime_type' => ZBX_LLD_DELETE_NEVER,
						'enabled_lifetime_type' => ZBX_LLD_DISABLE_IMMEDIATELY
					],
					[
						'name' => '3 Delete - never, disable - after 2w',
						'key_' => 'rule3',
						'type' => ITEM_TYPE_TRAPPER,
						'lifetime_type' => ZBX_LLD_DELETE_NEVER,
						'enabled_lifetime_type' => ZBX_LLD_DISABLE_AFTER,
						'enabled_lifetime' => '2w'
					],
					[
						'name' => '4 Delete - after 52w, disable - never',
						'key_' => 'rule4',
						'type' => ITEM_TYPE_TRAPPER,
						'lifetime_type' => ZBX_LLD_DELETE_AFTER,
						'lifetime' => '52w',
						'enabled_lifetime_type' => ZBX_LLD_DISABLE_NEVER
					],
					[
						'name' => '5 Delete - after 7d, disable - immediately',
						'key_' => 'rule5',
						'type' => ITEM_TYPE_TRAPPER,
						'lifetime_type' => ZBX_LLD_DELETE_AFTER,
						'lifetime' => '7d',
						'enabled_lifetime_type' => ZBX_LLD_DISABLE_IMMEDIATELY
					],
					[
						'name' => '6 Delete - after 7d, disable - after 20h',
						'key_' => 'rule6',
						'type' => ITEM_TYPE_TRAPPER,
						'lifetime_type' => ZBX_LLD_DELETE_AFTER,
						'lifetime' => '7d',
						'enabled_lifetime_type' => ZBX_LLD_DISABLE_AFTER,
						'enabled_lifetime' => '20h'
					],
					[
						'name' => '7 Delete - Immediately',
						'key_' => 'rule7',
						'type' => ITEM_TYPE_TRAPPER,
						'lifetime_type' => ZBX_LLD_DELETE_IMMEDIATELY
					]
				]
			]
		]);
		self::$hint_hostid = $hosts['hostids'][self::HINT_HOST];

		// Statuses of discovered object in DB.
		$time = time();
		$statuses = [
			// 1 Delete - never, disable - never.
			[
				'status' => 0,
				'lastcheck' => $time,
				'ts_delete' => 0,
				'disable_source' => 0,
				'ts_disable' => 0
			],
			// 2 Delete - never, disable - immediately.
			[
				'status' => 1,
				'lastcheck' => $time,
				'ts_delete' => 0,
				'disable_source' => 1,
				'ts_disable' => 1
			],
			// 3 Delete - never, disable - after 2w.
			[
				'status' => 0,
				'lastcheck' => $time,
				'ts_delete' => 0,
				'disable_source' => 1,
				'ts_disable' => $time + 1209600
			],
			// 4 Delete - after 52w, disable - never.
			[
				'status' => 0,
				'lastcheck' => $time,
				'ts_delete' => $time + 31536000,
				'disable_source' => 1,
				'ts_disable' => 0
			],
			// 5 Delete - after 7d, disable - immediately.
			[
				'status' => 1,
				'lastcheck' => $time,
				'ts_delete' => $time + 604800,
				'disable_source' => 1,
				'ts_disable' => 1
			],
			// 6 Delete - after 7d, disable - after 20h.
			[
				'status' => 0,
				'lastcheck' => $time,
				'ts_delete' => $time + 604800,
				'disable_source' => 1,
				'ts_disable' => $time + 72000
			],
			// 7 Delete - immediately.
			[
				'status' => 0,
				'lastcheck' => $time,
				'ts_delete' => $time,
				'disable_source' => 0,
				'ts_disable' => 0
			]
		];

		// Create item prototypes for hint check.
		$item_prototypes_data = [];
		$i = 1;
		foreach ($hosts['discoveryruleids'] as $lldid) {
			$item_prototypes_data[] = [
				'hostid' => self::$hint_hostid,
				'ruleid' => $lldid,
				'name' => '{#KEY} Prototype '.$i,
				'key_' => 'trap'.$i.'[{#KEY}]',
				'type' => ITEM_TYPE_TRAPPER,
				'value_type' => ITEM_VALUE_TYPE_UINT64
			];
			$i++;
		}
		$item_protototypes = CDataHelper::call('itemprototype.create', $item_prototypes_data);

		$discovered_items = [];
		$r = 1;
		foreach ($item_protototypes['itemids'] as $item_protototypeid) {
			$discovered_items[] = array_merge($statuses[$r - 1], [
				'item_name' => 'KEY1 Prototype '.$r,
				'key_' => 'trap'.$r.'[KEY1]',
				'itemid' => $r + 10090000,
				'item_prototypeid' => $item_protototypeid,
				'graph_itemid' => $r + 20090000,
				'itemdiscoveryid' => $r + 30090000
			]);
			$r++;
		}

		// Emulate item discovery in DB.
		foreach ($discovered_items as $discovered_item) {
			DBexecute('INSERT INTO items (itemid, type, hostid, name, description, key_, interfaceid, flags, query_fields,'.
					' params, posts, headers, status) VALUES ('.zbx_dbstr($discovered_item['itemid']).', 2, '.
					zbx_dbstr(self::$hint_hostid).', '.zbx_dbstr($discovered_item['item_name']).', \'\', '.
					zbx_dbstr($discovered_item['key_']).', NULL, 4, \'\', \'\', \'\', \'\', '.zbx_dbstr($discovered_item['status']).')'
			);
			DBexecute('INSERT INTO item_discovery (itemdiscoveryid, itemid, parent_itemid, lastcheck, ts_delete, disable_source,'.
					' ts_disable, status) VALUES ('.zbx_dbstr($discovered_item['itemdiscoveryid']).', '.
					zbx_dbstr($discovered_item['itemid']).', '.zbx_dbstr($discovered_item['item_prototypeid']).', '.
					zbx_dbstr($discovered_item['lastcheck']).', '.zbx_dbstr($discovered_item['ts_delete']).', '.
					zbx_dbstr($discovered_item['disable_source']).', '.zbx_dbstr($discovered_item['ts_disable']).', 1);'
			);
		}

		// Create trigger prototypes for hint check.
		$lld_count = count($hosts['discoveryruleids']);
		$trigger_protototypes_data = [];
		for ($j = 1; $j <= $lld_count; $j++) {
			$trigger_protototypes_data[] = [
				'description' => 'Trigger prototype '.$j.' {#KEY}',
				'expression' => 'last(/'.self::HINT_HOST.'/trap'.$j.'[{#KEY}])=0'
			];
		}
		CDataHelper::call('triggerprototype.create', $trigger_protototypes_data);
		$trigger_protototypeids = CDataHelper::getIds('description');

		$discovered_triggers = [];
		$y = 0;
		foreach ($discovered_items as $discovered_item) {
			$discovered_triggers[] = array_merge($statuses[$y], [
				'description' => 'Trigger prototype '.($y + 1).' KEY1',
				'functionid' => $y + 2090000,
				'triggerid' => $y + 3090000,
				'itemid' => $discovered_item['itemid'],
				'parent_prototypeid' => $trigger_protototypeids['Trigger prototype '.($y + 1).' {#KEY}']
			]);
			$y++;
		}

		// Emulate triggers discovery in DB.
		foreach ($discovered_triggers as $discovered_trigger) {
			DBexecute('INSERT INTO triggers (triggerid, description, expression, status, value, priority, comments, state, flags)'.
					' VALUES ('.zbx_dbstr($discovered_trigger['triggerid']).', '.zbx_dbstr($discovered_trigger['description']).
					', '.zbx_dbstr('{'.$discovered_trigger['functionid'].'}=0').', '.$discovered_trigger['status'].', 0, 0, \'\', 0, 4)'
			);

			DBexecute('INSERT INTO functions (functionid, itemid, triggerid, name, parameter) VALUES ('.
					zbx_dbstr($discovered_trigger['functionid']).', '.zbx_dbstr($discovered_trigger['itemid']).', '.
					zbx_dbstr($discovered_trigger['triggerid']).', \'last\', \'$\')'
			);

			DBexecute('INSERT INTO trigger_discovery (triggerid, parent_triggerid, lastcheck, ts_delete, disable_source, ts_disable, status) VALUES ('.
					zbx_dbstr($discovered_trigger['triggerid']).', '.zbx_dbstr($discovered_trigger['parent_prototypeid']).', '.
					zbx_dbstr($discovered_trigger['lastcheck']).', '.zbx_dbstr($discovered_trigger['ts_delete']).', '.
					zbx_dbstr($discovered_trigger['disable_source']).', '.zbx_dbstr($discovered_trigger['ts_disable']).', 1)'
			);
		}

		// Create graph prototypes for hint check.
		$graph_prototypes_data = [];
		$k = 1;
		foreach ($item_protototypes['itemids'] as $itemid) {
			$graph_prototypes_data[] = [
				'name' => 'Graph prototype '.$k.' {#KEY}',
				'width' => 600,
				'height' => 300,
				'gitems' => [
					[
						'itemid' => $itemid,
						'color' => '5C6BC0'
					]
				]
			];
			$k++;
		}
		$graph_protototypes = CDataHelper::call('graphprototype.create', $graph_prototypes_data);

		$discovered_graphs = [];
		$p = 0;
		foreach ($graph_protototypes['graphids'] as $graph_protototypeid) {
			$discovered_graphs[] = array_merge($statuses[$p], [
				'name' => 'Graph prototype '.($p + 1).' KEY1',
				'graphid' => $p + 2090000,
				'graph_prototypeid' => $graph_protototypeid,
				'graph_itemid' => $p + 3090000,
				'itemid' => $discovered_items[$p]['itemid']
			]);
			$p++;
		}

		// Emulate graph discovery in DB.
		foreach ($discovered_graphs as $discovered_graph) {
			DBexecute('INSERT INTO graphs (graphid, width, height, name, flags) VALUES ('.zbx_dbstr($discovered_graph['graphid']).
					', 600, 300, '.zbx_dbstr($discovered_graph['name']).', 4)'
			);
			DBexecute('INSERT INTO graphs_items (gitemid, graphid, itemid, color) VALUES ('.
					zbx_dbstr($discovered_graph['graph_itemid']).', '.zbx_dbstr($discovered_graph['graphid']).', '.
					zbx_dbstr($discovered_graph['itemid']).', '.zbx_dbstr('5C6BC0').')'
			);
			DBexecute('INSERT INTO graph_discovery (graphid, parent_graphid, lastcheck, ts_delete, status)'.
					' VALUES ('.zbx_dbstr($discovered_graph['graphid']).', '.zbx_dbstr($discovered_graph['graph_prototypeid']).', '.
					zbx_dbstr($discovered_graph['lastcheck']).', '.zbx_dbstr($discovered_graph['ts_delete']).', 1)'
			);
		}

		// Create host prototypes for hint check.
		$host_prototype_data = [];
		$l = 1;
		foreach ($hosts['discoveryruleids'] as $lldid) {
			$host_prototype_data[] = [
				'host' => 'Host prototype '.$l.' {#KEY}',
				'ruleid' => $lldid,
				'groupLinks' => [['groupid' => $hostgroupid]]
			];
			$l++;
		}
		$host_prototypes = CDataHelper::call('hostprototype.create', $host_prototype_data);

		$discovered_hosts = [];
		$m = 1;
		foreach ($host_prototypes['hostids'] as $host_prototypeid) {
			$discovered_hosts[] = array_merge($statuses[$m - 1], [
				'discovered_host_name' => 'Host prototype '.$m.' KEY1',
				'hostid' => $m + 7090000,
				'host_prototypeid' => $host_prototypeid,
				'host_groupid' => $m + 9090000
			]);
			$m++;
		}

		// Emulate host discovery in DB.
		foreach ($discovered_hosts as $discovered_host) {
			DBexecute('INSERT INTO hosts (hostid, host, name, status, flags, description) VALUES ('.
					zbx_dbstr($discovered_host['hostid']).', '.zbx_dbstr($discovered_host['discovered_host_name']).
					', '.zbx_dbstr($discovered_host['discovered_host_name']).', '.$discovered_host['status'].', 4, \'\')'
			);
			DBexecute('INSERT INTO host_discovery (hostid, parent_hostid, lastcheck, ts_delete, disable_source, ts_disable, status)'.
					' VALUES ('.zbx_dbstr($discovered_host['hostid']).', '.zbx_dbstr($discovered_host['host_prototypeid']).', '.
					zbx_dbstr($discovered_host['lastcheck']).', '.zbx_dbstr($discovered_host['ts_delete']).', '.
					zbx_dbstr($discovered_host['disable_source']).', '.zbx_dbstr($discovered_host['ts_disable']).', 1)'
			);
			DBexecute('INSERT INTO hosts_groups (hostgroupid, hostid, groupid) VALUES ('.zbx_dbstr($discovered_host['host_groupid']).
					', '.zbx_dbstr($discovered_host['hostid']).', 4)'
			);
		}
	}

	public static function getTestPagesData() {
		return [
			// #0.
			[
				[
					'object' => 'host',
					'url' => 'zabbix.php?action=host.list&filter_set=1&filter_host=KEY1'
				]
			],
			// #1.
			[
				[
					'object' => 'item',
					'url' => 'zabbix.php?action=item.list&context=host&filter_set=1&filter_hostids%5B0%5D='
				]
			],
			// #2.
			[
				[
					'object' => 'trigger',
					'url' => 'zabbix.php?action=trigger.list&context=host&filter_set=1&filter_hostids%5B0%5D='
				]
			],
			// #3.
			[
				[
					'object' => 'graph',
					'url' => 'graphs.php?filter_set=1&context=host&filter_hostids%5B0%5D='
				]
			]
		];
	}

	/**
	 * @dataProvider getTestPagesData
	 */
	public function testLowLevelDiscoveryDisabledObjects_TestPages($data) {
		$url = ($data['object'] === 'host') ? $data['url'] : $data['url'].self::$hint_hostid;
		$this->page->login()->open($url)->waitUntilReady();
		$table = $this->query('xpath://form/table')->asTable()->waitUntilVisible()->one();

		$lld_objects = [
			'1 Delete - never, disable - never' => [
				'common_hint' => '/^The '.$data['object'].' is not discovered anymore and will not be disabled,'.
						' will not be deleted\.$/',
				'graph_hint' => '/^The graph is not discovered anymore and will not be deleted\.$/'
			],
			'2 Delete - never, disable - immediately' => [
				'common_hint' => '/^The '.$data['object'].' is not discovered anymore and has been disabled,'.
						' will not be deleted\.$/',
				'graph_hint' => '/^The graph is not discovered anymore and will not be deleted\.$/',
				'disabled' => true
			],
			'3 Delete - never, disable - after 2w' => [
				'common_hint' => "/^The ".$data['object']." is not discovered anymore and will be disabled in".
						" \d{1,2}d( \d{1,2}h( \d{1,2}m)?)?, will not be deleted\.$/",
				'graph_hint' => '/^The graph is not discovered anymore and will not be deleted\.$/'
			],
			'4 Delete - after 52w, disable - never' => [
				'common_hint' => '/^The '.$data['object'].' is not discovered anymore and will not be disabled,'.
						' will be deleted in 1y\.$/',
				'graph_hint' => '/^The graph is not discovered anymore and will be deleted in 1y\.$/'
			],
			'5 Delete - after 7d, disable - immediately' => [
				'common_hint' => "/^The ".$data['object']." is not discovered anymore and has been disabled,".
						" will be deleted in \d{1,2}d( \d{1,2}h( \d{1,2}m)?)?\.$/",
				'graph_hint' => "/^The graph is not discovered anymore and will be deleted in \d{1,2}d( \d{1,2}h( \d{1,2}m)?)?\.$/",
				'disabled' => true
			],
			'6 Delete - after 7d, disable - after 20h' => [
				'common_hint' => "/^The ".$data['object']." is not discovered anymore and will be disabled in".
						" \d{1,2}h( \d{1,2}m( \d{1,2}s)?)?, will be deleted in \d{1,2}d( \d{1,2}h( \d{1,2}m)?)?\.$/",
				'graph_hint' => "/^The graph is not discovered anymore and will be deleted in \d{1,2}d( \d{1,2}h( \d{1,2}m)?)?\.$/"
			],
			'7 Delete - Immediately' => [
				'common_hint' => '/^The '.$data['object'].' is not discovered anymore and will'.
						' (be deleted the next time discovery rule is processed|not be disabled, will be deleted in 0)\.$/',
				'graph_hint' => '/^The graph is not discovered anymore and will'.
						' (be deleted the next time discovery rule is processed|not be disabled, will be deleted in 0)\.$/'
			]
		];

		foreach ($lld_objects as $lld => $hint) {
			$row = $table->findRow('Name', $lld, true);
			$overlay = $this->query('xpath://div[@data-hintboxid]')->asOverlayDialog();

			// Check object status, there is no Status column on Graph page.
			if ($data['object'] !== 'graph') {
				$status_column = $row->getColumn('Status');
				$status = (CTestArrayHelper::get($hint, 'disabled', false)) ? 'Disabled' :'Enabled';
				$this->assertEquals($status, $status_column->getText());
				$alert_button = $status_column->query('xpath:.//button[contains(@class, "zi-alert-with-content")]');

				// Check alert button and alert text if status is disabled.
				if ($status === 'Enabled') {
					$this->assertFalse($alert_button->exists());
				}
				else {
					$alert_button->one()->waitUntilClickable()->click();
					$alert_overlay = $overlay->all()->last()->waitUntilPresent();
					$this->assertEquals('Disabled automatically by an LLD rule.', $alert_overlay->getText());
					$alert_overlay->close();
				}
			}

			// Click button in Info column.
			$row->getColumn('Info')->query('xpath:.//button[contains(@class, "zi-i-warning")]')
					->one()->waitUntilCLickable()->click();
			$hint_overlay = $overlay->all()->last()->waitUntilReady();

			// Hints are different for graphs.
			$hint_text = ($data['object'] === 'graph') ? $hint['graph_hint'] : $hint['common_hint'];

			// Assert hint text for every object depending on LLD configuration.
			$this->assertEquals(1, preg_match($hint_text, $hint_overlay->getText()), 'Hint text "'.
					$hint_overlay->getText().'" does not match with expected "'.$hint_text.'"');
			$hint_overlay->close();
		}
	}
}