<?php
/**
 * Handles the scheduling and sending of malware-related notifications.
 *
 * @package WP_Defender\Model\Notification
 */

namespace WP_Defender\Model\Notification;

use Countable;
use WP_Defender\Traits\IO;
use WP_Defender\Model\Scan;
use WP_Defender\Component\Mail;
use WP_Defender\Component\Notification;
use WP_Defender\Model\Scan_Item;
use WP_Defender\Model\Notification\Malware_Report;
use WP_Defender\Traits\Scan_Email_Template;
use WP_Defender\Traits\Scan_Upsell;
use WP_Defender\Behavior\WPMUDEV;

/**
 * Handles the scheduling and sending of malware-related notifications.
 */
class Malware_Notification extends \WP_Defender\Model\Notification {

	use IO;
	use Scan_Email_Template;
	use Scan_Upsell;

	/**
	 * Table name.
	 *
	 * @var string
	 */
	protected $table = 'wd_malware_scanning_notification';
	/**
	 * Slug identifier for the malware notification.
	 *
	 * @var string
	 */
	public const SLUG = 'malware-notification';

	/**
	 * Retrieves the default values for the malware notification.
	 *
	 * @return array The default values for the malware notification.
	 */
	public function get_default_values(): array {
		$template = $this->get_email_template();

		return array(
			'subject_issue_found'     => $template['found']['subject'],
			'subject_issue_not_found' => $template['not_found']['subject'],
			'subject_error'           => $template['error']['subject'],
			'content_issue_found'     => $template['found']['body'],
			'content_issue_not_found' => $template['not_found']['body'],
			'content_error'           => $template['error']['body'],
		);
	}

	/**
	 * Sets default values for the malware notification.
	 *
	 * @return void
	 */
	protected function before_load(): void {
		$default = array(
			'slug'                 => self::SLUG,
			'title'                => esc_html__( 'Malware Scanning - Notification', 'defender-security' ),
			'status'               => self::STATUS_DISABLED,
			'description'          => esc_html__(
				'Get email notifications when Defender has finished manual malware scans.',
				'defender-security'
			),
			// @since 3.0.0 Fix 'Guest'-line.
			'in_house_recipients'  => is_user_logged_in() ? array( $this->get_default_user() ) : array(),
			'out_house_recipients' => array(),
			'type'                 => 'notification',
			'dry_run'              => false,
			'configs'              => array(
				'always_send' => false,
				'error_send'  => false,
				'template'    => $this->get_email_template(),
			),
		);
		$this->import( $default );
	}

	/**
	 * Sends the malware notification email to all subscribed users.
	 *
	 * @param  Scan   $scan  The scan object.
	 * @param  object $recipient_list  The object containing the recipient lists.
	 * @param  string $subject  The subject of the email.
	 * @param  string $content  The content of the email.
	 * @param  array  $issues  The list of issues to include in the email.
	 */
	protected function email_engine( Scan $scan, $recipient_list, $subject, $content, $issues ): void {
		$service = wd_di()->get( Notification::class );
		foreach ( $recipient_list->in_house_recipients as $user ) {
			if ( self::USER_SUBSCRIBED !== $user['status'] ) {
				continue;
			}
			$this->send_to_user( $user['email'], $user['name'], $subject, $content, $issues, $service );
		}
		foreach ( $recipient_list->out_house_recipients as $user ) {
			if ( self::USER_SUBSCRIBED !== $user['status'] ) {
				continue;
			}
			$this->send_to_user( $user['email'], $user['name'], $subject, $content, $issues, $service );
		}
	}

	/**
	 * Sends the malware notification email.
	 * Constructs and sends the malware notification email to the recipients.
	 *
	 * @param  Scan $scan  The scan object.
	 *
	 * @return void
	 */
	public function send( Scan $scan ): void {
		$object = $this;
		if ( $scan->is_automation ) {
			$object = wd_di()->get( Malware_Report::class );
			// If Malware Reporting is unchecked, then return.
			if ( self::STATUS_ACTIVE !== $object->status ) {
				return;
			}
			// @since 2.7.0 Remove 'dry_run'-condition.
		}
		$issues = $scan->get_issues( null, Scan_Item::STATUS_ACTIVE );
		/**
		 * When the Defender is unable to scan files for some reason and the 'error_send' option is checked.
		 * Possible reasons:
		 * - no connection to Scan API,
		 * - no checksums received from wp.org,
		 * - scan process stuck.
		 */
		if (
			isset( $object->configs['error_send'] )
			&& $object->configs['error_send']
			&& in_array( $scan->status, array( Scan::STATUS_ERROR, Scan::STATUS_IDLE ), true )
		) {
			$subject = $object->configs['template']['error']['subject'];
			$content = nl2br( $object->configs['template']['error']['body'] );

			$this->email_engine( $scan, $object, $subject, $content, $issues );

			return;
		}

		// Sometimes value may be as empty string for disabled 'always_send'.
		if (
			( ! isset( $object->configs['always_send'] ) || true !== $object->configs['always_send'] )
			&& 0 === count( $issues )
		) {
			return;
		}
		// For another case.
		$templates = count( $issues ) ? $object->configs['template']['found'] : $object->configs['template']['not_found'];
		$subject   = $templates['subject'];
		$content   = nl2br( $templates['body'] );

		$this->email_engine( $scan, $object, $subject, $content, $issues );
	}

