<?php
/**
 * Auto-reply rule sender facade.
 *
 * @package SubmissionInbox
 */

namespace SUBIN\AutoReplies;

use SUBIN\Forms\FormSubmissionContext;
use SUBIN\Leads\LeadRepository;
use SUBIN\MailLog\MailEventRepository;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Small future-facing API for sending auto-replies from saved leads.
 */
class AutoReplyRuleSender {
	/**
	 * Rule repository.
	 *
	 * @var AutoReplyRuleRepository
	 */
	private $rules;

	/**
	 * Template repository.
	 *
	 * @var AutoReplyTemplateRepository
	 */
	private $templates;

	/**
	 * Mail event repository.
	 *
	 * @var MailEventRepository
	 */
	private $events;

	/**
	 * Submission context.
	 *
	 * @var FormSubmissionContext
	 */
	private $context;

	/**
	 * Constructor.
	 *
	 * @param AutoReplyRuleRepository|null    $rules Rules.
	 * @param AutoReplyTemplateRepository|null $templates Templates.
	 * @param MailEventRepository|null        $events Events.
	 * @param FormSubmissionContext|null      $context Context.
	 */
	public function __construct( ?AutoReplyRuleRepository $rules = null, ?AutoReplyTemplateRepository $templates = null, ?MailEventRepository $events = null, ?FormSubmissionContext $context = null ) {
		$this->rules     = $rules ?: new AutoReplyRuleRepository();
		$this->templates = $templates ?: new AutoReplyTemplateRepository();
		$this->events    = $events ?: new MailEventRepository();
		$this->context   = $context ?: new FormSubmissionContext();
	}

	/**
	 * Find the rule that should apply to a lead.
	 *
	 * @param array<string,mixed>|object $lead Lead row.
	 * @return array<string,mixed>|null
	 */
	public function rule_for_lead( $lead ) {
		$provider = is_array( $lead ) ? ( $lead['provider'] ?? '' ) : ( $lead->provider ?? '' );
		$form_id  = is_array( $lead ) ? ( $lead['form_id'] ?? '' ) : ( $lead->form_id ?? '' );

		return $this->rules->find_enabled_rule( (string) $provider, (string) $form_id );
	}

	/**
	 * Send an auto-reply for a saved lead when an enabled rule exists.
	 *
	 * @param array<string,mixed> $lead Lead.
	 * @param array<int,array>    $lead_fields Lead fields.
	 * @return string Email status.
	 */
	public function send_for_lead( array $lead, array $lead_fields ) {
		$lead_id = absint( $lead['id'] ?? 0 );
		if ( $lead_id <= 0 || $this->has_auto_reply_event( $lead_id ) ) {
			return LeadRepository::EMAIL_STATUS_NO_EMAIL_GENERATED;
		}

		$rule = $this->rule_for_lead( $lead );
		if ( ! $rule ) {
			return LeadRepository::EMAIL_STATUS_NO_EMAIL_GENERATED;
		}

		$template = $this->templates->get( absint( $rule['template_id'] ?? 0 ) );
		if ( ! $template ) {
			$this->log_no_email_generated( $lead_id, '', '', __( 'Missing auto-reply template.', 'submission-inbox' ) );
			return LeadRepository::EMAIL_STATUS_NO_EMAIL_GENERATED;
		}

		$recipient = $this->visitor_email_from_rule( $rule, $lead_fields );
		$subject   = sanitize_text_field( $this->replace_variables( (string) ( $template['subject'] ?? '' ), $lead, $lead_fields, false ) );
		if ( '' === $subject ) {
			$subject = __( 'We received your request', 'submission-inbox' );
		}

		if ( ! is_email( $recipient ) ) {
			$this->log_no_email_generated( $lead_id, '', $subject, __( 'Auto-reply visitor email field did not contain a valid email address.', 'submission-inbox' ) );
			return LeadRepository::EMAIL_STATUS_NO_EMAIL_GENERATED;
		}

		$body = $this->render_body( $template, $lead, $lead_fields );
		if ( '' === trim( wp_strip_all_tags( $body ) ) && false === strpos( $body, '<img' ) ) {
			$this->log_no_email_generated( $lead_id, $recipient, $subject, __( 'Auto-reply template did not generate email content.', 'submission-inbox' ) );
			return LeadRepository::EMAIL_STATUS_NO_EMAIL_GENERATED;
		}

		$this->context->set_current_lead_id( $lead_id );
		$this->context->set_current_source( AutoReplySender::SOURCE );
		add_filter( 'wp_mail_content_type', array( $this, 'html_mail_content_type' ) );
		try {
			$sent = wp_mail( $recipient, $subject, $body, array( 'Content-Type: text/html; charset=UTF-8' ) );
		} catch ( \Throwable $throwable ) {
			$event_id = $this->events->create_pending(
				array(
					'lead_id'      => $lead_id,
					'source'       => AutoReplySender::SOURCE,
					'to_email'     => $recipient,
					'from_email'   => '',
					'subject'      => $subject,
					'message_hash' => hash( 'sha256', $body ),
				)
			);
			if ( $event_id > 0 ) {
				$this->events->mark_failed( $event_id, sanitize_text_field( wp_strip_all_tags( $throwable->getMessage() ) ) );
			}
			$sent = false;
		} finally {
			remove_filter( 'wp_mail_content_type', array( $this, 'html_mail_content_type' ) );
			$this->context->clear();
		}

		if ( ! $this->has_auto_reply_event( $lead_id ) ) {
			$event_id = $this->events->create_pending(
				array(
					'lead_id'      => $lead_id,
					'source'       => AutoReplySender::SOURCE,
					'to_email'     => $recipient,
					'from_email'   => '',
					'subject'      => $subject,
					'message_hash' => hash( 'sha256', $body ),
				)
			);
			if ( $event_id > 0 ) {
				if ( $sent ) {
					$this->events->mark_sent( $event_id );
				} else {
					$this->events->mark_failed( $event_id, __( 'WordPress mail returned false.', 'submission-inbox' ) );
				}
			}
		}

		return $sent ? LeadRepository::EMAIL_STATUS_SENT : LeadRepository::EMAIL_STATUS_FAILED;
	}

