<?php
/*
** Zabbix
** 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 General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** 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 General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/


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

/**
 * @backup hosts
 *
 * @onBefore prepareData
 */
class testPageMonitoringWebDetails extends CWebTest {

	/**
	 * Attach TableBehavior to the test.
	 *
	 * @return array
	 */
	public function getBehaviors() {
		return [CTableBehavior::class];
	}

	protected static $host_id;
	protected static $httptest_id;

	public function prepareData() {
		$response = CDataHelper::createHosts([
			[
				'host' => 'Host for web scenarios',
				'groups' => [
					'groupid' => 6
				]
			]
		]);
		self::$host_id = $response['hostids']['Host for web scenarios'];

		$response = CDataHelper::call('httptest.create', [
			[
				'name' => 'Layout',
				'hostid' => self::$host_id,
				'steps' => [
					[
						'name' => 'Step 1',
						'url' => 'http://example.com',
						'no' => 1
					]
				]
			]
		]);
		self::$httptest_id = $response['httptestids'][0];
	}

	/**
	 * Test the general layout.
	 */
	public function testPageMonitoringWebDetails_Layout() {
		$this->page->login()->open('httpdetails.php?httptestid='.self::$httptest_id)->waitUntilReady();

		// Assert title and header.
		$this->page->assertTitle('Details of web scenario');
		$this->page->assertHeader('Details of web scenario: Layout');

		// Assert table column names.
		$this->assertEquals(['Step', 'Speed', 'Response time', 'Response code', 'Status'],
				$this->query('class:list-table')->asTable()->one()->getHeadersText()
		);

		// Check filter layout and values.
		$form = $this->query('name:zbx_filter')->asForm()->one();
		$form->checkValue(['id:from' => 'now-1h', 'id:to' => 'now']);
		$this->assertEquals('selected', $form->query('link:Last 1 hour')->one()->getAttribute('class'));
		$buttons = [
			'xpath://button[contains(@class, "btn-time-left")]' => true,
			'xpath://button[contains(@class, "btn-time-right")]' => false,
			'button:Zoom out' => true,
			'button:Apply' => true,
			'id:from_calendar' => true,
			'id:to_calendar' => true
		];

		foreach ($buttons as $selector => $enabled) {
			$this->assertTrue($this->query($selector)->one()->isEnabled($enabled));
		}

		// Check that filter is expanded by default.
		$filter_tab = CFilterElement::find()->one()->setContext(CFilterElement::CONTEXT_RIGHT);
		$this->assertTrue($filter_tab->isExpanded());

		// Check that filter is collapsing/expanding on click.
		foreach ([false, true] as $status) {
			$filter_tab->expand($status);
			$this->assertTrue($filter_tab->isExpanded($status));
		}
	}

	public function getCheckFiltersData() {
		return [
			[
				[
					'fields' => ['id:from' => 'now-2h', 'id:to' => 'now-1h'],
					'expected' => 'from=now-2h&to=now-1h',
					'zoom_buttons' => [
						'btn-time-left' => true,
						'btn-time-out' => true,
						'btn-time-right' => true
					]
				]
			],
			[
				[
					'fields' => ['id:from' => 'now-2y', 'id:to' => 'now-1y'],
					'expected' => 'from=now-2y&to=now-1y',
					'zoom_buttons' => [
						'btn-time-left' => true,
						'btn-time-out' => true,
						'btn-time-right' => true
					]
				]
			],
			[
				[
					'link' => 'Last 30 days',
					'expected' => 'from=now-30d&to=now',
					'zoom_buttons' => [
						'btn-time-left' => true,
						'btn-time-out' => true,
						'btn-time-right' => false
					]
				]
			],
			[
				[
					'link' => 'Last 2 years',
					'expected' => 'from=now-2y&to=now',
					'zoom_buttons' => [
						'btn-time-left' => true,
						'btn-time-out' => false,
						'btn-time-right' => false
					]
				]
			]
		];
	}

