<?php
/**
 * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\TableNode;
use PHPUnit\Framework\Assert;

class LDAPContext implements Context {
	use AppConfiguration,
		CommandLine,
		Sharing; // Pulls in BasicStructure

	protected $configID;

	protected $apiUrl;

	/** @AfterScenario */
	public function teardown() {
		if ($this->configID === null) {
			return;
		}
		$this->disableLDAPConfiguration(); # via occ in case of big config issues
		$this->asAn('admin');
		$this->sendingTo('DELETE', $this->apiUrl . '/' . $this->configID);
	}

	/**
	 * @Given /^the response should contain a tag "([^"]*)"$/
	 */
	public function theResponseShouldContainATag($arg1) {
		$configID = simplexml_load_string($this->response->getBody())->data[0]->$arg1;
		Assert::assertInstanceOf(SimpleXMLElement::class, $configID[0]);
	}

	/**
	 * @Given /^creating an LDAP configuration at "([^"]*)"$/
	 */
	public function creatingAnLDAPConfigurationAt($apiUrl) {
		$this->apiUrl = $apiUrl;
		$this->sendingToWith('POST', $this->apiUrl, null);
		$configElements = simplexml_load_string($this->response->getBody())->data[0]->configID;
		$this->configID = $configElements[0];
	}

	/**
	 * @When /^deleting the LDAP configuration$/
	 */
	public function deletingTheLDAPConfiguration() {
		$this->sendingToWith('DELETE', $this->apiUrl . '/' . $this->configID, null);
	}

	/**
	 * @Given /^the response should contain a tag "([^"]*)" with value "([^"]*)"$/
	 */
	public function theResponseShouldContainATagWithValue($tagName, $expectedValue) {
		$data = simplexml_load_string($this->response->getBody())->data[0]->$tagName;
		Assert::assertEquals($expectedValue, $data[0]);
	}

	/**
	 * @When /^getting the LDAP configuration with showPassword "([^"]*)"$/
	 */
	public function gettingTheLDAPConfigurationWithShowPassword($showPassword) {
		$this->sendingToWith(
			'GET',
			$this->apiUrl . '/' . $this->configID . '?showPassword=' . $showPassword,
			null
		);
	}

	/**
	 * @Given /^setting the LDAP configuration to$/
	 */
	public function settingTheLDAPConfigurationTo(TableNode $configData) {
		$this->sendingToWith('PUT', $this->apiUrl . '/' . $this->configID, $configData);
	}

	/**
	 * @Given /^having a valid LDAP configuration$/
	 */
	public function havingAValidLDAPConfiguration() {
		$this->asAn('admin');
		$this->creatingAnLDAPConfigurationAt('/apps/user_ldap/api/v1/config');
		$data = new TableNode([
			['configData[ldapHost]', getenv('LDAP_HOST') ?: 'openldap'],
			['configData[ldapPort]', '389'],
			['configData[ldapBase]', 'dc=nextcloud,dc=ci'],
			['configData[ldapAgentName]', 'cn=admin,dc=nextcloud,dc=ci'],
			['configData[ldapAgentPassword]', 'admin'],
			['configData[ldapUserFilter]', '(&(objectclass=inetorgperson))'],
			['configData[ldapLoginFilter]', '(&(objectclass=inetorgperson)(uid=%uid))'],
			['configData[ldapUserDisplayName]', 'displayname'],
			['configData[ldapGroupDisplayName]', 'cn'],
			['configData[ldapEmailAttribute]', 'mail'],
			['configData[ldapConfigurationActive]', '1'],
		]);
		$this->settingTheLDAPConfigurationTo($data);
		$this->asAn('');
	}

	/**
	 * @Given /^looking up details for the first result matches expectations$/
	 * @param TableNode $expectations
	 */
	public function lookingUpDetailsForTheFirstResult(TableNode $expectations) {
		$userResultElements = simplexml_load_string($this->response->getBody())->data[0]->users[0]->element;
		$userResults = json_decode(json_encode($userResultElements), 1);
		$userId = array_shift($userResults);

		$this->sendingTo('GET', '/cloud/users/' . $userId);
		$this->theRecordFieldsShouldMatch($expectations);
	}

	/**
	 * @Given /^modify LDAP configuration$/
	 */
	public function modifyLDAPConfiguration(TableNode $table) {
		$originalAsAn = $this->currentUser;
		$this->asAn('admin');
		$configData = $table->getRows();
		foreach ($configData as &$row) {
			if (str_contains($row[0], 'Host') && getenv('LDAP_HOST')) {
				$row[1] = str_replace('openldap', getenv('LDAP_HOST'), $row[1]);
			}
			$row[0] = 'configData[' . $row[0] . ']';
		}
		$this->settingTheLDAPConfigurationTo(new TableNode($configData));
		$this->asAn($originalAsAn);
	}

	/**
	 * @Given /^the "([^"]*)" result should match$/
	 */
	public function theGroupResultShouldMatch(string $type, TableNode $expectations) {
		$listReturnedElements = simplexml_load_string($this->response->getBody())->data[0]->$type[0]->element;
		$extractedIDsArray = json_decode(json_encode($listReturnedElements), 1);
		foreach ($expectations->getRows() as $expectation) {
			if ((int)$expectation[1] === 1) {
				Assert::assertContains($expectation[0], $extractedIDsArray);
			} else {
				Assert::assertNotContains($expectation[0], $extractedIDsArray);
			}
		}
	}

	/**
	 * @Given /^Expect ServerException on failed web login as "([^"]*)"$/
	 */
	public function expectServerExceptionOnFailedWebLoginAs($login) {
		try {
			$this->loggingInUsingWebAs($login);
		} catch (\GuzzleHttp\Exception\ServerException $e) {
			Assert::assertEquals(500, $e->getResponse()->getStatusCode());
			return;
		}
		Assert::assertTrue(false, 'expected Exception not received');
	}

	/**
	 * @Given /^the "([^"]*)" result should contain "([^"]*)" of$/
	 */
	public function theResultShouldContainOf($type, $expectedCount, TableNode $expectations) {
		$listReturnedElements = simplexml_load_string($this->response->getBody())->data[0]->$type[0]->element;
		$extractedIDsArray = json_decode(json_encode($listReturnedElements), 1);
		$uidsFound = 0;
		foreach ($expectations->getRows() as $expectation) {
			if (in_array($expectation[0], $extractedIDsArray)) {
				$uidsFound++;
			}
		}
		Assert::assertSame((int)$expectedCount, $uidsFound);
	}

	/**
	 * @Given /^the record's fields should match$/
	 */
	public function theRecordFieldsShouldMatch(TableNode $expectations) {
		foreach ($expectations->getRowsHash() as $k => $v) {
			$value = (string)simplexml_load_string($this->response->getBody())->data[0]->$k;
			Assert::assertEquals($v, $value, "got $value");
		}

		$backend = (string)simplexml_load_string($this->response->getBody())->data[0]->backend;
		Assert::assertEquals('LDAP', $backend);
	}

	public function disableLDAPConfiguration() {
		$configKey = $this->configID . 'ldap_configuration_active';
		$this->invokingTheCommand('config:app:set user_ldap ' . $configKey . ' --value="0"');
	}

	protected function resetAppConfigs() {
		// not implemented
	}
}
