<?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/CIntegrationTest.php';

/**
 * Test suite for changecount() function evaluation.
 *
 * @required-components server
 * @hosts test_host
 * @backup history
 */
class testFunctionChangeCount extends CIntegrationTest {

	const HOST_NAME = 'test_host';
	const WAIT_TIME = 1;
	const FUNC_NAME = 'changecount';

	private static $hostid;
	private static $interfaceid;

	private static $items = [
		'item_ui64' => [
			'key' => 'item_ui64',
			'value_type' => ITEM_VALUE_TYPE_UINT64,
			'triggers' => [
				// All values different, this case is also used to check
				// "not enough data" scenario
				'all_different' => [
					'params' => '#5',
					'expected_result' => 4
				],
				// Some values are equal
				'some_equal' => [
					'params' => '#5',
					'expected_result' => 2
				],
				// "inc" mode
				'inc' => [
					'params' => '#5,"inc"',
					'expected_result' => 2
				],
				// "dec" mode
				'dec' => [
					'params' => '#5,"dec"',
					'expected_result' => 2
				],
				// Explicit "all" mode
				'all' => [
					'params' => '#5,"all"',
					'expected_result' => 3
				],
				// Below scenarios have wrong parameters, trigger creation must fail
				[
					'params' => '#5,"inc","strict"',
					'expected_result' => 0,
					'expected_error' => true
				],
				[
					'params' => '',
					'expected_result' => 0,
					'expected_error' => true
				],
				[
					'params' => '"inc"',
					'expected_result' => 0,
					'expected_error' => true
				],
				[
					'params' => '#20,"strict"',
					'expected_result' => 0,
					'expected_error' => true
				]
			]
		],
		'item_dbl' => [
			'key' => 'item_dbl',
			'value_type' => ITEM_VALUE_TYPE_FLOAT,
			'triggers' => [
				// All values different
				'all_different' => [
					'params' => '#5',
					'expected_result' => 4
				],
				// Some values are equal, explicit "all" mode
				'some_equal' => [
					'params' => '#5,"all"',
					'expected_result' => 3
				],
				// "inc" mode
				'inc' => [
					'params' => '#5,"inc"',
					'expected_result' => 3
				],
				// "dec" mode
				'dec' => [
					'params' => '#5,"dec"',
					'expected_result' => 1
				]
			]
		],
		'item_str' => [
			'key' => 'item_str',
			'value_type' => ITEM_VALUE_TYPE_STR,
			'triggers' => [
				// All values different
				'all_different' => [
					'params' => '#5',
					'expected_result' => 4
				],
				// Some values are equal
				'some_equal' => [
					'params' => '#5',
					'expected_result' => 2
				]
			]
		]
	];

