<?php
/**
 * Auto-reply sender for built-in forms.
 *
 * @package SubmissionInbox
 */

namespace SUBIN\AutoReplies;

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

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

/**
 * Builds and sends visitor auto-replies after successful built-in submissions.
 */
class AutoReplySender {
	const SOURCE = 'submission_inbox_auto_reply';

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

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

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

	/**
	 * Form field repository.
	 *
	 * @var FormFieldRepository
	 */
	private $fields;

	/**
	 * Rule repository.
	 *
	 * @var AutoReplyRuleRepository
	 */
	private $rules;

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

	/**
	 * Whether a built-in form has an enabled auto-reply rule.
	 *
	 * @param array<string,mixed> $form Form row.
	 * @return bool
	 */
	public function has_enabled_rule_for_form( array $form ) {
		$form_id = absint( $form['id'] ?? 0 );

		return $form_id > 0 && null !== $this->rules->find_enabled_rule( AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM, (string) $form_id );
	}

	/**
	 * Send an auto-reply for a saved lead.
	 *
	 * @param array<string,mixed>  $form Form row.
	 * @param int                  $lead_id Lead id.
	 * @param array<int,array>     $lead_fields Saved lead fields.
	 * @param array<string,string> $detected Detected lead values.
	 * @param array<string,mixed>  $settings Form settings.
	 * @return string Lead email status.
	 */
	public function send_for_submission( array $form, $lead_id, array $lead_fields, array $detected, array $settings ) {
		$lead_id = absint( $lead_id );
		if ( $lead_id <= 0 ) {
			return LeadRepository::EMAIL_STATUS_NO_EMAIL_GENERATED;
		}

		unset( $settings );

		$rule = $this->rules->find_enabled_rule( AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM, (string) absint( $form['id'] ?? 0 ) );
		if ( ! $rule ) {
			return LeadRepository::EMAIL_STATUS_NO_EMAIL_GENERATED;
		}

		$template_id = absint( $rule['template_id'] ?? 0 );
		$template    = $template_id > 0 ? $this->templates->get( $template_id ) : null;
		if ( ! $template ) {
			$this->log_no_email_generated( $lead_id, '', '', __( 'Missing auto-reply template.', 'submission-inbox' ) );
			return LeadRepository::EMAIL_STATUS_NO_EMAIL_GENERATED;
		}
		$recipient_result = $this->resolve_visitor_email_for_rule( $form, $lead_fields, $rule );
		$recipient        = $recipient_result['email'];
		if ( ! is_email( $recipient ) ) {
			$reason = $this->auto_reply_reason_message( $recipient_result['reason'] );
			$this->log_no_email_generated( $lead_id, '', $this->replace_variables( $template['subject'] ?? '', $form, $lead_id, $lead_fields, $detected, false ), $reason );
			return LeadRepository::EMAIL_STATUS_NO_EMAIL_GENERATED;
		}

		$subject = $this->replace_variables( $template['subject'] ?? '', $form, $lead_id, $lead_fields, $detected, false );
		$subject = sanitize_text_field( '' !== $subject ? $subject : __( 'We received your request', 'submission-inbox' ) );
		$body    = $this->render_auto_reply_email_for_sending( $template, $form, $lead_id, $lead_fields, $detected );
		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;
		}

