<?php

namespace StevenBuehner\sbChurchtoolsGrouphomepage\Service;

use StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\Firebase\JWT\JWT;
use StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\Firebase\JWT\Key;
use StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\Nette\Mail\Message;
use StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\Nette\Mail\SendException;
use StevenBuehner\ChurchTools\Api\GroupApi;
use StevenBuehner\ChurchTools\Api\MasterDataApi;
use StevenBuehner\ChurchTools\Api\PermissionApi;
use StevenBuehner\ChurchTools\Api\PersonApi;
use StevenBuehner\ChurchTools\ApiException;
use StevenBuehner\ChurchTools\Configuration;
use StevenBuehner\ChurchTools\Model\CreateOrUpdateMemberRequest;
use StevenBuehner\ChurchTools\Model\CreatePersonRequest;
use StevenBuehner\ChurchTools\Model\CreatePersonRequestPrivacyPolicyAgreement;
use StevenBuehner\ChurchTools\Model\Person;
use StevenBuehner\ChurchTools\Model\PutCheckinPersons200ResponseDataEmailsInner;
use StevenBuehner\ChurchToolsApi\ChurchToolsClientInterface;
use StevenBuehner\sbChurchtoolsGrouphomepage\Always\MyContainer;
use StevenBuehner\sbChurchtoolsGrouphomepage\Controller\NewsletterSettingsController;
use StevenBuehner\sbChurchtoolsGrouphomepage\Service\Models\NewsletterBlockSettings;

class CtNewsletterService implements NewsletterServiceInterface {

	const STATUS_TO_DELETE = 'to_delete';
	const STATUS_ACTIVE    = 'active';
	const STATUS_REQUESTED = 'requested';

	const TEMPLATE_DIR = __DIR__ . '/templates/';
	protected $client;
	protected $config;

	protected $groupApi                     = NULL;
	protected $personsApi                   = NULL;
	protected $masterdataPerson             = NULL;
	protected $openSslKey                   = NULL;
	protected $globalPermissions            = NULL;
	protected $allNewsletterGroupCandidates = [];

	protected $tokensExpireAfter = '+6 hours';

	public function __construct(ChurchToolsClientInterface $client, Configuration $config, array $openSslKey) {
		$this->client     = $client;
		$this->config     = $config;
		$this->openSslKey = $openSslKey;
	}

	public function getChurchToolsPrivacyUrl(): string {

		$fullApiUrl = $this->config->getHost();
		$urlParams  = parse_url($fullApiUrl);

		if (isset($urlParams['scheme']) && isset($urlParams†['host'])) {
			return $urlParams['scheme'] . '://' . $urlParams['host'] . '/dataprivacy';
		}

		return '';

	}

	/**
	 * @param int|array $groupTypeId
	 * @return array
	 * @throws \StevenBuehner\ChurchTools\ApiException
	 */
	public function getAllNewsletterGroupCandidates($groupTypeId = NULL): array {

		if (empty($groupTypeId)) {
			$groupTypeId = NULL;
		}

		$hashKey = $groupTypeId;

		if (!isset($this->allNewsletterGroupCandidates[$hashKey])) {

			$this->client->login();

			$groups = $this
				->getGroupApi()
				->getGroups(NULL, NULL, NULL, NULL, NULL, NULL, NULL, $groupTypeId, NULL, NULL, FALSE, FALSE, NULL, NULL, NULL, 1, 100)
				->getData();

			/** @var InlineResponse20017Data $g */
			$this->allNewsletterGroupCandidates[$hashKey] = array_map(function ($g) {

				$temp = [
					'id'          => $g->getId(),
					'name'        => $g->getName(),
					'campus'      => $g->getInformation()->getCampusId(),
					'groupTypeId' => $g->getInformation()->getGroupTypeId(),
				];

				if ($temp['campus'] !== NULL) {
					$temp['campus'] = $this->getCampus($temp['campus'])->getName();
				}

				return $temp;

			}, $groups);
		}

		return $this->allNewsletterGroupCandidates[$hashKey];

	}