	/**
	 * @inheritdoc
	 */
	public function prepareData() {
		// Create host "test_host"
		$response = $this->call('host.create', [
			[
				'host' => self::HOST_NAME,
				'interfaces' => [
					'type' => 1,
					'main' => 1,
					'useip' => 1,
					'ip' => '127.0.0.1',
					'dns' => '',
					'port' => $this->getConfigurationValue(self::COMPONENT_AGENT, 'ListenPort')
				],
				'groups' => [['groupid' => 4]],
				'status' => HOST_STATUS_NOT_MONITORED
			]
		]);

		$this->assertArrayHasKey('hostids', $response['result']);
		$this->assertArrayHasKey(0, $response['result']['hostids']);
		self::$hostid = $response['result']['hostids'][0];

		// Get host interface ids
		$response = $this->call('host.get', [
			'output' => ['host'],
			'hostids' => [self::$hostid],
			'selectInterfaces' => ['interfaceid']
		]);

		$this->assertArrayHasKey(0, $response['result']);
		$this->assertArrayHasKey('interfaces', $response['result'][0]);
		$this->assertArrayHasKey(0, $response['result'][0]['interfaces']);
		self::$interfaceid = $response['result'][0]['interfaces'][0]['interfaceid'];

		// Create trapper items
		foreach (self::$items as &$item) {
			$items[] = [
				'name' => $item['key'],
				'key_' => $item['key'],
				'value_type' => $item['value_type'],
				'type' => ITEM_TYPE_TRAPPER,
				'hostid' => self::$hostid
			];
		}

		$response = $this->call('item.create', $items);
		$this->assertArrayHasKey('itemids', $response['result']);
		$this->assertEquals(count($items), count($response['result']['itemids']));
		$itemids = $response['result']['itemids'];
		$id = 0;

		foreach (self::$items as &$item) {
			$item['itemid'] = $itemids[$id++];
		}

		// Create triggers
		foreach (self::$items as &$item) {
			foreach ($item['triggers'] as &$trigger) {
				$response = $this->call('trigger.create', [
					[
						'description' => self::FUNC_NAME.'('.$trigger['params'].')',
						'expression' => self::FUNC_NAME.'(/'.self::HOST_NAME.'/'.$item['key'].','.$trigger['params'].')='.$trigger['expected_result']
					]
				], $error = (array_key_exists('expected_error', $trigger)));
				// Check for scenarios when trigger creation must fail
				if (array_key_exists('expected_error', $trigger)) {
					$this->assertArrayHasKey('error', $response);
					continue;
				}

				$this->assertArrayHasKey('triggerids', $response['result']);
				$this->assertEquals(1, count($response['result']['triggerids']));
				$trigger['triggerid'] = $response['result']['triggerids'][0];
			}
		}
	}

	/**
	 * Send values
	 */
	private function sendValues($item, $values) {
		$senderValues = [];

		foreach ($values as &$value) {
			if (!is_scalar($value)) {
				$value = json_encode($value);
			}

			$data = [
				'host' => self::HOST_NAME,
				'key' => $item,
				'value' => $value
			];

			array_push($senderValues, $data);
		}

		$this->sendSenderValues($senderValues);
	}

	/**
	 * Check trigger value
	 */
	private function checkTriggerValue($trigger) {
		$response = $this->call('trigger.get', ['triggerids' => [$trigger['triggerid']]]);
		$this->assertEquals(1, $response['result'][0]['value']);
	}

	public function testFunctionChangeCount_Send_NotEnoughData() {
		$this->sendValues('item_ui64', [64]);
	}

	/**
	 * @depends testFunctionChangeCount_Send_NotEnoughData
	 */
	public function testFunctionChangeCount_Get_NotEnoughData() {
		$response = $this->call('trigger.get',
			['triggerids' => [self::$items['item_ui64']['triggers']['all_different']['triggerid']]]);

		$this->assertArrayHasKey('error', $response['result'][0]);
		$this->assertStringContainsString('not enough data', $response['result'][0]['error']);
	}

	/**
	 * @depends testFunctionChangeCount_Get_NotEnoughData
	 */
	public function testFunctionChangeCount_Send_Ui64ImplicitAll_AllDifferent() {
		$this->sendValues('item_ui64', [64, 24, 69, 32, 96]);
	}

	/**
	 * @depends testFunctionChangeCount_Send_Ui64ImplicitAll_AllDifferent
	 */
	public function testFunctionChangeCount_Get_Ui64ImplicitAll_AllDifferent() {
		$this->checkTriggerValue(self::$items['item_ui64']['triggers']['all_different']);
	}

	public function testFunctionChangeCount_Send_Ui64ImplicitAll_SomeEqual() {
		$this->sendValues('item_ui64', [97, 35, 35, 29, 29]);
	}

	/**
	 * @depends testFunctionChangeCount_Send_Ui64ImplicitAll_SomeEqual
	 */
	public function testFunctionChangeCount_Get_Ui64ImplicitAll_SomeEqual() {
		$this->checkTriggerValue(self::$items['item_ui64']['triggers']['some_equal']);
	}