		$headers = array( 'Content-Type: text/html; charset=UTF-8' );
		$this->context->set_current_lead_id( $lead_id );
		$this->context->set_current_source( self::SOURCE );
		add_filter( 'wp_mail_content_type', array( $this, 'html_mail_content_type' ) );
		try {
			$sent = wp_mail( $recipient, $subject, $body, $headers );
		} catch ( \Throwable $throwable ) {
			$event_id = $this->events->create_pending(
				array(
					'lead_id'      => $lead_id,
					'source'       => self::SOURCE,
					'to_email'     => $recipient,
					'from_email'   => '',
					'subject'      => $subject,
					'message_hash' => hash( 'sha256', $body ),
				)
			);
			if ( $event_id > 0 ) {
				$this->events->mark_failed( $event_id, $this->sanitize_mail_error( $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'       => self::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;
	}

	/**
	 * Resolve visitor email from a rule-selected field.
	 *
	 * @param array<string,mixed> $form Form row.
	 * @param array<int,array>    $lead_fields Lead fields.
	 * @param array<string,mixed> $rule Rule row.
	 * @return array{email:string,reason:string,matched:bool}
	 */
	private function resolve_visitor_email_for_rule( array $form, array $lead_fields, array $rule ) {
		$selected_key = sanitize_key( (string) ( $rule['visitor_email_field'] ?? '' ) );
		if ( '' === $selected_key ) {
			return array(
				'email'   => '',
				'reason'  => 'auto_reply_email_field_not_selected',
				'matched' => false,
			);
		}

		$form_email_field = $this->selected_form_email_field( absint( $form['id'] ?? 0 ), $selected_key, 0 );
		if ( ! $form_email_field ) {
			return array(
				'email'   => '',
				'reason'  => 'auto_reply_email_field_not_found',
				'matched' => false,
			);
		}

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

			$email = sanitize_email( $field['field_value'] ?? '' );
			return array(
				'email'   => $email,
				'reason'  => is_email( $email ) ? 'resolved' : ( '' === $email ? 'auto_reply_email_empty' : 'auto_reply_email_invalid' ),
				'matched' => true,
			);
		}

		return array(
			'email'   => '',
			'reason'  => 'auto_reply_email_field_not_found',
			'matched' => false,
		);
	}

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

	/**
	 * Sanitize mail errors before storing them.
	 *
	 * @param string $message Error message.
	 * @return string
	 */
	private function sanitize_mail_error( $message ) {
		return sanitize_text_field( wp_strip_all_tags( (string) $message ) );
	}

	/**
	 * Check whether a visitor auto-reply event already exists for a lead.
	 *
	 * @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 ( self::SOURCE === sanitize_key( $event->source ?? '' ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Resolve the visitor email address for an auto-reply.
	 *
	 * Form settings currently store the selected visitor email field as field_key
	 * in auto_reply.recipient_field_key. Legacy data may contain a numeric field id,
	 * so this resolver supports both field_key and field_id matching.
	 *
	 * @param array<string,mixed> $form Form row.
	 * @param array<int,array>    $lead_fields Lead fields.
	 * @param array<string,mixed> $settings Form settings.
	 * @return array{email:string,reason:string,matched:bool}
	 */
	private function resolve_visitor_email_for_auto_reply( array $form, array $lead_fields, array $settings ) {
		$config = is_array( $settings['auto_reply'] ?? null ) ? $settings['auto_reply'] : array();
		$raw_selected = $config['recipient_field_key']
			?? $config['email_field_key']
			?? $config['auto_reply_email_field_key']
			?? $config['recipient_field_id']
			?? $config['email_field_id']
			?? $config['auto_reply_email_field_id']
			?? '';

		$selected_key = sanitize_key( (string) $raw_selected );
		$selected_id  = absint( $raw_selected );

		if ( '' === $selected_key && $selected_id <= 0 ) {
			return array(
				'email'   => '',
				'reason'  => 'auto_reply_email_field_not_selected',
				'matched' => false,
			);
		}

		$form_email_field = $this->selected_form_email_field( absint( $form['id'] ?? 0 ), $selected_key, $selected_id );
		if ( ! $form_email_field ) {
			return array(
				'email'   => '',
				'reason'  => 'auto_reply_email_field_not_found',
				'matched' => false,
			);
		}

		$form_field_key = sanitize_key( $form_email_field['field_key'] ?? '' );
		$form_field_id  = absint( $form_email_field['id'] ?? 0 );
		foreach ( $lead_fields as $field ) {
			$field_key = sanitize_key( $field['field_key'] ?? '' );
			$field_id  = absint( $field['field_id'] ?? 0 );
			$matches_key = '' !== $form_field_key && $form_field_key === $field_key;
			$matches_id  = $form_field_id > 0 && $form_field_id === $field_id;

			if ( ! $matches_key && ! $matches_id ) {
				continue;
			}

			if ( 'email' !== sanitize_key( $field['field_type'] ?? '' ) ) {
				return array(
					'email'   => '',
					'reason'  => 'auto_reply_email_field_not_found',
					'matched' => true,
				);
			}

			$email = sanitize_email( $field['field_value'] ?? '' );
			if ( '' === $email ) {
				return array(
					'email'   => '',
					'reason'  => 'auto_reply_email_empty',
					'matched' => true,
				);
			}

			if ( ! is_email( $email ) ) {
				return array(
					'email'   => '',
					'reason'  => 'auto_reply_email_invalid',
					'matched' => true,
				);
			}

			return array(
				'email'   => $email,
				'reason'  => 'resolved',
				'matched' => true,
			);
		}

		return array(
			'email'   => '',
			'reason'  => 'auto_reply_email_field_not_found',
			'matched' => false,
		);
	}

	/**
	 * Find the selected real email field on the current form.
	 *
	 * @param int    $form_id Form id.
	 * @param string $selected_key Selected field key.
	 * @param int    $selected_id Selected field id.
	 * @return array<string,mixed>|null
	 */
	private function selected_form_email_field( $form_id, $selected_key, $selected_id ) {
		if ( $form_id <= 0 ) {
			return null;
		}

		foreach ( $this->fields->get_by_form_id( $form_id ) as $field ) {
			if ( 'email' !== sanitize_key( $field['field_type'] ?? '' ) ) {
				continue;
			}

			$field_key = sanitize_key( $field['field_key'] ?? '' );
			$field_id  = absint( $field['id'] ?? 0 );
			if ( ( '' !== $selected_key && $selected_key === $field_key ) || ( $selected_id > 0 && $selected_id === $field_id ) ) {
				return $field;
			}
		}

		return null;
	}

	/**
	 * Get a safe no-email-generated reason message.
	 *
	 * @param string $reason Reason code.
	 * @return string
	 */
	private function auto_reply_reason_message( $reason ) {
		$messages = array(
			'auto_reply_email_field_not_selected' => __( 'Auto-reply visitor email field is not selected.', 'submission-inbox' ),
			'auto_reply_email_field_not_found'    => __( 'Visitor email field is missing.', 'submission-inbox' ),
			'auto_reply_email_empty'              => __( 'Auto-reply visitor email field was empty.', 'submission-inbox' ),
			'auto_reply_email_invalid'            => __( 'Auto-reply visitor email field did not contain a valid email address.', 'submission-inbox' ),
		);

		return $messages[ $reason ] ?? __( 'Auto-reply requires a valid submitted email field.', 'submission-inbox' );
	}

	/**
	 * Render a self-contained, email-safe HTML body for sending.
	 *
	 * @param array<string,mixed>  $template Template.
	 * @param array<string,mixed>  $form Form.
	 * @param int                  $lead_id Lead id.
	 * @param array<int,array>     $lead_fields Lead fields.
	 * @param array<string,string> $detected Detected values.
	 * @return string
	 */
	private function render_auto_reply_email_for_sending( array $template, array $form, $lead_id, array $lead_fields, array $detected ) {
		return $this->build_compiled_template_email_body( $template, $form, $lead_id, $lead_fields, $detected );
	}

	/**
	 * Build the send email from final compiled HTML.
	 *
	 * @param array<string,mixed>  $template Template.
	 * @param array<string,mixed>  $form Form.
	 * @param int                  $lead_id Lead id.
	 * @param array<int,array>     $lead_fields Lead fields.
	 * @param array<string,string> $detected Detected values.
	 * @return string
	 */
	private function build_compiled_template_email_body( array $template, array $form, $lead_id, array $lead_fields, array $detected ) {
		$html = (string) ( $template['rendered_html'] ?? $template['body'] ?? '' );
		$html = preg_replace( '/<\?(?:php)?[\s\S]*?\?>/i', '', $html );
		$html = strip_shortcodes( $html );
		$html = $this->replace_variables( $html, $form, $lead_id, $lead_fields, $detected, true );
		$html = $this->remove_unresolved_variables( $html );
		$html = $this->sanitize_email_html( $html );

		return AutoReplyTemplateRepository::sanitize_svg_paint_attributes( $html );
	}

	/**
	 * Get a submitted field value by key.
	 *
	 * @param array<int,array> $lead_fields Lead fields.
	 * @param string           $key Field key.
	 * @return string
	 */
	private function field_value( array $lead_fields, $key ) {
		$key = sanitize_key( $key );
		foreach ( $lead_fields as $field ) {
			if ( $key === sanitize_key( $field['field_key'] ?? '' ) ) {
				return sanitize_textarea_field( $field['field_value'] ?? '' );
			}
		}

		return '';
	}

	/**
	 * Get safe visitor email from detected lead data or saved email fields.
	 *
	 * @param array<int,array>     $lead_fields Lead fields.
	 * @param array<string,string> $detected Detected values.
	 * @return string
	 */
	private function visitor_email( array $lead_fields, array $detected ) {
		$email = sanitize_email( $detected['customer_email'] ?? '' );
		if ( is_email( $email ) ) {
			return $email;
		}

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

			$email = sanitize_email( $field['field_value'] ?? '' );
			if ( is_email( $email ) ) {
				return $email;
			}
		}

		return '';
	}

	/**
	 * Get safe form name.
	 *
	 * @param array<string,mixed> $form Form.
	 * @return string
	 */
	private function form_name( array $form ) {
		$name = sanitize_text_field( $form['title'] ?? '' );
		return '' !== $name ? $name : __( 'this form', 'submission-inbox' );
	}

	/**
	 * Get formatted submission date.
	 *
	 * @return string
	 */
	private function submission_date() {
		return sanitize_text_field( current_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) );
	}