	public function getAllGroupTypes(): array {

		$allTypes = $this->getGroupTypes();

		/** @var InlineResponse20043Data $g */
		return array_map(function ($g) {
			return [
				'id'    => $g->getId(),
				'name'  => $g->getName(),
				'roles' => array_map(function ($t) {

					return [
						'id'   => $t->getId(),
						'name' => $t->getName()
					];

				}, $this->getGroupTypeRoles($g->getId()))
			];
		}, $allTypes);

	}


	public function getAllAvailableNewsletter(NewsletterBlockSettings $nbSettings, ?int $ctUserId = NULL): array {

		$groupIds             = $nbSettings->getGroupsIds();
		$defaultGroupRole     = $nbSettings->getDefaultGroupRoleId();
		$groupRoleIdsSelected = $nbSettings->getGroupRoleIdsSelected();

		if (!in_array($defaultGroupRole, $groupRoleIdsSelected)) {
			$groupRoleIdsSelected[] = $defaultGroupRole;
		}

		// Keine Gruppen konfiguriert -> da kann ich mir einen ChurchTools Login sparen
		if (count($groupIds) === 0) {
			return [];
		}

		$this->client->login();

		$newsletterGroups = $this
			->getGroupApi()
			->getGroups($groupIds, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, FALSE, FALSE, NULL, NULL, NULL, 1, 100)
			->getData();

		if ($ctUserId !== NULL) {
			$userGroups = $this->getGroupApi()->getAllGroupsForPerson($ctUserId)->getData();
		} else {
			$userGroups = [];
		}


		// Merge User-Information with Newsletter-Settings
		$allNewsletter         = [];
		$user_is_in_this_group = NULL;

		foreach ($newsletterGroups as $nlGroup) {

			// Rausfinden ob der Nutzer in dieser Gruppe ist
			if ($ctUserId !== NULL) {
				$user_is_in_this_group = FALSE;
				foreach ($userGroups as $uGroup) {

					$uGroupId = (int)$uGroup->getGroup()->getDomainIdentifier();

					if ($uGroupId !== $nlGroup->getId()) {
						continue;
					}

					$user_is_in_this_group = in_array((int)$uGroup->getGroupTypeRoleId(), $groupRoleIdsSelected);

					if ($user_is_in_this_group && !$nbSettings->isMembershipWithoutConfirmation()) {
						// BEI TRUE:
						// Beim Löschen => Direkt gelöscht
						// Beim Einschreiben => Status "active"

						// BEI FALSE:
						// Beim Löschen => Status "to_delete"
						// Beim Einschreiben => Status "requested"

						// Nur wenn Status "to_delete" gesetzt ist, gilt der Nutzer nachher als nicht in der Gruppe eingeschrieben.
						if ($uGroup->getGroupMemberStatus() === self::STATUS_TO_DELETE) {
							$user_is_in_this_group = FALSE;
							continue;
						}

					}


					break;
				}
			}

			$nl = $this->_newsletterToArray($nlGroup, $user_is_in_this_group);

			$allNewsletter[] = $nl;
		}

		return $allNewsletter;

	}

	/**
	 * @return array
	 */
	public function getPersonRegistrationSettings() {

		$settings = get_option(NewsletterSettingsController::OPTION_CHURCHTOOLS_REGISTRATION_SETTINGS, []);

		// Make shure, it is an array
		$settings = !is_array($settings) ? [] : $settings;

		$keys = [
			'status'     => NULL,
			'department' => NULL,
			'campus'     => NULL,
		];

		$settings = array_merge($keys, $settings);

		// Make shure, all values are ints
		foreach ($settings as $key => $val) {
			$settings[$key] = is_null($val) ? NULL : (int)$val;
		}

		return $settings;

	}

	public function getGroupTypes() {
		return $this->loadMasterdataPerson()->getGroupTypes();
	}

