<?php
/**
 * Privacy-safe presentation model for immutable mail events.
 *
 * @package SubmissionInbox
 */

namespace SUBIN\MailLog;

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

/**
 * Gives every Email Log surface one honest vocabulary for handoff and delivery.
 */
final class MailEventPresenter {
	/** @var int */
	private $stalled_after_seconds;

	/**
	 * @param int $stalled_after_seconds Pending age before it needs attention.
	 */
	public function __construct( $stalled_after_seconds = MailEventRepository::STALLED_AFTER_SECONDS ) {
		$this->stalled_after_seconds = max( 60, absint( $stalled_after_seconds ) );
	}

	/**
	 * Present one event without exposing stored content or claiming delivery.
	 *
	 * @param object                  $event Event row, preferably repository-enriched.
	 * @param \DateTimeInterface|null $now Current site time for deterministic callers.
	 * @return array<string,mixed>
	 */
	public function present( $event, ?\DateTimeInterface $now = null ) {
		if ( ! is_object( $event ) ) {
			return array();
		}

		return array(
			'id'          => absint( $event->id ?? 0 ),
			'outcome'     => $this->outcome( $event, $now ),
			'email_type'  => $this->email_type( $event ),
			'event_kind'  => $this->event_kind( $event ),
			'origin'      => $this->origin( $event ),
			'fingerprint' => $this->fingerprint( $event ),
			'evidence'    => $this->provider_evidence( $event ),
		);
	}

	/**
	 * Resolve the strongest evidenced outcome.
	 *
	 * @param object                  $event Event row.
	 * @param \DateTimeInterface|null $now Current site time.
	 * @return array{key:string,label:string,description:string,tone:string,delivery_verified:bool}
	 */
	public function outcome( $event, ?\DateTimeInterface $now = null ) {
		$evidence = $this->provider_evidence( $event );
		if ( ! empty( $evidence ) ) {
			$provider_outcome = $this->provider_outcome( $evidence['status'] );
			if ( ! empty( $provider_outcome ) ) {
				return $provider_outcome;
			}
		}

		$status = sanitize_key( (string) ( $event->status ?? MailEventRepository::STATUS_UNKNOWN ) );
		switch ( $status ) {
			case MailEventRepository::STATUS_SENT:
				if ( 'pre_wp_mail_short_circuit' === sanitize_key( (string) ( $event->reason_code ?? '' ) ) ) {
					return $this->outcome_definition(
						'handled',
						__( 'Handled before transport', 'submission-inbox' ),
						__( 'A WordPress filter reported success before the normal mail transport ran. Transport acceptance and recipient delivery were not verified.', 'submission-inbox' ),
						'info'
					);
				}
				return $this->outcome_definition(
					'accepted',
					__( 'Accepted by WordPress', 'submission-inbox' ),
					__( 'WordPress accepted this email for sending. Delivery to the recipient has not been verified.', 'submission-inbox' ),
					'success'
				);
			case MailEventRepository::STATUS_FAILED:
				return $this->outcome_definition(
					'failed',
					__( 'Failed', 'submission-inbox' ),
					__( 'WordPress or the configured mail transport rejected this sending attempt.', 'submission-inbox' ),
					'danger'
				);
			case MailEventRepository::STATUS_NO_EMAIL_GENERATED:
					return $this->outcome_definition(
						'not_generated',
						__( 'Not generated', 'submission-inbox' ),
						__( 'No email message reached the WordPress mail transport. Inspect the reason for this event.', 'submission-inbox' ),
					'warning'
				);
			case MailEventRepository::STATUS_PENDING:
				if ( $this->is_stalled( $event, $now ) ) {
					return $this->outcome_definition(
						'stalled',
						__( 'Stalled', 'submission-inbox' ),
						__( 'This attempt has remained pending longer than expected and needs review.', 'submission-inbox' ),
						'warning'
					);
				}
				return $this->outcome_definition(
					'processing',
					__( 'Processing', 'submission-inbox' ),
					__( 'The sending attempt is still within the normal processing window.', 'submission-inbox' ),
					'info'
				);
			default:
				return $this->outcome_definition(
					'awaiting',
					__( 'Awaiting outcome', 'submission-inbox' ),
					__( 'WordPress ended without a reliable success or failure signal.', 'submission-inbox' ),
					'info'
				);
		}
	}

	/**
	 * Resolve a stable type independently from the form/plugin origin.
	 *
	 * @param object $event Event row.
	 * @return array{key:string,label:string}
	 */
	public function email_type( $event ) {
		$type = sanitize_key( (string) ( $event->email_type ?? $event->job_email_type ?? '' ) );
		if ( '' === $type ) {
			$kind = $this->event_kind( $event );
			$type = $kind['key'];
		}

		$labels = array(
			'admin_notification'   => __( 'Admin notification', 'submission-inbox' ),
			'visitor_auto_reply'   => __( 'Visitor confirmation', 'submission-inbox' ),
			'visitor_confirmation' => __( 'Visitor confirmation', 'submission-inbox' ),
			'auto_reply_test'      => __( 'Auto-reply test', 'submission-inbox' ),
			'health_verification'  => __( 'Health verification', 'submission-inbox' ),
			'wordpress_email'      => __( 'WordPress email', 'submission-inbox' ),
			'generic'              => __( 'WordPress email', 'submission-inbox' ),
		);
		if ( ! isset( $labels[ $type ] ) ) {
			$type = 'wordpress_email';
		}

		return array( 'key' => $type, 'label' => $labels[ $type ] );
	}

