<?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/CMessageBehavior.php';
require_once dirname(__FILE__).'/../behaviors/CTableBehavior.php';

/**
 * @backup maintenances
 *
 * @onBefore prepareMaintenanceData
 *
 * @dataSource HostGroups
 */
class testPageMaintenance extends CWebTest {

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

	const MAINTENANCE_SQL = 'SELECT * FROM maintenances ORDER BY maintenanceid';
	const APPROACHING_MAINTENANCE = 'Approaching maintenance';
	const HOST_MAINTENANCE = 'Maintenance with assigned host';
	const MULTIPLE_GROUPS_MAINTENANCE = 'Maintenance with 2 host groups';
	const FILTER_NAME_MAINTENANCE = 'Maintenance для фильтра - ʍąɨɲţ€ɲąɲc€🙂';
	const ACTIVE_MAINTENANCE = 'Active maintenance';
	const DESCRIPTION_MAINTENANCE = 'Description maintenance';

	public function prepareMaintenanceData() {
		CDataHelper::call('maintenance.create', [
			[
				'name' => self::APPROACHING_MAINTENANCE,
				'maintenance_type' => MAINTENANCE_TYPE_NODATA,
				'active_since' => 2017008000,
				'active_till' => 2019600000,
				'groups' => [
					[
						'groupid' => CDataHelper::get('HostGroups.Group for Maintenance')
					]
				],
				'timeperiods' => [[]]
			],
			[
				'name' => self::MULTIPLE_GROUPS_MAINTENANCE,
				'maintenance_type' => MAINTENANCE_TYPE_NODATA,
				'active_since' => 1388534400,
				'active_till' => 1420070400,
				'groups' => [
					[
						'groupid' => 4
					],
					[
						'groupid' => 5 // "Discovered hosts" group
					]
				],
				'timeperiods' => [[]]
			],
			[
				'name' => self::HOST_MAINTENANCE,
				'maintenance_type' => MAINTENANCE_TYPE_NORMAL,
				'active_since' => 1577836800,
				'active_till' => 1577923200,
				'hosts' => [
					[
						'hostid' => 10084
					]
				],
				'timeperiods' => [[]]
			],
			[
				'name' => self::FILTER_NAME_MAINTENANCE,
				'maintenance_type' =>  MAINTENANCE_TYPE_NORMAL,
				'active_since' => 1686009600,
				'active_till' => 1688601600,
				'groups' => [
					[
						'groupid' => 4
					]
				],
				'timeperiods' => [[]]
			],
			[
				'name' => self::ACTIVE_MAINTENANCE,
				'maintenance_type' => MAINTENANCE_TYPE_NORMAL,
				'active_since' => 1688601600,
				'active_till' => 2019600000,
				'groups' => [
					[
						'groupid' => 4
					]
				],
				'timeperiods' => [[]]
			],
			[
				'name' => self::DESCRIPTION_MAINTENANCE,
				'maintenance_type' => MAINTENANCE_TYPE_NORMAL,
				'active_since' => 1640995200,
				'active_till' => 1640998800,
				'description' => 'Test description of the maintenance',
				'groups' => [
					[
						'groupid' => 4
					]
				],
				'timeperiods' => [[]]
			]
		]);
	}