	/**
	 * Change values in the filter section and check the resulting changes in graphs.
	 *
	 * @dataProvider getCheckFiltersData
	 */
	public function testPageMonitoringWebDetails_CheckFilters($data) {
		$this->page->login()->open('httpdetails.php?httptestid='.self::$httptest_id)->waitUntilReady();
		$form = $this->query('name:zbx_filter')->asForm()->one();

		// Set custom time filter.
		if (CTestArrayHelper::get($data, 'fields')) {
			$form->fill($data['fields']);
		}
		else {
			$form->query('link', $data['link'])->waitUntilClickable()->one()->click();
		}

		$graph = $this->query('id:graph_in')->one();
		$form->fill(CTestArrayHelper::get($data, 'fields', 'link'));
		$form->query('id:apply')->one()->click();
		$graph->waitUntilReloaded();

		foreach (['graph_in', 'graph_time'] as $graph_id) {
			$this->assertStringContainsString($data['expected'], $this->query('id', $graph_id)
					->one()->getAttribute('src')
			);
		}

		// Check Zoom buttons.
		foreach ($data['zoom_buttons'] as $button => $state) {
			$this->assertTrue($this->query('xpath://button[@class='.CXPathHelper::escapeQuotes($button).
					']')->one()->isEnabled($state)
			);
		}
	}

	/**
	 * Open and test the Kiosk mode.
	 */
	public function testPageMonitoringWebDetails_CheckKioskMode() {
		$this->page->login()->open('httpdetails.php?httptestid='.self::$httptest_id)->waitUntilReady();

		// Test Kiosk mode.
		$this->query('xpath://button[@title="Kiosk mode"]')->one()->click();
		$this->page->waitUntilReady();

		// Check that Header and Filter disappeared.
		$this->query('xpath://h1[@id="page-title-general"]')->waitUntilNotVisible();
		$this->assertFalse($this->query('xpath://div[@aria-label="Filter"]')->exists());
		$this->assertTrue($this->query('class:list-table')->exists());

		$this->query('xpath://button[@title="Normal view"]')->waitUntilPresent()->one()->click(true);
		$this->page->waitUntilReady();

		// Check that Header and Filter are visible again.
		$this->query('xpath://h1[@id="page-title-general"]')->waitUntilVisible();
		$this->assertTrue($this->query('xpath://div[@aria-label="Filter"]')->exists());
		$this->assertTrue($this->query('class:list-table')->exists());
	}

	public function getDisplayTableData() {
		return [
			[
				[
					'name' => 'Many steps',
					'steps' => array_fill(0, 50, [])
				]
			],
			[
				[
					'name' => 'TEST ЗАББИКС !@#$%-() 🙂🙃 <br/> &nbsp;',
					'steps' => [
						['name' => 'test ēõšŗ тест 测试 テスト δοκιμή'],
						['name' => '!@#$%^&*_+\\/()[]{}<>🙂🙃'],
						['name' => '<script>window.onload=function(){alert("hi!");}</script>']
					]
				]
			],
			[
				[
					'name' => ' 	Test whitespace 	',
					'steps' => [
						['name' => '	Tabs	'],
						['name' => ' Spaces '],
						['name' => "\nNewline\n"]
					]
				]
			],
			[
				[
					'name' => STRING_64,
					'steps' => [
						['name' => STRING_64]
					]
				]
			],
			[
				[
					'name' => 'Result - OK',
					'global_item_data' => [
						HTTPSTEP_ITEM_TYPE_LASTSTEP => 0
					],
					'expected_totals' => ['Response time' => '16m 39s 123.46ms', 'Status' => 'OK'],
					'steps' => [
						[
							'item_data' => [
								HTTPSTEP_ITEM_TYPE_RSPCODE => 200,
								HTTPSTEP_ITEM_TYPE_TIME => 0.123456,
								HTTPSTEP_ITEM_TYPE_IN => 3000
							],
							'expected_data' => [
								'Speed' => '2.93 KBps',
								'Response time' => '123.46ms',
								'Response code' => '200',
								'Status' => 'OK'
							]
						],
						[
							'item_data' => [
								HTTPSTEP_ITEM_TYPE_RSPCODE => 404,
								HTTPSTEP_ITEM_TYPE_TIME => 999,
								HTTPSTEP_ITEM_TYPE_IN => 1099511627776
							],
							'expected_data' => [
								'Speed' => '1 TBps',
								'Response time' => '16m 39s',
								'Response code' => '404',
								'Status' => 'OK'
							]
						]
					]
				]
			],
			[
				[
					'name' => 'Result - Empty',
					'expected_totals' => ['Status' => ''],
					'steps' => [
						['expected_data' => ['Status' => '']]
					]
				]
			],
			[
				[
					'name' => 'Result - Unknown error',
					'global_item_data' => [
						HTTPSTEP_ITEM_TYPE_LASTSTEP => 1
					],
					'expected_totals' => ['Status' => 'Unknown error'],
					'steps' => [
						['expected_data' => ['Status' => 'OK']],
						['expected_data' => ['Status' => 'Unknown error']],
						['expected_data' => ['Status' => 'Unknown']],
						['expected_data' => ['Status' => 'Unknown']]
					]
				]
			],
			[
				[
					'name' => 'Result - Error',
					'global_item_data' => [
						HTTPSTEP_ITEM_TYPE_LASTSTEP => 1,
						HTTPSTEP_ITEM_TYPE_LASTERROR => 'TEST ERROR TEXT 🙂🙃'
					],
					'expected_totals' => ['Status' => 'Error: TEST ERROR TEXT 🙂🙃'],
					'steps' => [
						['expected_data' => ['Status' => 'OK']],
						['expected_data' => ['Status' => 'Error: TEST ERROR TEXT 🙂🙃']],
						['expected_data' => ['Status' => 'Unknown']],
						['expected_data' => ['Status' => 'Unknown']]
					]
				]
			]
		];
	}