	/**
	 * Resolve the immutable event purpose, with conservative legacy inference.
	 *
	 * @param object $event Event row.
	 * @return array{key:string,label:string}
	 */
	public function event_kind( $event ) {
		$kind = sanitize_key( (string) ( $event->effective_event_kind ?? $event->event_kind ?? '' ) );
		if ( ! in_array( $kind, $this->event_kinds(), true ) ) {
			$source = sanitize_key( (string) ( $event->source ?? '' ) );
			if ( 'submission_inbox_auto_reply' === $source ) {
				$kind = MailEventRepository::EVENT_KIND_VISITOR_CONFIRMATION;
			} elseif ( 'submission_inbox_form' === $source ) {
				$kind = MailEventRepository::EVENT_KIND_ADMIN_NOTIFICATION;
			} else {
				$kind = MailEventRepository::EVENT_KIND_GENERIC;
			}
		}

		$labels = array(
			MailEventRepository::EVENT_KIND_GENERIC              => __( 'WordPress email', 'submission-inbox' ),
			MailEventRepository::EVENT_KIND_ADMIN_NOTIFICATION   => __( 'Admin notification', 'submission-inbox' ),
			MailEventRepository::EVENT_KIND_VISITOR_CONFIRMATION => __( 'Visitor confirmation', 'submission-inbox' ),
			MailEventRepository::EVENT_KIND_AUTO_REPLY_TEST      => __( 'Auto-reply test', 'submission-inbox' ),
			MailEventRepository::EVENT_KIND_HEALTH_VERIFICATION  => __( 'Health verification', 'submission-inbox' ),
		);

		return array( 'key' => $kind, 'label' => $labels[ $kind ] );
	}

	/**
	 * Resolve form origin separately from email purpose.
	 *
	 * @param object $event Event row.
	 * @return array{key:string,label:string,source:string,form_id:string,form_name:string}
	 */
	public function origin( $event ) {
		$provider = sanitize_key( (string) ( $event->effective_provider ?? $event->provider ?? $event->lead_provider ?? '' ) );
		$source   = sanitize_key( (string) ( $event->source ?? 'unknown' ) );
		$key      = '' !== $provider ? $provider : $source;
		$labels   = array(
			'submission_inbox_form'       => __( 'InboxMend Forms', 'submission-inbox' ),
			'submission_inbox_auto_reply' => __( 'InboxMend Auto-replies', 'submission-inbox' ),
			'contact_form_7'              => __( 'Contact Form 7', 'submission-inbox' ),
			'wpforms'                     => __( 'WPForms', 'submission-inbox' ),
			'fluent_forms'                => __( 'Fluent Forms', 'submission-inbox' ),
			'gravity_forms'               => __( 'Gravity Forms', 'submission-inbox' ),
			'forminator'                  => __( 'Forminator', 'submission-inbox' ),
			'ninja_forms'                 => __( 'Ninja Forms', 'submission-inbox' ),
			'wp_mail'                     => __( 'WordPress', 'submission-inbox' ),
			'wordpress'                   => __( 'WordPress', 'submission-inbox' ),
			'unknown'                     => __( 'Unknown origin', 'submission-inbox' ),
		);
		$label = $labels[ $key ] ?? ucwords( str_replace( array( '-', '_' ), ' ', $key ) );

		return array(
			'key'       => '' !== $key ? $key : 'unknown',
			'label'     => '' !== $label ? $label : __( 'Unknown origin', 'submission-inbox' ),
			'source'    => $source,
			'form_id'   => sanitize_text_field( (string) ( $event->effective_form_id ?? $event->form_id ?? $event->lead_form_id ?? '' ) ),
			'form_name' => sanitize_text_field( (string) ( $event->form_name ?? '' ) ),
		);
	}

	/**
	 * Explain the non-reversible message hash without exposing message content.
	 *
	 * @param object $event Event row.
	 * @return array{value:string,label:string,description:string}|array<empty>
	 */
	public function fingerprint( $event ) {
		$hash = strtolower( trim( (string) ( $event->message_hash ?? '' ) ) );
		if ( ! preg_match( '/^[a-f0-9]{64}$/', $hash ) ) {
			return array();
		}

		return array(
			'value'       => substr( $hash, 0, 12 ),
			'label'       => __( 'Privacy fingerprint', 'submission-inbox' ),
			'description' => __( 'A one-way fingerprint used to correlate attempts. InboxMend does not store the email body in this log.', 'submission-inbox' ),
		);
	}

