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

use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\Directory\Model\Directory;
use SabaiApps\Directories\Exception;

class ServicesHelper {

	protected $_impls = array();

	public function help( Application $application, $type, $useCache = true ) {
		if ( ! in_array( $type, array( 'event', 'recipient', 'sender' ) ) ) {
			throw new Exception\UnexpectedValueException( sprintf( 'Invalid service type (%s)', $type ) );
		}

		if ( ! $useCache
			|| ( ! $services = $application->getPlatform()->getCache( 'notification_services_' . $type ) )
		) {
			$services        = $weights = $labels = array();
			$type_name       = ucfirst( $type );
			$interface       = 'Notification\\I' . $type_name . 's';
			$names_method    = 'notificationGet' . $type_name . 'Names';
			$instance_method = 'notificationGet' . $type_name;
			$info_method     = 'notification' . $type_name . 'Info';
			foreach ( $application->InstalledComponentsByInterface( $interface ) as $component_name ) {
				if ( ! $application->isComponentLoaded( $component_name ) ) {
					continue;
				}

				foreach ( $application->getComponent( $component_name )->$names_method() as $name ) {
					if ( ! $instance = $application->getComponent( $component_name )->$instance_method( $name ) ) {
						continue;
					}
					if ( ! $info = $instance->$info_method() ) {
						continue;
					}
					$services[ $name ] = array( $component_name, $instance_method );
					$weights[ $name ]  = (int) ( $info['weight'] ?? 10 );
					$labels[ $name ]   = $info['label'] ?? $name;
				}
			}
			uksort(
				$services,
				function ( $a, $b ) use ( $weights, $labels ) {
					if ( $weights[ $a ] < $weights[ $b ] ) {
						return -1;
					}
					if ( $weights[ $a ] === $weights[ $b ] ) {
						return strcasecmp( $labels[ $a ], $labels[ $b ] );
					}
					return 1;
				}
			);

			$application->getPlatform()->setCache( $services, 'notification_services_' . $type );
		}

		return $services;
	}

	public function deleteCache( Application $application, $type = null ) {
		if ( $type === null ) {
			$application->getPlatform()->deleteCache( 'notification_services_event' );
			$application->getPlatform()->deleteCache( 'notification_services_recipient' );
			$application->getPlatform()->deleteCache( 'notification_services_sender' );
		} elseif ( in_array( $type, array( 'event', 'recipient', 'sender' ) ) ) {
			$application->getPlatform()->deleteCache( 'notification_services_' . $type );
		} else {
			throw new Exception\UnexpectedValueException( sprintf( 'Invalid service type (%s)', $type ) );
		}
	}

	protected function _getInstance( Application $application, $type, $name, $returnFalse = false, $useCache = true ) {
		if ( ! isset( $this->_impls[ $type ][ $name ] ) ) {
			if ( ( ! $services = $this->help( $application, $type, $useCache ) )
				|| ! isset( $services[ $name ] )
				|| ! $application->isComponentLoaded( $services[ $name ][0] )
			) {
				if ( $returnFalse ) {
					return false;
				}
				throw new Exception\UnexpectedValueException( sprintf( 'Invalid service type (%s) or name (%s)', $type, $name ) );
			}
			$instance_method                = $services[ $name ][1];
			$this->_impls[ $type ][ $name ] = $application->getComponent( $services[ $name ][0] )->$instance_method( $name );
		}

		return $this->_impls[ $type ][ $name ];
	}

	public function event( Application $application, $name, $returnFalse = false, $useCache = true ) {
		return $this->_getInstance( $application, 'event', $name, $returnFalse, $useCache );
	}

	public function recipient( Application $application, $name, $returnFalse = false, $useCache = true ) {
		return $this->_getInstance( $application, 'recipient', $name, $returnFalse, $useCache );
	}

	public function sender( Application $application, $name, $returnFalse = false, $useCache = true ) {
		return $this->_getInstance( $application, 'sender', $name, $returnFalse, $useCache );
	}

	public function events( Application $application, $useCache = true ) {
		$events = $this->help( $application, 'event', $useCache );
		foreach ( array_keys( $events ) as $event_name ) {
			if ( ! $this->event( $application, $event_name, true ) ) {
				unset( $events[ $event_name ] ); // this should not happen
			}
		}

		return $events;
	}

	public function createDefaultNotifications( Application $application, $eventNames, ?Directory $directory = null ) {
		$model = $application->getModel( null, 'Notification' );
		foreach ( $eventNames as $event_name ) {
			if ( ( ! $event = $this->event( $application, $event_name, true ) )
				|| ( ! $notification_data = $event->notificationEventDefaultNotificationData() )
				|| $model->Notification->event_is( $event_name )->system_is( 1 )->count()
			) {
				continue;
			}

			foreach ( $notification_data as $_notification_data ) {
				$notification         = $model->create( 'Notification' )->markNew();
				$notification->event  = $event_name;
				$notification->system = $notification->active = 1;
				if ( ! isset( $_notification_data['event'] ) ) {
					$_notification_data['event'] = array();
				}
				$_notification_data['event'] += (array) $event->notificationEventInfo( 'default_settings' );
				$notification->data           = $_notification_data + array( 'label' => $event->notificationEventInfo( 'label' ) );
			}
		}
		$model->commit();

		return isset( $notification ) ? $notification : null;
	}

	public function triggerEvent( Application $application, $eventName, array $params ) {
		$application->logDebug( sprintf( 'Triggering notification event "%s"', $eventName ) );
		$event         = $this->event( $application, $eventName );
		$notifications = $application->getModel( 'Notification', 'Notification' )->active_is( 1 )->event_is( $eventName );
		foreach ( $notifications->fetch() as $notification ) {
			$notification_settings = isset( $notification->data['event'] ) ? (array) $notification->data['event'] : array();
			try {
				if ( ! $messages = $event->notificationEventMessages( $notification_settings, $params ) ) {
					continue;
				}
			} catch ( Exception\IException $e ) {
				$application->LogError( $e );
				continue;
			}

			foreach ( array_keys( $notification->data['sender'] ) as $sender_name ) {
				$application->logDebug(
					sprintf(
						'Sending notification "%s" for event "%s" using sender "%s"',
						$notification->data['label'] ?? $notification->id,
						$eventName,
						$this->sender( $application, $sender_name )->notificationSenderInfo( 'label' )
					)
				);
				try {
					$this->sender( $application, $sender_name )->notificationSenderSend( $notification->data['sender'][ $sender_name ], $messages );
				} catch ( Exception\IException $e ) {
					$application->LogError( $e );
					continue;
				}
			}
		}
	}
}