	/**
	 * Sends the malware notification email to a specific user.
	 * Constructs and sends the malware notification email to the specified recipient.
	 *
	 * @param  string $email  The recipient's email address.
	 * @param  string $name  The recipient's name.
	 * @param  string $subject  The email subject.
	 * @param  string $content  The email content.
	 * @param  array  $issues  The malware issues detected.
	 * @param  object $service  The notification service object.
	 *
	 * @return void
	 */
	private function send_to_user( $email, $name, $subject, $content, $issues, $service ) {
		$wpmudev           = new WPMUDEV();
		$maybe_show_upsell = ! $wpmudev->is_whitelabel_enabled() && $wpmudev->is_pro();
		$controller_scan   = wd_di()->get( \WP_Defender\Controller\Scan::class );
		$replacers         = array(
			'issues_count' => is_array( $issues ) || $issues instanceof \Countable ? (string) count( $issues ) : '0',
			'issues_list'  => $controller_scan->render_partial(
				'email/scan-issue-list',
				array(
					'issues' => $issues,
					'email'  => $email,
				),
				false
			),
			'site_url'     => network_site_url(),
			'user_name'    => $name,
		);

		/**
		 * Filters the replaced data. It's without replacement.
		 *
		 * @param  array  $replacers  Email params.
		 *
		 * @deprecated 3.2.0
		 */
		$replacers = apply_filters_deprecated( 'wd_notification_email_params', array( $replacers ), '3.2.0' );

		$filter_name = ( is_array( $issues ) || $issues instanceof Countable ? count( $issues ) : 0 ) > 0
			? 'wd_notification_email_subject_issue'
			: 'wd_notification_email_subject';
		$subject     = apply_filters( $filter_name, $subject );

		/**
		 * It's without replacement.
		 *
		 * @deprecated 3.2.0
		 */
		$content = apply_filters_deprecated(
			'wd_notification_email_content_before',
			array( $content ),
			'3.2.0'
		);
		foreach ( $replacers as $key => $replacer ) {

			if ( is_scalar( $replacer ) && ! is_string( $replacer ) ) {
				$replacer = (string) $replacer;
			}

			$content = str_replace( "{{$key}}", $replacer, $content );
			$content = str_replace( '{' . strtoupper( $key ) . '}', $replacer, $content );
			$subject = str_replace( "{{$key}}", $replacer, $subject );
			$subject = str_replace( '{' . strtoupper( $key ) . '}', $replacer, $subject );
		}
		/**
		 * It's without replacement.
		 *
		 * @deprecated 3.2.0
		 */
		$content          = apply_filters_deprecated(
			'wd_notification_email_content_after',
			array( $content ),
			'3.2.0'
		);
		$unsubscribe_link = $service->create_unsubscribe_url( $this->slug, $email );
		$template         = $controller_scan->render_partial(
			'email/index',
			array(
				'title'            => esc_html__( 'Malware Scanning', 'defender-security' ),
				'content_body'     => sprintf( '<div style="font-size: 16px">%s</div>', $content ),
				'unsubscribe_link' => $unsubscribe_link,
			),
			false
		);

		$headers = wd_di()->get( Mail::class )->get_headers(
			defender_noreply_email( 'wd_scan_noreply_email' ),
			self::SLUG
		);

		$ret = wp_mail( $email, $subject, $template, $headers );
		if ( $ret ) {
			$this->save_log( $email );
		}
	}

	/**
	 * Defines labels for the malware notification settings.
	 *
	 * @return array The array of malware notification settings labels.
	 */
	public function labels(): array {
		return array(
			'notification'                  => esc_html__( 'Malware Scanning - Notification', 'defender-security' ),
			'always_send_notification'      => esc_html__( 'Send notifications when no issues are detected', 'defender-security' ),
			'error_send'                    => esc_html__(
				"Send notifications when Defender couldn't scan the files",
				'defender-security'
			),
			'email_subject_issue_found'     => esc_html__( 'Email subject when an issue is found', 'defender-security' ),
			'email_subject_issue_not_found' => esc_html__( 'Email subject when no issues are found', 'defender-security' ),
			'email_subject_error'           => esc_html__( 'Email subject when failed to scan', 'defender-security' ),
			'email_content_issue_found'     => esc_html__( 'Email body when an issue is found', 'defender-security' ),
			'email_content_issue_not_found' => esc_html__( 'Email body when no issues are found', 'defender-security' ),
			'email_content_error'           => esc_html__( 'When failed to scan', 'defender-security' ),
			'notification_subscribers'      => esc_html__( 'Recipients', 'defender-security' ),
		);
	}

	/**
	 * Checks if the malware scanning notification options are enabled.
	 * Checks whether the malware scanning notification is enabled or if the malware reporting is enabled or the scan
	 * is automated.
	 *
	 * @return bool True if the malware scanning notification options are enabled, false otherwise.
	 */
	public function check_options(): bool {
		return self::STATUS_ACTIVE === $this->status
				|| self::STATUS_ACTIVE === wd_di()->get( Malware_Report::class )->status;
	}

	/**
	 * Additional converting rules.
	 *
	 * @param  array $configs  The configuration data.
	 *
	 * @return array The type-casted configuration data.
	 * @since 3.1.0
	 */
	public function type_casting( array $configs ): array {
		$configs['always_send'] = (bool) $configs['always_send'];
		$configs['error_send']  = (bool) $configs['error_send'];
		// Additional security layer because parent 'configs'-property doesn't have sanitized stuff for nested properties.
		$template                                    = $configs['template'];
		$configs['template']['found']['subject']     = sanitize_text_field( $template['found']['subject'] );
		$configs['template']['found']['body']        = sanitize_textarea_field( $template['found']['body'] );
		$configs['template']['not_found']['subject'] = sanitize_text_field( $template['not_found']['subject'] );
		$configs['template']['not_found']['body']    = sanitize_textarea_field( $template['not_found']['body'] );
		$configs['template']['error']['subject']     = sanitize_text_field( $template['error']['subject'] );
		$configs['template']['error']['body']        = sanitize_textarea_field( $template['error']['body'] );

		return $configs;
	}
}