	/**
	 * Return provider evidence only when every authoritative fact is present.
	 *
	 * @param object $event Event row.
	 * @return array{status:string,message_id:string,source:string,occurred_at:string,occurred_at_gmt:string}|array<empty>
	 */
	public function provider_evidence( $event ) {
		$status     = sanitize_key( (string) ( $event->provider_status ?? '' ) );
		$message_id = sanitize_text_field( (string) ( $event->provider_message_id ?? '' ) );
		$source     = sanitize_key( (string) ( $event->provider_evidence_source ?? '' ) );
		$occurred   = sanitize_text_field( (string) ( $event->provider_evidence_at ?? '' ) );
		$allowed    = array( 'accepted', 'queued', 'delivered', 'bounced', 'deferred', 'dropped', 'complained', 'opened' );
		if ( ! in_array( $status, $allowed, true ) || '' === $message_id || '' === $source || ! preg_match( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $occurred ) ) {
			return array();
		}
		$occurred_utc = \DateTimeImmutable::createFromFormat( '!Y-m-d H:i:s', $occurred, new \DateTimeZone( 'UTC' ) );
		if ( ! $occurred_utc || $occurred_utc->format( 'Y-m-d H:i:s' ) !== $occurred ) {
			return array();
		}

		return array(
			'status'      => $status,
			'message_id'  => $message_id,
			'source'      => $source,
			'occurred_at' => $occurred_utc->setTimezone( wp_timezone() )->format( 'Y-m-d H:i:s' ),
			'occurred_at_gmt' => $occurred,
		);
	}

	/** @return array<string,mixed> */
	private function provider_outcome( $status ) {
		$definitions = array(
			'accepted'   => array( __( 'Accepted by provider', 'submission-inbox' ), __( 'The authenticated email provider accepted this message. Recipient delivery has not been verified.', 'submission-inbox' ), 'success', false ),
			'queued'     => array( __( 'Queued by provider', 'submission-inbox' ), __( 'The authenticated email provider queued this message for delivery.', 'submission-inbox' ), 'info', false ),
			'delivered'  => array( __( 'Delivered', 'submission-inbox' ), __( 'The authenticated email provider confirmed delivery.', 'submission-inbox' ), 'success', true ),
			'bounced'    => array( __( 'Bounced', 'submission-inbox' ), __( 'The authenticated email provider reported that delivery bounced.', 'submission-inbox' ), 'danger', false ),
			'dropped'    => array( __( 'Dropped', 'submission-inbox' ), __( 'The authenticated email provider dropped this message before delivery.', 'submission-inbox' ), 'danger', false ),
			'complained' => array( __( 'Spam complaint', 'submission-inbox' ), __( 'The authenticated email provider reported a spam complaint.', 'submission-inbox' ), 'danger', false ),
			'opened'     => array( __( 'Opened', 'submission-inbox' ), __( 'The authenticated email provider reported an open event.', 'submission-inbox' ), 'success', true ),
			'deferred'   => array( __( 'Deferred', 'submission-inbox' ), __( 'The authenticated email provider deferred delivery and may retry.', 'submission-inbox' ), 'warning', false ),
		);
		if ( ! isset( $definitions[ $status ] ) ) {
			return array();
		}

		$definition = $definitions[ $status ];
		return $this->outcome_definition( $status, $definition[0], $definition[1], $definition[2], $definition[3] );
	}

	/** @return array{key:string,label:string,description:string,tone:string,delivery_verified:bool} */
	private function outcome_definition( $key, $label, $description, $tone, $delivery_verified = false ) {
		return array(
			'key'               => sanitize_key( $key ),
			'label'             => (string) $label,
			'description'       => (string) $description,
			'tone'              => sanitize_key( $tone ),
			'delivery_verified' => (bool) $delivery_verified,
		);
	}

	/** @return bool */
	private function is_stalled( $event, ?\DateTimeInterface $now ) {
		$updated_at = sanitize_text_field( (string) ( $event->updated_at ?? $event->created_at ?? '' ) );
		$updated    = \DateTimeImmutable::createFromFormat( 'Y-m-d H:i:s', $updated_at, wp_timezone() );
		if ( ! $updated ) {
			return true;
		}

		if ( null === $now ) {
			$now = current_datetime();
		}

		return $updated->getTimestamp() < ( $now->getTimestamp() - $this->stalled_after_seconds );
	}

	/** @return array<int,string> */
	private function event_kinds() {
		return array(
			MailEventRepository::EVENT_KIND_GENERIC,
			MailEventRepository::EVENT_KIND_ADMIN_NOTIFICATION,
			MailEventRepository::EVENT_KIND_VISITOR_CONFIRMATION,
			MailEventRepository::EVENT_KIND_AUTO_REPLY_TEST,
			MailEventRepository::EVENT_KIND_HEALTH_VERIFICATION,
		);
	}
}