	public function hasPermission($moduleName, $permission) {
		// Permissions gibt mir leider nicht das zurück, was ich brauche -> verschoben auf später

		return;

		if (is_null($this->globalPermissions)) {
			// Cache Permissions
			$permissionApi           = new PermissionApi($this->client, $this->config);
			$this->globalPermissions = $permissionApi->getGlobalPermissions()->getData();
		}

		$moduleName = strtoupper(substr($moduleName, 0, 1)) . strtolower(substr($moduleName, 1));
		$fn         = 'get' . $moduleName;


		$moduleData = $this->globalPermissions->$fn();
		$moduleData = $this->globalPermissions->getChurchdb();

	}

	/**
	 * @param int $groupTypeId
	 * @return \StevenBuehner\ChurchTools\Model\InlineResponse20043DataGroupTypes|null
	 */
	public function getGroupType(int $groupTypeId) {
		$groupTypes = $this->getGroupTypes();

		foreach ($groupTypes as $groupType) {
			if ($groupType->getId() === $groupTypeId) {
				return $groupType;
			}
		}

		return NULL;
	}

	public function getGroupTypeRoles(int $groupTypeId) {
		/** @var $el InlineResponse20041Data */
		return array_filter($this->loadMasterdataPerson()->getRoles(), function ($el) use ($groupTypeId) {
			return $el->getGroupTypeId() === $groupTypeId;
		});
	}

	public function getPersonMasterdataCampuses() {
		return $this->loadMasterdataPerson()->getCampuses();
	}

	public function getCampus(int $campusId) {

		$campuses = $this->getPersonMasterdataCampuses();

		foreach ($campuses as $campus) {
			if ($campus->getId() === $campusId) {
				return $campus;
			}
		}

		return NULL;

	}

	public function getPersonMasterdataStatuses() {
		return $this->loadMasterdataPerson()->getStatuses();
	}

	public function getPersonMasterdataDepartmens() {
		return $this->loadMasterdataPerson()->getDepartments();
	}

	protected function loadMasterdataPerson() {
		if ($this->masterdataPerson === NULL) {
			$this->client->login();
			$masterdataPersonApi    = new MasterDataApi($this->client, $this->config);
			$this->masterdataPerson = @$masterdataPersonApi->getPersonMasterdata()->getData();
		}

		return $this->masterdataPerson;
	}

	protected function _newsletterToArray($group, $userIsInThisGroup = NULL): array {

		$data = [
			'id'          => $group->getId(),
			'title'       => $group->getName(),
			'description' => $group->getInformation()->getNote(),
		];

		if ($userIsInThisGroup === FALSE || $userIsInThisGroup === TRUE) {
			$data['user_is_subscribed'] = $userIsInThisGroup;
		}

		return $data;

	}

	public function subscribe(NewsletterBlockSettings $nbSettings, int $newsletterGroupId, $userId): bool {

		if (!in_array($newsletterGroupId, $nbSettings->getGroupsIds())) {
			throw new ServiceException('required NewsletterGroup was not configured with this settings');
		}

		$targetRoleId                = $nbSettings->getDefaultGroupRoleId();
		$createOrUpdateMemberRequest = new CreateOrUpdateMemberRequest([
			'comment'             => 'subscribed @ ' . get_home_url(),
			'group_type_role_id'  => $targetRoleId,
			'group_member_status' => $nbSettings->isMembershipWithoutConfirmation() ? self::STATUS_ACTIVE : self::STATUS_REQUESTED,
		]);

		try {
			$this->getGroupApi()->createOrUpdateMember($userId, $newsletterGroupId, $createOrUpdateMemberRequest);
		} catch (ApiException $e) {
			return FALSE;
		}

		return TRUE;

	}

	public function unsubscribe(NewsletterBlockSettings $nbSettings, int $newsletterGroupId, $userId): bool {

		try {

			if ($nbSettings->isMembershipWithoutConfirmation()) {
				$this->getGroupApi()->deleteMember($userId, $newsletterGroupId);
			} else {
				$createOrUpdateMemberRequest = new CreateOrUpdateMemberRequest([
					'comment'             => 'removed subscription @ ' . get_home_url(),
					'group_member_status' => self::STATUS_TO_DELETE,
				]);
				$this->getGroupApi()->createOrUpdateMember($userId, $newsletterGroupId, $createOrUpdateMemberRequest);
			}

		} catch (ApiException $e) {
			return FALSE;
		}

		return TRUE;

	}