	/**
	 * Use HTML content type while sending.
	 *
	 * @return string
	 */
	public function html_mail_content_type() {
		return 'text/html';
	}

	/**
	 * Check whether a visitor auto-reply event already exists.
	 *
	 * @param int $lead_id Lead id.
	 * @return bool
	 */
	private function has_auto_reply_event( $lead_id ) {
		foreach ( $this->events->get_by_lead_id( $lead_id ) as $event ) {
			if ( AutoReplySender::SOURCE === sanitize_key( $event->source ?? '' ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Resolve visitor email from selected rule field.
	 *
	 * @param array<string,mixed> $rule Rule.
	 * @param array<int,array>    $lead_fields Lead fields.
	 * @return string
	 */
	private function visitor_email_from_rule( array $rule, array $lead_fields ) {
		$selected = sanitize_key( (string) ( $rule['visitor_email_field'] ?? '' ) );
		if ( '' === $selected ) {
			return '';
		}

		foreach ( $lead_fields as $field ) {
			if ( $selected !== sanitize_key( $field['field_key'] ?? '' ) ) {
				continue;
			}

			return sanitize_email( $field['field_value'] ?? '' );
		}

		return '';
	}

	/**
	 * Render safe email body.
	 *
	 * @param array<string,mixed> $template Template.
	 * @param array<string,mixed> $lead Lead.
	 * @param array<int,array>    $lead_fields Lead fields.
	 * @return string
	 */
	private function render_body( array $template, array $lead, array $lead_fields ) {
		$html = (string) ( $template['rendered_html'] ?? $template['body'] ?? '' );
		$html = preg_replace( '/<\?(?:php)?[\s\S]*?\?>/i', '', $html );
		$html = strip_shortcodes( $html );
		$html = $this->replace_variables( $html, $lead, $lead_fields, true );
		$html = preg_replace( '/\{[A-Za-z0-9_:-]+\}/', '', (string) $html );
		$html = wp_kses( $html, AutoReplyTemplateRepository::allowed_email_html(), array( 'http', 'https', 'mailto', 'tel' ) );

		return AutoReplyTemplateRepository::sanitize_svg_paint_attributes( $html );
	}

	/**
	 * Replace template variables.
	 *
	 * @param string              $content Content.
	 * @param array<string,mixed> $lead Lead.
	 * @param array<int,array>    $lead_fields Lead fields.
	 * @param bool                $html Whether replacing for HTML.
	 * @return string
	 */
	private function replace_variables( $content, array $lead, array $lead_fields, $html ) {
		$fields = array();
		foreach ( $lead_fields as $field ) {
			$key = sanitize_key( $field['field_key'] ?? '' );
			if ( '' !== $key ) {
				$fields[ $key ] = (string) ( $field['field_value'] ?? '' );
			}
		}

		$values = array(
			'site_name'       => get_bloginfo( 'name' ),
			'form_name'       => sanitize_text_field( (string) ( $lead['form_name'] ?? '' ) ),
			'submission_date' => current_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ),
			'lead_id'         => (string) absint( $lead['id'] ?? 0 ),
			'visitor_email'   => sanitize_email( (string) ( $lead['customer_email'] ?? '' ) ),
		);

		foreach ( $values as $key => $value ) {
			$content = str_replace( '{' . $key . '}', $html ? esc_html( $value ) : sanitize_text_field( $value ), $content );
		}

		return (string) preg_replace_callback(
			'/\{field:([A-Za-z0-9_-]+)\}/',
			function ( $matches ) use ( $fields, $html ) {
				$key   = sanitize_key( $matches[1] );
				$value = $fields[ $key ] ?? '';

				return $html ? nl2br( esc_html( $value ) ) : sanitize_text_field( $value );
			},
			$content
		);
	}

	/**
	 * Log an intentionally skipped auto-reply.
	 *
	 * @param int    $lead_id Lead id.
	 * @param string $recipient Recipient.
	 * @param string $subject Subject.
	 * @param string $reason Reason.
	 * @return void
	 */
	private function log_no_email_generated( $lead_id, $recipient, $subject, $reason ) {
		$this->events->create_no_email_generated(
			array(
				'lead_id'       => $lead_id,
				'source'        => AutoReplySender::SOURCE,
				'to_email'      => $recipient,
				'subject'       => $subject,
				'error_message' => $reason,
			)
		);
	}
}