	/**
	 * Test the display of data in the table.
	 * Additional complexity comes from the Status column, as the displayed values there are calculated on the fly.
	 *
	 * @dataProvider getDisplayTableData
	 */
	public function testPageMonitoringWebDetails_DisplayTable($data) {
		// Fill in step data so that a web scenario can be created with API.
		$api_steps = [];
		foreach ($data['steps'] as $i => $step){
			$api_step['name'] = $step['name'] ?? 'Step '.$i + 1;
			$api_step['url'] = 'http://example.com';
			$api_step['no'] = $i;
			$api_steps[] = $api_step;
		}

		// Create the web scenario with API.
		$response = CDataHelper::call('httptest.create', [
			[
				'name' => $data['name'],
				'hostid' => self::$host_id,
				'steps' => $api_steps
			]
		]);
		$httptest_id = $response['httptestids'][0];

		// Generate data for global web scenario items.
		foreach ($data['global_item_data'] ?? [] as $data_type => $data_value) {
			// Gets id of the correct item.
			$sql = 'SELECT ti.itemid FROM httptestitem ti'.
					' JOIN items i ON ti.itemid=i.itemid'.
					' WHERE ti.httptestid='.$httptest_id.
						' AND ti.type='.$data_type;
			$item_id = CDBHelper::getValue($sql);
			CDataHelper::addItemData($item_id, $data_value);
		}

		// Generate data for step items.
		foreach ($data['steps'] as $i => $step) {
			// Each step has several item types.
			foreach ($step['item_data'] ?? [] as $data_type => $data_value) {
				// Gets id of the correct item.
				$sql = 'SELECT si.itemid FROM httpstepitem si'.
						' JOIN httpstep s ON si.httpstepid=s.httpstepid'.
						' JOIN httptest t ON s.httptestid=t.httptestid'.
						' WHERE t.httptestid='.$httptest_id.
							' AND s.no='.$i.
							' AND si.type='.$data_type;
				$item_id = CDBHelper::getValue($sql);
				CDataHelper::addItemData($item_id, $data_value);
			}
		}

		$this->page->login()->open('httpdetails.php?httptestid='.$httptest_id)->waitUntilReady();

		// Assert header.
		$this->page->assertHeader('Details of web scenario: '.trim($data['name']));

		// Assert data table.
		$expected_rows = [];
		foreach ($data['steps'] as $i => $step) {
			// Whitespace at the beginning and end should not be displayed.
			$expected_row = ['Step' => trim($step['name'] ?? 'Step '.$i + 1)];
			$expected_row = array_merge($expected_row, $step['expected_data'] ?? []);
			$expected_rows[] = $expected_row;
		}

		// The table contains an additional TOTAL row.
		$expected_rows[] = array_merge(['Step' => 'TOTAL'], $data['expected_totals'] ?? []);
		$this->assertTableData($expected_rows);
	}
}