	/**
	 * @return GroupApi
	 */
	protected function getGroupApi() {
		if ($this->groupApi === NULL) {
			$this->groupApi = new GroupApi($this->client, $this->config);
		}

		return $this->groupApi;
	}

	/**
	 * @return PersonApi
	 */
	protected function getPersonApi() {
		if ($this->personsApi === NULL) {
			$this->personsApi = new PersonApi($this->client, $this->config);
		}

		return $this->personsApi;
	}

	/**
	 * @param string $emailAdress
	 * @param array <string > $replacementsVars
	 */
	public function sendEmailVerification(string $emailAdress, NewsletterBlockSettings $nlBlockSettings, array $replacementsVars = []) {

		$now          = new \DateTime('now');
		$tokenExpires = (clone($now))->modify($this->tokensExpireAfter);

		$verifyToken = $this->encodeJWT([
			'email' => $emailAdress,
			'pid'   => $nlBlockSettings->getPostId(),
			'bid'   => $nlBlockSettings->getBlockId(),
			'iat'   => $now->getTimestamp(), // Issued At Time
			'exp'   => $tokenExpires->getTimestamp(),
		]);

		$defaultReplacements = [
			'landingpage' => '',
			'firstname'   => 'firstname',
			'lastname'    => 'lastname',
			'email'       => $emailAdress,
			'verifytoken' => $verifyToken,
			'year'        => date('Y'),
			'dsgvolink'   => $this->getChurchToolsPrivacyUrl(),
			'themecolor'  => $nlBlockSettings->getThemecolor(),
			'logopath'    => $nlBlockSettings->getLogoPath(),
			'subject'     => $nlBlockSettings->getVerifyMailSubject(),
		];

		$replacementsVars = array_merge($defaultReplacements, $replacementsVars);

		// Verifikations Link zusammenstellen (auch wenn die URL bereits ? enhtält, muss es richtig geparsed werden)
		$replacementsVars['verificationUrl'] = $this->addParameterToUrl($replacementsVars['landingpage'], 'token', $verifyToken);

		// Parse Custom Mailtext
		$replacementsVars['mailtext'] = $this->replaceVars($nlBlockSettings->getVerifyMailText(), [
			'email'     => $replacementsVars['email'],
			'firstname' => $replacementsVars['firstname'],
			'lastname'  => $replacementsVars['lastname']
		]);

		// Parse Custom Mail Subject
		$subject = $this->replaceVars($nlBlockSettings->getVerifyMailSubject(), [
			'email'     => $replacementsVars['email'],
			'firstname' => $replacementsVars['firstname'],
			'lastname'  => $replacementsVars['lastname'],
		]);

		$mailer = MyContainer::getMailer();

		$mail = new Message();
		$mail->setFrom($mailer->getFrom(), $nlBlockSettings->getVerifyMailSender())
			->addTo($emailAdress)
			->setSubject($subject)
			->setHtmlBody($this->loadTemplate(self::TEMPLATE_DIR . 'verifyEmail.twig', $replacementsVars));

		try {
			$mailer->send($mail);
		} catch (SendException $e) {
			return ['error' => $e->getMessage(), 'success' => FALSE];
		} catch (\Exception $e) {
			return ['success' => FALSE];
		}

		return ['success' => TRUE];

	}

	protected function addParameterToUrl($url, $paramName, $paramValue) {
		// Parse the URL
		$urlParts = parse_url($url);

		// Parse existing query parameters
		$queryParams = [];
		if (isset($urlParts['query'])) {
			parse_str($urlParts['query'], $queryParams);
		}

		// Add the new parameter
		$queryParams[$paramName] = $paramValue;

		// Rebuild the query string
		$urlParts['query'] = http_build_query($queryParams);

		// Rebuild the URL
		return $this->build_url($urlParts);
	}