	/**
	 * Remove unresolved placeholders from final email HTML.
	 *
	 * @param string $html HTML.
	 * @return string
	 */
	private function remove_unresolved_variables( $html ) {
		$html = preg_replace( '/\{field:[A-Za-z0-9_-]+\}/', '', (string) $html );
		$html = preg_replace( '/\{(?:form_name|submission_date|visitor_email|site_name|lead_id)\}/', '', (string) $html );
		$html = preg_replace( '/\{[A-Za-z0-9_:-]+\}/', '', (string) $html );

		return (string) $html;
	}

	/**
	 * Replace supported variables.
	 *
	 * @param string               $content Content.
	 * @param array<string,mixed>  $form Form.
	 * @param int                  $lead_id Lead id.
	 * @param array<int,array>     $lead_fields Lead fields.
	 * @param array<string,string> $detected Detected values.
	 * @param bool                 $html Whether replacements are for HTML output.
	 * @return string
	 */
	private function replace_variables( $content, array $form, $lead_id, array $lead_fields, array $detected, $html ) {
		$content = (string) $content;
		$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( $form['title'] ?? '' ),
			'submission_date' => current_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ),
			'lead_id'         => (string) absint( $lead_id ),
			'visitor_email'   => $this->visitor_email( $lead_fields, $detected ),
		);

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

		$content = 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
		);

		return (string) $content;
	}

	/**
	 * Log an auto-reply that was intentionally not generated.
	 *
	 * @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'        => self::SOURCE,
				'to_email'      => $recipient,
				'subject'       => $subject,
				'error_message' => $reason,
			)
		);
	}

	/**
	 * Sanitize email HTML after variable replacement.
	 *
	 * @param string $html HTML.
	 * @return string
	 */
	private function sanitize_email_html( $html ) {
		$html = preg_replace( '/<\?(?:php)?[\s\S]*?\?>/i', '', (string) $html );
		$html = strip_shortcodes( $html );
		$html = wp_kses( $html, AutoReplyTemplateRepository::allowed_email_html(), array( 'http', 'https', 'mailto', 'tel' ) );

		return AutoReplyTemplateRepository::sanitize_svg_paint_attributes( $html );
	}
}
