<?php
namespace SabaiApps\Directories\Component\FrontendSubmit\Helper;

use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\FrontendSubmit\Restrictor\IRestrictor;
use SabaiApps\Directories\Exception;
use SabaiApps\Framework\User\RegisteredIdentity;
use SabaiApps\Directories\Component\Entity\Model\Bundle;
use SabaiApps\Framework\User\AbstractIdentity;

class RestrictorsHelper {

	public function help( Application $application, $useCache = true ) {
		if ( ! $useCache
			|| ( ! $restrictors = $application->getPlatform()->getCache( 'frontendsubmit_restrictors' ) )
		) {
			$restrictors = array();
			foreach ( $application->InstalledComponentsByInterface( 'FrontendSubmit\IRestrictors' ) as $component_name ) {
				if ( ! $application->isComponentLoaded( $component_name ) ) {
					continue;
				}

				foreach ( $application->getComponent( $component_name )->frontendsubmitGetRestrictorNames() as $restrictor_name ) {
					if ( ! $restrictor = $application->getComponent( $component_name )->frontendsubmitGetRestrictor( $restrictor_name ) ) {
						continue;
					}

					$restrictors[ $restrictor_name ] = $component_name;
				}
			}
			$restrictors = array( 'default' => $restrictors['default'] ) + $restrictors; // Show default first
			$application->getPlatform()->setCache( $restrictors, 'frontendsubmit_restrictors' );
		}

		return $restrictors;
	}

	private $_impls = array();

	/**
	 * Gets an implementation of IRestrictor interface for a given restrictor name
	 *
	 * @param Application $application
	 * @param string      $restrictor
	 * @param bool        $returnFalse
	 * @return IRestrictor
	 * @throws Exception\IException
	 */
	public function impl( Application $application, $restrictor, $returnFalse = false ) {
		if ( ! isset( $this->_impls[ $restrictor ] ) ) {
			if ( ( ! $restrictors = $application->FrontendSubmit_Restrictors() )
				|| ! isset( $restrictors[ $restrictor ] )
				|| ! $application->isComponentLoaded( $restrictors[ $restrictor ] )
			) {
				if ( $returnFalse ) {
					return false;
				}
				throw new Exception\UnexpectedValueException( sprintf( 'Invalid restrictor: %s', $restrictor ) );
			}
			$this->_impls[ $restrictor ] = $application->getComponent( $restrictors[ $restrictor ] )->frontendsubmitGetRestrictor( $restrictor );
		}

		return $this->_impls[ $restrictor ];
	}

	public function isRestricted( Application $application ) {
		$restrict_config = $application->getComponent( 'FrontendSubmit' )->getConfig( 'restrict' );
		if ( ! empty( $restrict_config['type'] ) ) {
			$restrictor = $this->impl( $application, $restrict_config['type'] );
			if ( $restrictor->frontendsubmitRestrictorEnabled() ) {
				return $restrictor;
			}
		}
		return false;
	}

