<?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 alerting for services.
 *
 * @required-components server, proxy
 * @configurationDataProvider configurationProvider
 * @backup hosts, regexps, config_autoreg_tls, globalmacro, auditlog, changelog, ha_node, ids
 */
class testProxyConfSync extends CIntegrationTest
{
	private $expected_initial = [
		'config' =>
		[
			'insert' => '1',
			'update' => '0',
			'delete' => '0'
		],
		'config_autoreg_tls' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'autoreg_host' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'hosts' =>
		[
			'insert' => '15',
			'update' => '0',
			'delete' => '0'
		],
		'host_inventory' =>
		[
			'insert' => '3',
			'update' => '0',
			'delete' => '0'
		],
		'hosts_templates' =>
		[
			'insert' => '4',
			'update' => '0',
			'delete' => '0'
		],
		'globalmacro' =>
		[
			'insert' => '3',
			'update' => '0',
			'delete' => '0'
		],
		'hostmacro' =>
		[
			'insert' => '5',
			'update' => '0',
			'delete' => '0'
		],
		'interface' =>
		[
			'insert' => '15',
			'update' => '0',
			'delete' => '0'
		],
		'items' =>
		[
			'insert' => '78',
			'update' => '0',
			'delete' => '0'
		],
		'item_discovery' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'triggers' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'trigger_depends' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'trigger_tag' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'host_tag' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'item_tag' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'functions' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'regexps' =>
		[
			'insert' => '1',
			'update' => '0',
			'delete' => '0'
		],
		'actions' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'operations' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'conditions' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'correlation' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'corr_condition' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'corr_operation' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'hstgrp' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'item_preproc' =>
		[
			'insert' => '11',
			'update' => '0',
			'delete' => '0'
		],
		'item_parameter' =>
		[
			'insert' => '2',
			'update' => '0',
			'delete' => '0'
		],
		'maintenances' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'drules' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'httptest' =>
		[
			'insert' => '3',
			'update' => '0',
			'delete' => '0'
		],
		'connector' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'connector_tag' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'proxy' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'proxy_group' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'host_proxy' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		]
	];

	private $expected_update = [
		"config" =>
		[
			"insert" =>
			"1",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"config_autoreg_tls" =>
		[
			"insert" =>
			"0",
			"update" =>
			"1",
			"delete" =>
			"0"
		],
		'autoreg_host' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		"hosts" =>
		[
			"insert" =>
			"0",
			"update" =>
			"15",
			"delete" =>
			"0"
		],
		"host_inventory" =>
		[
			"insert" =>
			"0",
			"update" =>
			"1",
			"delete" =>
			"2"
		],
		"hosts_templates" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"globalmacro" =>
		[
			"insert" =>
			"0",
			"update" =>
			"3",
			"delete" =>
			"0"
		],
		"hostmacro" =>
		[
			"insert" =>
			"2",
			"update" =>
			"2",
			"delete" =>
			"2"
		],
		"interface" =>
		[
			"insert" =>
			"0",
			"update" =>
			"8",
			"delete" =>
			"0"
		],
		"items" =>
		[
			"insert" =>
			"0",
			"update" =>
			"49",
			"delete" =>
			"0"
		],
		"item_discovery" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"triggers" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"trigger_depends" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"trigger_tag" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"host_tag" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"item_tag" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"functions" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"regexps" =>
		[
			"insert" =>
			"1",
			"update" =>
			"0",
			"delete" =>
			"1"
		],
		"actions" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"operations" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"conditions" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"correlation" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"corr_condition" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"corr_operation" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"hstgrp" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"item_preproc" =>
		[
			"insert" =>
			"0",
			"update" =>
			"6",
			"delete" =>
			"0"
		],
		"item_parameter" =>
		[
			"insert" =>
			"0",
			"update" =>
			"1",
			"delete" =>
			"0"
		],
		"maintenances" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"drules" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"httptest" =>
		[
			"insert" =>
			"0",
			"update" =>
			"3",
			"delete" =>
			"0"
		],
		'connector' =>
		[
			'insert' =>
			'0',
			'update' =>
			'0',
			'delete' =>
			'0'
		],
		'connector_tag' =>
		[
			'insert' =>
			'0',
			'update' =>
			'0',
			'delete' =>
			'0'
		],
		'proxy' =>
		[
			'insert' =>
			'0',
			'update' =>
			'0',
			'delete' =>
			'0'
		],
		'proxy_group' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'host_proxy' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		]
	];

	private $expected_delete = [
		"config" =>
		[
			"insert" =>
			"1",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"config_autoreg_tls" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		'autoreg_host' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		"hosts" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"15"
		],
		"host_inventory" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"1"
		],
		"hosts_templates" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"4"
		],
		"globalmacro" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"hostmacro" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"5"
		],
		"interface" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"15"
		],
		"items" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"78"
		],
		"item_discovery" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"triggers" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"trigger_depends" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"trigger_tag" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"host_tag" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"item_tag" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"functions" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"regexps" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"1"
		],
		"actions" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"operations" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"conditions" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"correlation" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"corr_condition" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"corr_operation" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"hstgrp" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		"item_preproc" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"11"
		],
		"item_parameter" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"2"
		],
		"maintenances" =>
		[
			"insert" =>
			"0",
			"update" =>
			"0",
			"delete" =>
			"0"
		],
		'drules' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'httptest' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '3'
		],
		'connector' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'connector_tag' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'proxy' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'proxy_group' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		],
		'host_proxy' =>
		[
			'insert' => '0',
			'update' => '0',
			'delete' => '0'
		]
	];

	private static $regexpid;
	private static $vaultmacroid;
	private static $secretmacroid;
	private static $tlshostid;

	/**
	 * @inheritdoc
	 */
	public function prepareData()
	{
		$this->purgeExisting('action', 'actionids');
		$this->purgeExisting('host', 'hostids');
		$this->purgeExisting('proxy', 'extend');
		$this->purgeExisting('template', 'templateids');
		$this->purgeExisting('item', 'itemids');
		$this->purgeExisting('trigger', 'triggerids');
		$this->purgeExisting('regexp', 'extend');
		$this->purgeHostGroups();
		$this->purgeGlobalMacros();

		return true;
	}

	public function createProxy()
	{
		$response = $this->call('proxy.create', [
			'name' => 'Proxy',
			'operating_mode' => PROXY_OPERATING_MODE_PASSIVE,
			'hosts' => [],
			'address' => '127.0.0.1',
			'port' => PHPUNIT_PORT_PREFIX.self::PROXY_PORT_SUFFIX
		]);
		$this->assertArrayHasKey("proxyids", $response['result']);
	}

	/**
	 * Component configuration provider for server related tests.
	 *
	 * @return array
	 */
	public function configurationProvider()
	{
		return [
			self::COMPONENT_SERVER => [
				'LogFileSize' => 0,
				'DebugLevel' => 5,
				'Vault' => 'CyberArk',
				'VaultURL' => 'https://127.0.0.1:1858'
			],
			self::COMPONENT_PROXY => [
				'ProxyMode' => PROXY_OPERATING_MODE_PASSIVE,
				'DebugLevel' => 5,
				'LogFileSize' => 0,
				'Hostname' => 'Proxy',
				'ListenPort' => PHPUNIT_PORT_PREFIX.self::PROXY_PORT_SUFFIX
			]
		];
	}

	private function parseSyncResults()
	{
		$log = file_get_contents(self::getLogPath(self::COMPONENT_PROXY));
		$data = explode("\n", $log);

		$sync_lines = preg_grep('/zbx_dc_sync_configuration.*\([0-9]+\/[0-9]+\/[0-9]+\)\.$/', $data);

		$sync_lines1 = preg_replace(
			[
				"/^\s*[0-9]+:[0-9]+:[0-9]+\.[0-9]+ zbx_dc_sync_configuration\(\) /",
				"/\s+/",
				"/-?[0-9]+bytes/",
				"/:sql:[0-9]+\.[0-9]+sync:[0-9]+\.[0-9]+sec/",
				"/:sql:[0-9]+\.[0-9]+sec/"
			],
			"",
			$sync_lines
		);

		$sync_lines2 = preg_replace(
			[
				"/(\(\))|(\()/",
				"/\)\.|\./"
			],
			[
				":",
				""
			],
			$sync_lines1
		);

		$results = [];

		foreach ($sync_lines2 as $v) {
			$o = explode(":", $v);

			$subject = $o[0];
			$operations = explode("/", $o[1]);

			if (count($operations) < 3) {
				continue;
			}

			$results[$subject] = [
				'insert' => $operations[0],
				'update' => $operations[1],
				'delete' => $operations[2]
			];
		}

		return $results;
	}

	private function assertSyncResults($got, $expected_objects)
	{
		$diff_keys = array_diff_key($expected_objects, $got);

		$this->assertEmpty($diff_keys,
			'following objects are missing from sync log:\n'.var_export(array_keys($diff_keys), true));

		$diff_objects = [];

		foreach ($expected_objects as $obj_name => $obj_val)
		{
			$expected_values = $expected_objects[$obj_name];

			$diff = array_diff_assoc($obj_val, $expected_values);
			if (count($diff) > 0) {
				$failed_obj = [
					'expected' => $expected_values,
					'synced' => $obj_val
				];

				$diff_objects[$obj_name] = $failed_obj;
			}
		}

		$this->assertEmpty($diff_objects, 'different objects were found:\n'.var_export($diff_objects, true));
	}


	private function purgeHostGroups()
	{
		$response = $this->call('hostgroup.get', [
			'output' => 'extend',
			'preservekeys' => true
		]);
		$this->assertArrayHasKey('result', $response);

		$filtered_groups = array_filter($response['result'], function ($obj) {
			return $obj['name'] != 'Discovered hosts';
		});

		$ids = array_keys($filtered_groups);
		if (empty($ids)) {
			return;
		}

		$response = $this->call('hostgroup.delete', $ids);
	}

	private function purgeGlobalMacros()
	{
		$response = $this->call('usermacro.get', [
			'output' => 'extend',
			'globalmacro' => true,
			'preservekeys' => true
		]);
		$this->assertArrayHasKey('result', $response);

		$ids = array_keys($response['result']);
		if (empty($ids)) {
			return;
		}

		$response = $this->call('usermacro.deleteglobal', $ids);
	}

	private function purgeExisting($method, $field_name)
	{
		$params = [
			'output' => $field_name,
			'preservekeys' => true
		];

		$response = $this->call($method . '.get', $params);
		$this->assertArrayHasKey('result', $response);

		$ids = array_keys($response['result']);

		if (empty($ids)) {
			return;
		}

		$response = $this->call($method . '.delete', $ids);
	}

	private function createGlobalMacros()
	{
		$response = $this->call('usermacro.createglobal', [
			'macro' => '{$GLOBDELAY}',
			'value' => '1'
		]);
		$this->assertArrayHasKey('result', $response);
		$this->assertArrayHasKey('globalmacroids', $response['result']);

		$response = $this->call('usermacro.createglobal', [
			'macro' => '{$SECRETMACRO}',
			'value' => '1234567890',
			'type' => 1
		]);
		$this->assertArrayHasKey('result', $response);
		$this->assertArrayHasKey('globalmacroids', $response['result']);
		self::$secretmacroid = $response['result']['globalmacroids'][0];

		$response = $this->call('usermacro.createglobal', [
			'macro' => '{$VAULTMACRO}',
			'value' => 'secret/zabbix:password',
			'type' => 2
		]);
		$this->assertArrayHasKey('result', $response);
		$this->assertArrayHasKey('globalmacroids', $response['result']);
		self::$vaultmacroid = $response['result']['globalmacroids'][0];
	}

	private function updateAutoregistration()
	{
		$response = $this->call('autoregistration.update', [
			'tls_accept' => '3',
			'tls_psk_identity' => 'PSK 001',
			'tls_psk' => '12111595725ac58dd977beef14b97461a7c1045b9a1c923453302c5473193478'
		]);
		$this->assertArrayHasKey('result', $response);
		$this->assertEquals(true, $response['result']);
	}

	private function updateGlobalMacro()
	{
		$response = $this->call('usermacro.get', [
			'output' => 'extend',
			'globalmacro' => 'true'
		]);
		$this->assertArrayHasKey(0, $response['result']);
		$this->assertArrayHasKey('globalmacroid', $response['result'][0]);

		$globalmacroid = $response['result'][0]['globalmacroid'];

		$response = $this->call('usermacro.updateglobal', [
			'globalmacroid' => $globalmacroid,
			'macro' => '{$UU}',
			'value' => 'updated'
		]);
		$this->assertArrayHasKey('globalmacroids', $response['result']);

		$response = $this->call('usermacro.updateglobal', [
			'globalmacroid' => self::$secretmacroid,
			'value' => 'qwerasdfzxcv'
		]);
		$this->assertArrayHasKey('globalmacroids', $response['result']);

		$response = $this->call('usermacro.updateglobal', [
			'globalmacroid' => self::$vaultmacroid,
			'value' => 'secret/zabbix:ZABBIX123'
		]);
		$this->assertArrayHasKey('globalmacroids', $response['result']);
	}

	private function importTemplate($filename)
	{
		$xml = file_get_contents('integration/data/' . $filename);

		$response = $this->call('configuration.import', [
			'format' => 'xml',
			'source' => $xml,
			'rules' => [
				'template_groups' =>
				[
					'updateExisting' => true,
					'createMissing' => true
				],
				'host_groups' =>
				[
					'updateExisting' => true,
					'createMissing' => true
				],
				'templates' =>
				[
					'updateExisting' => true,
					'createMissing' => true
				],
				'valueMaps' =>
				[
					'updateExisting' => true,
					'createMissing' => true,
					'deleteMissing' => false
				],
				'templateDashboards' =>
				[
					'updateExisting' => true,
					'createMissing' => true,
					'deleteMissing' => false
				],
				'templateLinkage' =>
				[
					'createMissing' => true,
					'deleteMissing' => false
				],
				'items' =>
				[
					'updateExisting' => true,
					'createMissing' => true,
					'deleteMissing' => false
				],
				'discoveryRules' =>
				[
					'updateExisting' => true,
					'createMissing' => true,
					'deleteMissing' => false
				],
				'triggers' =>
				[
					'updateExisting' => true,
					'createMissing' => true,
					'deleteMissing' => false
				],
				'graphs' =>
				[
					'updateExisting' => true,
					'createMissing' => true,
					'deleteMissing' => false
				],
				'httptests' =>
				[
					'updateExisting' => true,
					'createMissing' => true,
					'deleteMissing' => false
				]
			]
		]);
	}

	private function importTemplateForUpdate($filename)
	{
		$xml = file_get_contents('integration/data/' . $filename);

		$response = $this->call('configuration.import', [
			'format' => 'xml',
			'source' => $xml,
			'rules' => [
				'template_groups' =>
				[
					'updateExisting' => true,
					'createMissing' => false
				],
				'host_groups' =>
				[
					'updateExisting' => true,
					'createMissing' => false
				],
				'templates' =>
				[
					'updateExisting' => true,
					'createMissing' => false
				],
				'valueMaps' =>
				[
					'updateExisting' => true,
					'createMissing' => false,
					'deleteMissing' => false
				],
				'templateDashboards' =>
				[
					'updateExisting' => true,
					'createMissing' => false,
					'deleteMissing' => false
				],
				'templateLinkage' =>
				[
					'createMissing' => false,
					'deleteMissing' => false
				],
				'items' =>
				[
					'updateExisting' => true,
					'createMissing' => false,
					'deleteMissing' => false
				],
				'discoveryRules' =>
				[
					'updateExisting' => true,
					'createMissing' => false,
					'deleteMissing' => false
				],
				'triggers' =>
				[
					'updateExisting' => true,
					'createMissing' => false,
					'deleteMissing' => false
				],
				'graphs' =>
				[
					'updateExisting' => true,
					'createMissing' => false,
					'deleteMissing' => false
				],
				'httptests' =>
				[
					'updateExisting' => true,
					'createMissing' => false,
					'deleteMissing' => false
				]
			]
		]);
	}

	private function createRegexp()
	{
		$response = $this->call('regexp.create', [
			'name' => 'global regexp test',
			'test_string' => '/boot',
			'expressions' => [
				[
					'expression' => '.*',
					'expression_type' => EXPRESSION_TYPE_FALSE,
					'case_sensitive' => 1
				]
			]
		]);
		$this->assertArrayHasKey("regexpids", $response['result']);
		self::$regexpid = $response['result']['regexpids'][0];
	}

	private function updateRegexp()
	{
		$response = $this->call('regexp.update', [
			'regexpid' => self::$regexpid,
			'test_string' => '/tmp',
			'expressions' => [
				[
					'expression' => '.*a',
					'expression_type' => EXPRESSION_TYPE_TRUE,
					'case_sensitive' => 1
				]
			]
		]);
		$this->assertArrayHasKey("regexpids", $response['result']);
	}

	private function setupTlsForHost()
	{
		$response = $this->call('host.get', [
			'output' => 'hostids',
			'filter' => [
				'host' => ['Host1']
			],
			'preservekeys' => true
		]);
		$this->assertArrayHasKey('result', $response);
		self::$tlshostid = array_key_first($response['result']);

		$response = $this->call('host.update', [
			'hostid' => self::$tlshostid,
			'tls_connect' => HOST_ENCRYPTION_PSK,
			'tls_accept' => HOST_ENCRYPTION_PSK | HOST_ENCRYPTION_CERTIFICATE,
			'tls_issuer' => 'iss',
			'tls_subject' => 'sub',
			'tls_psk_identity' => '2790d1e1781449f8879714a21fb706f9f008910ccf6b7339bb1975bc33e0c449',
			'tls_psk' => '1e07e499695b1c5f8fc1ccb5ee935240ae1b85d0ac0f821c7133aa17852bf7d8'
		]);
		$this->assertArrayHasKey('hostids', $response['result']);
		$this->assertEquals(1, count($response['result']['hostids']));
	}


	public function loadInitialConfiguration()
	{
		$this->createRegexp();
		$this->createGlobalMacros();
		$this->importTemplate('confsync_proxy_tmpl.xml');

		$xml = file_get_contents('integration/data/confsync_proxy_hosts.xml');

		$response = $this->call('configuration.import', [
			'format' => 'xml',
			'source' => $xml,
			'rules' => [
				'host_groups' =>
				[
				'updateExisting' => true,
				'createMissing' => true
				],
				'hosts' =>
				[
				'updateExisting' => true,
				'createMissing' => true
				],
				'valueMaps' =>
				[
				'updateExisting' => true,
				'createMissing' => true,
				'deleteMissing' => false
				],
				'templateLinkage' =>
				[
				'createMissing' => true,
				'deleteMissing' => false
				],
				'items' =>
				[
				'updateExisting' => true,
				'createMissing' => true,
				'deleteMissing' => false
				],
				'discoveryRules' =>
				[
				'updateExisting' => true,
				'createMissing' => true,
				'deleteMissing' => false
				],
				'triggers' =>
				[
				'updateExisting' => true,
				'createMissing' => true,
				'deleteMissing' => false
				],
				'graphs' =>
				[
				'updateExisting' => true,
				'createMissing' => true,
				'deleteMissing' => false
				],
				'httptests' =>
				[
				'updateExisting' => true,
				'createMissing' => true,
				'deleteMissing' => false
				]

			]
		]);

		$this->setupTlsForHost();
	}

	private function disableAllHosts()
	{
		$response = $this->call('host.get', [
			'output' => 'hostid',
			'preservekeys' => true
		]);
		$this->assertArrayHasKey('result', $response);

		$ids = array_keys($response['result']);

		if (empty($ids)) {
			return;
		}

		foreach ($ids as $hostid) {
			$response = $this->call('host.update', [
				'hostid' => $hostid,
				'status' => 1
			]);
		}
	}

	private function updateTlsForHost()
	{
		$response = $this->call('host.update', [
			'hostid' => self::$tlshostid,
			'tls_connect' => HOST_ENCRYPTION_CERTIFICATE,
			'tls_accept' => HOST_ENCRYPTION_CERTIFICATE,
			'tls_issuer' => 'iss',
			'tls_subject' => 'sub'
		]);
		$this->assertArrayHasKey('hostids', $response['result']);
		$this->assertEquals(1, count($response['result']['hostids']));
	}

	/**
	 * @required-components server, proxy
	 */
	public function testProxyConfSync_Insert()
	{
		self::stopComponent(self::COMPONENT_SERVER);
		self::clearLog(self::COMPONENT_SERVER);
		self::stopComponent(self::COMPONENT_PROXY);
		self::clearLog(self::COMPONENT_PROXY);

		$this->purgeExisting('host', 'hostids');
		$this->purgeExisting('proxy', 'extend');
		$this->purgeExisting('template', 'templateids');
		$this->purgeExisting('item', 'itemids');
		$this->purgeExisting('trigger', 'triggerids');
		$this->purgeExisting('regexp', 'extend');
		$this->purgeHostGroups();
		$this->purgeGlobalMacros();
		$this->createProxy();

		self::startComponent(self::COMPONENT_SERVER);
		$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, "End of zbx_dc_sync_configuration()", true, 30, 1);
		self::startComponent(self::COMPONENT_PROXY);
		$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'sending configuration data to proxy "Proxy"', true, 90, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "received configuration data from server", true, 90, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "memory statistics for configuration cache", true, 90, 1);

		self::stopComponent(self::COMPONENT_SERVER);
		self::clearLog(self::COMPONENT_SERVER);
		self::stopComponent(self::COMPONENT_PROXY);
		self::clearLog(self::COMPONENT_PROXY);

		$this->loadInitialConfiguration();

		self::startComponent(self::COMPONENT_SERVER);
		$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, "End of zbx_dc_sync_configuration()", true, 30, 1);
		self::startComponent(self::COMPONENT_PROXY);
		$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'sending configuration data to proxy "Proxy"', true, 90, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "received configuration data from server", true, 90, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "memory statistics for configuration cache", true, 90, 1);

		$got = $this->parseSyncResults();
		$this->assertSyncResults($got, $this->expected_initial);

		return true;
	}

	public function testProxyConfSync_Update()
	{
		$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'sending configuration data to proxy "Proxy"', true, 90, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "received configuration data from server", true, 90, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "memory statistics for configuration cache", true, 90, 1);

		self::stopComponent(self::COMPONENT_SERVER);
		self::clearLog(self::COMPONENT_SERVER);
		self::clearLog(self::COMPONENT_PROXY);

		$this->updateRegexp();
		$this->updateAutoregistration();

		$this->importTemplateForUpdate('confsync_proxy_tmpl_updated.xml');
		$xml = file_get_contents('integration/data/confsync_proxy_hosts_updated.xml');

		$response = $this->call('configuration.import', [
			'format' => 'xml',
			'source' => $xml,
			'rules' => [
				'hosts' => [
					'createMissing' => false,
					'updateExisting' => true
				],
				'items' => [
					'createMissing' => false,
					'updateExisting' => true
				],
				'host_groups' => [
					'createMissing' => false,
					'updateExisting' => true
				],
				'discoveryRules' => [
					'createMissing' => false,
					'updateExisting' => true,
					'deleteMissing' => false
				],
				'httptests' => [
					'createMissing' => false,
					'updateExisting' => true,
					'deleteMissing' => false
				],
				'triggers' => [
					'createMissing' => false,
					'updateExisting' => true,
					'deleteMissing' => false
				],
				'templateLinkage' => [
					'createMissing' => false
				]
			]

		]);

		$this->updateGlobalMacro();
		$this->updateTlsForHost();
		$this->disableAllHosts();

		self::startComponent(self::COMPONENT_SERVER);
		$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, "End of zbx_dc_sync_configuration()", true, 30, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'sending configuration data to proxy "Proxy"', true, 90, 1);

		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "received configuration data from server", true, 90, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "memory statistics for configuration cache", true, 90, 1);

		$got = $this->parseSyncResults();

		// Count of updated interfaces can vary due to errors being treated as configuration changes
		if (array_key_exists('interface', $got)) {
			if ($got['interface']['update'] > 8)
				$got['interface']['update'] = 8;
		}

		$this->assertSyncResults($got, $this->expected_update);

		return true;
	}

	public function testProxyConfSync_Delete()
	{
		$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'sending configuration data to proxy "Proxy"', true, 90, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "received configuration data from server", true, 90, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "memory statistics for configuration cache", true, 90, 1);

		self::stopComponent(self::COMPONENT_SERVER);
		self::clearLog(self::COMPONENT_SERVER);
		self::clearLog(self::COMPONENT_PROXY);

		$this->purgeExisting('maintenance', 'maintenanceids');
		$this->purgeExisting('host', 'hostids');
		$this->purgeExisting('template', 'templateids');
		$this->purgeExisting('correlation', 'correlationids');
		$this->purgeExisting('regexp', 'extend');
		$this->purgeExisting('item', 'itemids');
		$this->purgeHostGroups();

		self::startComponent(self::COMPONENT_SERVER);
		$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, "End of zbx_dc_sync_configuration()", true, 30, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_SERVER, 'sending configuration data to proxy "Proxy"', true, 90, 1);

		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "received configuration data from server", true, 90, 1);
		$this->waitForLogLineToBePresent(self::COMPONENT_PROXY, "memory statistics for configuration cache", true, 90, 1);

		$got = $this->parseSyncResults();
		$this->assertSyncResults($got, $this->expected_delete);

		return true;
	}
}