	public function testFunctionChangeCount_Send_Ui64Inc() {
		$this->sendValues('item_ui64', [21, 34, 73, 55, 46]);
	}

	/**
	 * @depends testFunctionChangeCount_Send_Ui64Inc
	 */
	public function testFunctionChangeCount_Get_Ui64Inc() {
		$this->checkTriggerValue(self::$items['item_ui64']['triggers']['inc']);
	}

	public function testFunctionChangeCount_Send_Ui64Dec() {
		$this->sendValues('item_ui64', [7, 92, 67, 96, 44]);
	}

	/**
	 * @depends testFunctionChangeCount_Send_Ui64Dec
	 */
	public function testFunctionChangeCount_Get_Ui64Dec() {
		$this->checkTriggerValue(self::$items['item_ui64']['triggers']['dec']);
	}

	public function testFunctionChangeCount_Send_Ui64ExplicitAll() {
		$this->sendValues('item_ui64', [55, 93, 76, 76, 33]);
	}

	/**
	 * @depends testFunctionChangeCount_Send_Ui64ExplicitAll
	 */
	public function testFunctionChangeCount_Get_Ui64_ExplicitAll() {
		$this->checkTriggerValue(self::$items['item_ui64']['triggers']['all']);
	}

	public function testFunctionChangeCount_Send_DblImplicitAll() {
		$this->sendValues('item_dbl', [0.000005, 0.000001, 0.000007, 0.000004, 0.000002]);
	}

	/**
	 * @depends testFunctionChangeCount_Send_DblImplicitAll
	 */
	public function testFunctionChangeCount_Get_DblImplicitAll() {
		$this->checkTriggerValue(self::$items['item_dbl']['triggers']['all_different']);
	}

	public function testFunctionChangeCount_Send_DblExplicitAll() {
		$this->sendValues('item_dbl', [0.0001, 0.0011, 0.0011, 0.0002, 0.0005]);
	}

	/**
	 * @depends testFunctionChangeCount_Send_DblExplicitAll
	 */
	public function testFunctionChangeCount_Get_DblExplicitAll() {
		$this->checkTriggerValue(self::$items['item_dbl']['triggers']['some_equal']);
	}

	public function testFunctionChangeCount_Send_DblInc() {
		$this->sendValues('item_dbl', [0.0001, 0.0002, 0.0003, 0.0004, 0.0004]);
	}

	/**
	 * @depends testFunctionChangeCount_Send_DblInc
	 */
	public function testFunctionChangeCount_Get_DblInc() {
		$this->checkTriggerValue(self::$items['item_dbl']['triggers']['inc']);
	}

	public function testFunctionChangeCount_Send_DblDec() {
		$this->sendValues('item_dbl', [0.00001, 0.00002, 0.00003, 0.00004, 0.00003]);
	}

	/**
	 * @depends testFunctionChangeCount_Send_DblDec
	 */
	public function testFunctionChangeCount_Get_DblDec() {
		$this->checkTriggerValue(self::$items['item_dbl']['triggers']['dec']);
	}

	public function testFunctionChangeCount_Send_StrAllDifferent() {
		$this->sendValues('item_str', ['abc', 'def', 'ghi', 'jkl', 'mno']);
	}

	/**
	 * @depends testFunctionChangeCount_Send_StrAllDifferent
	 */
	public function testFunctionChangeCount_Get_StrAllDifferent() {
		$this->checkTriggerValue(self::$items['item_str']['triggers']['all_different']);
	}

	public function testFunctionChangeCount_Send_StrSomeEqual() {
		$this->sendValues('item_str', ['abc', 'abc', 'ghi', 'ghi', 'mno']);
	}

	/**
	 * @depends testFunctionChangeCount_Send_StrSomeEqual
	 */
	public function testFunctionChangeCount_Get_StrSomeEqual() {
		$this->checkTriggerValue(self::$items['item_str']['triggers']['some_equal']);
	}
}