	/**
	 * @param Application                  $application
	 * @param Bundle                       $bundle
	 * @param AbstractIdentity|string|null $identity
	 * @param int|null                     $parentEntityId
	 * @return bool
	 * @throws Exception\IException
	 */
	public function isAllowed( Application $application, Bundle $bundle, $identity = null, $parentEntityId = null ) {
		if ( $restrictor = $this->isRestricted( $application ) ) {
			$restrict_config = $application->getComponent( 'FrontendSubmit' )->getConfig( 'restrict' );
			$settings        = (array) ( $restrict_config['settings'][ $restrict_config['type'] ] ?? array() );
			$settings       += (array) $restrictor->frontendsubmitRestrictorInfo( 'default_settings' );
			$identity        = $this->_validateIdentity( $application, $identity );
			if ( ! $identity instanceof AbstractIdentity
				|| $identity->isAnonymous()
			) {
				$user_id = 0;
			} elseif ( $user_id = $identity->id ) {
					// Do not restrict administrators
				if ( $application->getPlatform()->isAdministrator( $user_id ) ) {
					return true;
				}
			}
			$limit = $restrictor->frontendsubmitRestrictorLimit( $bundle, $settings, $user_id );
			if ( empty( $limit ) ) {
				return false; // no submission allowed
			}
			if ( $limit === -1 ) {
				return true; // no limit if -1
			}

			if ( $this->count( $application, $bundle, $identity, $parentEntityId, $limit, false ) >= $limit ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * @param Application                  $application
	 * @param Bundle                       $bundle
	 * @param AbstractIdentity|string|null $identity
	 * @param int|null                     $parentEntityId
	 * @param int|null                     $limit
	 * @param bool                         $validateIdentity
	 * @return int
	 * @throws Exception\IException
	 */
	protected function count( Application $application, Bundle $bundle, $identity = null, $parentEntityId = null, $limit = null, $validateIdentity = true ) {
		if ( $validateIdentity ) {
			$identity = $this->_validateIdentity( $application, $identity );
		}
		$user_id = $identity instanceof AbstractIdentity ? $identity->id : 0;
		$query   = $application->Entity_Query( $bundle->entitytype_name, $bundle->name )->fieldIs( 'author', $user_id );
		if ( ! empty( $bundle->info['parent'] ) ) {
			if ( empty( $parentEntityId ) ) {
				throw new Exception\RuntimeException( 'Child entity requires a parent entity ID for checking submit restriction.' );
			}
			$query->fieldIs( 'parent', $parentEntityId );
		}
		$status = array( 'publish', 'pending' );
		$query->fieldIsIn( 'status', $application->Entity_Status( $bundle->entitytype_name, $status ) );

		// Check by email address if guest
		if ( empty( $user_id ) ) {
			$email = $ip = null;
			if ( $identity instanceof AbstractIdentity ) {
				$email = trim( $identity->email );
				if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
					$email = null;
				}
			} else {
				$email_or_ip = trim( $identity );
				if ( ! filter_var( $email_or_ip, FILTER_VALIDATE_EMAIL ) ) {
					if ( filter_var( $email_or_ip, FILTER_VALIDATE_IP ) ) {
						$ip = $email_or_ip;
					}
				} else {
					$email = $email_or_ip;
				}
			}
			if ( isset( $email ) ) {
				$query->fieldIs( 'frontendsubmit_guest', $email, 'email' );
			} elseif ( isset( $ip ) ) {
				$query->fieldIs( 'frontendsubmit_guest', $application->getPlatform()->getHash( $ip ), 'ip_hash' );
			} else {
				$application->logError( 'Submission restriction for guest users require an e-mail address.' );
				return 0;
			}
		}

		$count = $query->count();
		if ( isset( $limit )
			&& $count >= $limit
		) {
			return $count;
		}

		// If payment enabled, check if any draft status items with pending payment status
		if ( ! empty( $bundle->info['payment_enable'] )
			&& $application->isComponentLoaded( 'Payment' )
		) {
			$query->removeNamedCriteria( 'status' )->fieldIs( 'status', $application->Entity_Status( $bundle->entitytype_name, 'draft' ) );
			foreach ( $query->fetch() as $draft_entity ) {
				if ( ! $application->Payment_Util_hasPendingOrder( $draft_entity, array( 'add', 'submit', 'renew', 'upgrade' ) ) ) {
					continue;
				}

				++$count;
				if ( isset( $limit )
					&& $count >= $limit
				) {
					return $count;
				}
			}
		}

		return $count;
	}

	protected function _validateIdentity( Application $application, $identity = null ) {
		if ( ! isset( $identity ) ) {
			$identity = $application->getUser()->getIdentity();
		}
		if ( $identity instanceof RegisteredIdentity
			|| ( is_string( $identity ) && strlen( $identity ) ) // anonymous identity requires an e-mail address
		) {
			return $identity;
		}

		throw new Exception\InvalidArgumentException( 'Submission restriction requires a valid registered user identity or guest e-mail address. Enable "Collect e-mail address" and "Require e-mail address" settings under Directories -> Settigngs -> Frontend Submit -> Guest Post Settings.' );
	}

	public function hasEntityLevel( Application $application, Bundle $bundle, $identity = null ) {
		if ( $restrictor = $this->_getCurrentRestrictor( $application ) ) {
			$restrict_config = $application->getComponent( 'FrontendSubmit' )->getConfig( 'restrict' );
			$settings        = isset( $restrict_config['settings'][ $restrictor->getName() ] ) ? (array) $restrict_config['settings'][ $restrictor->getName() ] : array();
			$settings       += (array) $restrictor->frontendsubmitRestrictorInfo( 'default_settings' );
			$identity        = $this->_validateIdentity( $application, $identity );
			$user_id         = $identity instanceof AbstractIdentity ? $identity->id : 0;
			if ( $level = $restrictor->frontendsubmitRestrictorHasEntityLevel( $bundle, $settings, $user_id ) ) {
				return $level;
			}
		}
		return false;
	}

	protected function _getCurrentRestrictor( Application $application ) {
		$restrict_config = $application->getComponent( 'FrontendSubmit' )->getConfig( 'restrict' );
		if ( ! empty( $restrict_config['type'] ) ) {
			$restrictor = $this->impl( $application, $restrict_config['type'] );
			if ( $restrictor->frontendsubmitRestrictorEnabled() ) {
				return $restrictor;
			}
		}
	}
}