	public function getMaintenanceData() {
		return [
			[
				[
					[
						'Name' => 'Active maintenance',
						'Type' => 'With data collection',
						'Active since' => '2023-07-06 03:00',
						'Active till' => '2033-12-31 02:00',
						'State' => 'Active',
						'Description' => ''
					],
					[
						'Name' => 'Approaching maintenance',
						'Type' => 'No data collection',
						'Active since' => '2033-12-01 02:00',
						'Active till' => '2033-12-31 02:00',
						'State' => 'Approaching',
						'Description' => ''
					],
					[
						'Name' => 'Description maintenance',
						'Type' => 'With data collection',
						'Active since' => '2022-01-01 02:00',
						'Active till' => '2022-01-01 03:00',
						'State' => 'Expired',
						'Description' => 'Test description of the maintenance'
					],
					[
						'Name' => 'Maintenance with 2 host groups',
						'Type' => 'No data collection',
						'Active since' => '2014-01-01 02:00',
						'Active till' => '2015-01-01 02:00',
						'State' => 'Expired',
						'Description' => ''
					],
					[
						'Name' => 'Maintenance with assigned host',
						'Type' => 'With data collection',
						'Active since' => '2020-01-01 02:00',
						'Active till' => '2020-01-02 02:00',
						'State' => 'Expired',
						'Description'=> ''
					],
					[
						'Name' => 'Maintenance для фильтра - ʍąɨɲţ€ɲąɲc€🙂',
						'Type' => 'With data collection',
						'Active since' => '2023-06-06 03:00',
						'Active till' => '2023-07-06 03:00',
						'State' => 'Expired',
						'Description' => ''
					]
				]
			]
		];
	}

	/**
	 * @dataProvider getMaintenanceData
	 */
	public function testPageMaintenance_Layout($data) {
		$maintenances = CDBHelper::getCount(self::MAINTENANCE_SQL);
		$this->page->login()->open('maintenance.php')->waitUntilReady();
		$this->page->assertTitle('Configuration of maintenance periods');
		$this->page->assertHeader('Maintenance periods');

		// Check buttons.
		$this->assertEquals(4, $this->query('button', ['Create maintenance period', 'Apply', 'Reset', 'Select'])
				->all()->filter(CElementFilter::CLICKABLE)->count()
		);
		$this->assertFalse($this->query('button', 'Delete')->one()->isEnabled());

		// Check rows in the table.
		$this->assertTableHasData($data);

		// Get filter element.
		$filter = CFilterElement::find()->one();
		$form = $filter->getForm();

		// Check filter expanding/collapsing.
		$this->assertTrue($filter->isExpanded());
		foreach ([false, true] as $state) {
			$filter->expand($state);
			// Leave the page and reopen the previous page to make sure the filter state is still saved.
			$this->page->open('zabbix.php?action=host.list')->waitUntilReady();
			$this->page->open('maintenance.php')->waitUntilReady();
			$this->assertTrue($filter->isExpanded($state));
		}

		$this->assertEquals(['Host groups', 'Name', 'State'], $form->getLabels()->asText());
		$this->assertEquals('type here to search', $form->getField('id:filter_groups__ms')
				->getAttribute('placeholder')
		);
		$this->assertEquals(255, $form->getField('Name')->getAttribute('maxlength'));
		$this->assertEquals(['Any', 'Active', 'Approaching', 'Expired'], $form->getField('State')->getLabels()
				->asText()
		);
		$form->checkValue(['Host groups' => '', 'Name' => '', 'State' => 'Any']);

		// Check table headers and sortable headers.
		$table = $this->getTable();
		$this->assertEquals(['Name', 'Type', 'Active since', 'Active till'], $table->getSortableHeaders()->asText());
		$this->assertEquals(['', 'Name', 'Type', 'Active since', 'Active till', 'State', 'Description'],
				$table->getHeadersText()
		);

		// Check the selected amount.
		$this->assertTableStats($maintenances);
		$this->assertSelectedCount(0);
		$this->selectTableRows();
		$this->assertSelectedCount($maintenances);

		// Check that delete button became clickable.
		$this->assertTrue($this->query('button:Delete')->one()->isClickable());

		// Reset filter and check that maintenances are unselected.
		$form->query('button:Reset')->one()->click();
		$this->page->waitUntilReady();
		$this->assertSelectedCount(0);
	}