	protected function build_url($parts) {
		return (isset($parts['scheme']) ? "{$parts['scheme']}:" : '') .
			((isset($parts['user']) || isset($parts['host'])) ? '//' : '') .
			(isset($parts['user']) ? "{$parts['user']}" : '') .
			(isset($parts['pass']) ? ":{$parts['pass']}" : '') .
			(isset($parts['user']) ? '@' : '') .
			(isset($parts['host']) ? "{$parts['host']}" : '') .
			(isset($parts['port']) ? ":{$parts['port']}" : '') .
			(isset($parts['path']) ? "{$parts['path']}" : '') .
			(isset($parts['query']) ? "?{$parts['query']}" : '') .
			(isset($parts['fragment']) ? "#{$parts['fragment']}" : '');
	}

	protected function loadTemplate(string $path, array $replacements) {

		$content = file_get_contents($path);

		return $this->replaceVars($content, $replacements);

	}

	protected function replaceVars(string $content, array $replacements) {

		$newContent = preg_replace_callback('~\{\{\s*(?<key>[a-z0-9A-Z_]+)\s*\}\}~', function ($matches) use ($replacements) {

			if (isset($replacements[$matches['key']])) {
				return $replacements[$matches['key']];
			} else {
				// Do nothing
				return $matches[0];
			}

		}, $content);

		return $newContent;
	}


	public function encodeJWT($payload) {

		$privateKey = $this->openSslKey['private'];

		return JWT::encode($payload, $privateKey, 'RS256');

	}

	public function decodeJWT($token) {

		$publicKey = $this->openSslKey['public'];

		return (array)JWT::decode($token, new Key($publicKey, 'RS256'));

	}

	/**
	 * @param string $email
	 * @return Person[]
	 * @throws \StevenBuehner\ChurchTools\ApiException
	 */
	protected function findPersons(string $email) {

		$matchingPersons = [];

		$hasMore = TRUE;
		$page    = 1;
		$limit   = 100;

		$this->client->login();

		while ($hasMore === TRUE) {
			$response = $this->getPersonApi()
				->getAllPersons(NULL, NULL, NULL, NULL, NULL, FALSE, $page, $limit);

			/*
			$response2 = $this->getPersonApi()
				->getPersonProperties();
			*/

			$hasMore = $response->getMeta()->getPagination()->getLastPage() > $page;
			$page++;

			foreach ($response->getData() as $person) {
				/** @var PutCheckinPersons200ResponseDataEmailsInner $emails */
				foreach ($person->getEmails() as $mail) {
					if ($mail->getEmail() === $email) {
						$matchingPersons[] = $person;
					}
				}

			}

		}

		return $matchingPersons;

	}

	public function getRegisteredAccountNewsletters(string $email, NewsletterBlockSettings $nbSettings) {

		$response   = [];
		$allPersons = $this->findPersons($email);

		foreach ($allPersons as $person) {
			$personId       = $person->getId();
			$personResponse = [
				'id'         => $personId,
				'mail'       => $person->getEmail(),
				'firstname'  => $person->getFirstName(),
				'lastname'   => $person->getLastName(),
				'newsletter' => $this->getAllAvailableNewsletter($nbSettings, $personId)
			];
			$response[]     = $personResponse;
		}

		return $response;

	}

	public function registerNewChurchtoolsPerson(string $email, string $firstname, string $lastname) {

		$this->client->login();

		$settings   = $this->getPersonRegistrationSettings();
		$personData = new CreatePersonRequest();

		$personData->setEmail($email);
		$personData->setFirstName($firstname);
		$personData->setLastName($lastname);
		$personData->setDepartmentIds([$settings['department']]);
		$personData->setCampusId($settings['campus']);
		$personData->setStatusId($settings['status']);

		$agreement = new CreatePersonRequestPrivacyPolicyAgreement();
		$agreement->setDate(new \DateTime('now'));
		$agreement->setTypeId(1);
		$agreement->setWhoId(1);
		$personData->setPrivacyPolicyAgreement($agreement);

		$responsePerson = $this->getPersonApi()->createPerson($personData);

		return $responsePerson;

	}


}