	public function getFilterData() {
		return [
			// #0 View results for one host group.
			[
				[
					'filter' => [
						'Host groups' => 'Discovered hosts'
					],
					'expected' => [
						self::MULTIPLE_GROUPS_MAINTENANCE
					]
				]
			],
			// #1 View results for two host groups.
			[
				[
					'filter' => [
						'Host groups' => ['Discovered hosts', 'Zabbix servers']
					],
					'expected' => [
						self::ACTIVE_MAINTENANCE,
						self::DESCRIPTION_MAINTENANCE,
						'Maintenance for update (data collection)',
						'Maintenance period 1 (data collection)',
						'Maintenance period 2 (no data collection)',
						self::MULTIPLE_GROUPS_MAINTENANCE,
						self::HOST_MAINTENANCE,
						self::FILTER_NAME_MAINTENANCE
					]
				]
			],
			// #2 Name with empty spaces.
			[
				[
					'filter' => [
						'Name' => ' '
					],
					'expected' => [
						self::ACTIVE_MAINTENANCE,
						self::APPROACHING_MAINTENANCE,
						self::DESCRIPTION_MAINTENANCE,
						'Maintenance for Host availability widget',
						'Maintenance for host group testing',
						'Maintenance for suppression test',
						'Maintenance for update (data collection)',
						'Maintenance period 1 (data collection)',
						'Maintenance period 2 (no data collection)',
						self::MULTIPLE_GROUPS_MAINTENANCE,
						self::HOST_MAINTENANCE,
						self::FILTER_NAME_MAINTENANCE
					]
				]
			],
			// #3 Name with special symbols.
			[
				[
					'filter' => [
						'Name' => 'ʍąɨɲţ€ɲąɲc€🙂'
					],
					'expected' => [
						self::FILTER_NAME_MAINTENANCE
					]
				]
			],
			// #4 Search by description.
			[
				[
					'filter' => [
						'Name' => 'Test description of the maintenance'
					]
				]
			],
			// #5 State - Active.
			[
				[
					'filter' => [
						'State' => 'Active'
					],
					'expected' => [
						self::ACTIVE_MAINTENANCE,
						'Maintenance for Host availability widget',
						'Maintenance for suppression test'
					]
				]
			],
			// #6 State - Approaching.
			[
				[
					'filter' => [
						'State' => 'Approaching'
					],
					'expected' => [
						self::APPROACHING_MAINTENANCE
					]
				]
			],
			// #7 State - Expired.
			[
				[
					'filter' => [
						'State' => 'Expired'
					],
					'expected' => [
						self::DESCRIPTION_MAINTENANCE,
						'Maintenance for host group testing',
						'Maintenance for update (data collection)',
						'Maintenance period 1 (data collection)',
						'Maintenance period 2 (no data collection)',
						self::MULTIPLE_GROUPS_MAINTENANCE,
						self::HOST_MAINTENANCE,
						self::FILTER_NAME_MAINTENANCE
					]
				]
			],
			// #8 State - Any.
			[
				[
					'filter' => [
						'State' => 'Any'
					],
					'expected' => [
						self::ACTIVE_MAINTENANCE,
						self::APPROACHING_MAINTENANCE,
						self::DESCRIPTION_MAINTENANCE,
						'Maintenance for Host availability widget',
						'Maintenance for host group testing',
						'Maintenance for suppression test',
						'Maintenance for update (data collection)',
						'Maintenance period 1 (data collection)',
						'Maintenance period 2 (no data collection)',
						self::MULTIPLE_GROUPS_MAINTENANCE,
						self::HOST_MAINTENANCE,
						self::FILTER_NAME_MAINTENANCE
					]
				]
			],
			// #9 Combined filters.
			[
				[
					'filter' => [
						'Name' => 'Host',
						'State' => 'Expired',
						'Host groups' => 'Zabbix servers'
					],
					'expected' => [
						self::MULTIPLE_GROUPS_MAINTENANCE,
						self::HOST_MAINTENANCE
					]
				]
			]
		];
	}

	/**
	 * @dataProvider getFilterData
	 */
	public function testPageMaintenance_Filter($data) {
		$this->page->login()->open('maintenance.php?sort=name&sortorder=ASC');
		$form = CFilterElement::find()->one()->getForm();

		// Fill filter fields if such present in data provider.
		$form->fill(CTestArrayHelper::get($data, 'filter'));
		$form->submit();
		$this->page->waitUntilReady();

		// Check that expected maintenances are returned in the list.
		$this->assertTableDataColumn(CTestArrayHelper::get($data, 'expected', []));

		// Check the displaying amount.
		$this-> assertTableStats(count(CTestArrayHelper::get($data, 'expected', [])));

		// Reset filter to not influence further tests.
		$this->query('button:Reset')->one()->click();
	}

	public function testPageMaintenance_Sort() {
		$this->page->login()->open('maintenance.php?sort=name&sortorder=DESC');
		$table = $this->getTable();

		foreach (['Name', 'Active since', 'Active till'] as $column) {
			$values = $this->getTableColumnData($column);
			natcasesort($values);

			if ($column === 'Type') {
				$values = array_reverse($values);
			}

			foreach ([$values, array_reverse($values)] as $sorted_values) {
				$table->query('link', $column)->waitUntilClickable()->one()->click();
				$table->waitUntilReloaded();
				$this->assertTableDataColumn($sorted_values, $column);
			}
		}
	}

	public function testPageMaintenance_CancelOneDelete() {
		$this->cancelDelete([self::ACTIVE_MAINTENANCE]);
	}

	public function testPageMaintenance_CancelMassDelete() {
		$this->cancelDelete();
	}

	public function getDeleteData() {
		return [
			// Delete 1 maintenance.
			[
				[
					'expected' => TEST_GOOD,
					'name' => [self::APPROACHING_MAINTENANCE]
				]
			],
			// Delete 2 maintenances.
			[
				[
					'expected' => TEST_GOOD,
					'name' => [self::MULTIPLE_GROUPS_MAINTENANCE, self::HOST_MAINTENANCE]
				]
			],
			// Delete all maintenances.
			[
				[
					'expected' => TEST_GOOD
				]
			]
		];
	}

	/**
	 * @dataProvider getDeleteData
	 */
	public function testPageMaintenance_Delete($data) {
		$this->page->login()->open('maintenance.php');
		// Maintenance count that will be selected before delete action.
		$this->selectTableRows(CTestArrayHelper::get($data, 'name'));
		$this->query('button:Delete')->one()->waitUntilClickable()->click();
		$this->page->acceptAlert();
		$this->page->waitUntilReady();
		$this->assertMessage(TEST_GOOD, 'Maintenance deleted');
		$this->assertSelectedCount(0);

		$all = CDBHelper::getCount(self::MAINTENANCE_SQL);
		$db_check = (count(CTestArrayHelper::get($data, 'name', [])) > 0)
				? CDBHelper::getCount('SELECT NULL FROM maintenances WHERE name IN ('.CDBHelper::escape($data['name']).')')
				: $all;
		$this->assertEquals(0, $db_check);

		$this->assertTableStats($all);
	}

	protected function cancelDelete($maintenances = []) {
		$old_hash = CDBHelper::getHash(self::MAINTENANCE_SQL);
		// Maintenance count that will be selected before delete action.
		$maintenance_count = ($maintenances === []) ? CDBHelper::getCount(self::MAINTENANCE_SQL) : count($maintenances);

		$this->page->login()->open('maintenance.php');
		$this->selectTableRows($maintenances);
		$this->query('button:Delete')->one()->waitUntilClickable()->click();
		$this->assertEquals('Delete selected maintenance periods?', $this->page->getAlertText());
		$this->page->dismissAlert();
		$this->page->waitUntilReady();
		$this->assertSelectedCount($maintenance_count);
		$this->assertEquals($old_hash, CDBHelper::getHash(self::MAINTENANCE_SQL));
	}
}