<?php
/**
 * Sync event recorder.
 *
 * @package SubmissionInbox
 */

namespace SUBIN\Sync;

use SUBIN\Mail\EmailErrorClassifier;

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

/**
 * Records local domain events for future Cloud synchronization.
 */
class SyncEventRecorder {
	/**
	 * Sync events.
	 *
	 * @var SyncEventRepository
	 */
	private $sync_events;

	/**
	 * Lead repository.
	 *
	 * @var \SUBIN\Leads\LeadRepository
	 */
	private $leads;

	/**
	 * Lead fields.
	 *
	 * @var \SUBIN\Leads\LeadFieldRepository
	 */
	private $fields;

	/**
	 * Lead activity.
	 *
	 * @var \SUBIN\Leads\LeadActivityRepository
	 */
	private $activity;

	/**
	 * Mail events.
	 *
	 * @var \SUBIN\MailLog\MailEventRepository
	 */
	private $mail_events;

	/**
	 * Settings repository.
	 *
	 * @var \SUBIN_SettingsRepository
	 */
	private $settings;

	/**
	 * Email error classifier.
	 *
	 * @var EmailErrorClassifier
	 */
	private $error_classifier;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->sync_events      = new SyncEventRepository();
		$this->leads            = new \SUBIN\Leads\LeadRepository();
		$this->fields           = new \SUBIN\Leads\LeadFieldRepository();
		$this->activity         = new \SUBIN\Leads\LeadActivityRepository();
		$this->mail_events      = new \SUBIN\MailLog\MailEventRepository();
		$this->settings         = new \SUBIN_SettingsRepository();
		$this->error_classifier = new EmailErrorClassifier();
	}

	/**
	 * Register event hooks.
	 *
	 * @return void
	 */
	public function register() {
		add_action( 'subin_lead_created', array( $this, 'record_lead_created' ), 10, 1 );
		add_action( 'subin_lead_workflow_updated', array( $this, 'record_lead_workflow_updated' ), 10, 2 );
		add_action( 'subin_lead_note_added', array( $this, 'record_lead_note_added' ), 10, 2 );
		add_action( 'subin_mail_event_status_updated', array( $this, 'record_mail_event_status_updated' ), 10, 2 );
		add_action( 'subin_mail_event_no_email_generated', array( $this, 'record_no_email_generated' ), 10, 1 );
	}

	/**
	 * Record lead.created.
	 *
	 * @param int $lead_id Lead id.
	 * @return void
	 */
	public function record_lead_created( $lead_id ) {
		if ( ! $this->should_record() ) {
			return;
		}

		$payload = $this->lead_payload( $lead_id );
		if ( empty( $payload ) ) {
			return;
		}

		$this->sync_events->enqueue( 'lead.created', 'lead', $lead_id, $payload );
	}

	/**
	 * Record lead.workflow_updated.
	 *
	 * @param int                 $lead_id Lead id.
	 * @param array<string,mixed> $updates Updates.
	 * @return void
	 */
	public function record_lead_workflow_updated( $lead_id, array $updates ) {
		if ( ! $this->should_record() ) {
			return;
		}

		$this->sync_events->enqueue(
			'lead.workflow_updated',
			'lead',
			$lead_id,
			array(
				'lead'    => $this->lead_payload( $lead_id ),
				'updates' => $updates,
			)
		);
	}

	/**
	 * Record lead.note_added.
	 *
	 * @param int $lead_id Lead id.
	 * @param int $note_id Note id.
	 * @return void
	 */
	public function record_lead_note_added( $lead_id, $note_id ) {
		if ( ! $this->should_record() ) {
			return;
		}

		$this->sync_events->enqueue(
			'lead.note_added',
			'lead',
			$lead_id,
			array(
				'lead'    => $this->lead_payload( $lead_id ),
				'note_id' => absint( $note_id ),
			)
		);
	}

	/**
	 * Record email status events.
	 *
	 * @param int    $event_id Event id.
	 * @param string $status Status.
	 * @return void
	 */
	public function record_mail_event_status_updated( $event_id, $status ) {
		if ( ! $this->should_record() ) {
			return;
		}

		$status = sanitize_key( (string) $status );
		if ( ! in_array( $status, array( 'sent', 'failed' ), true ) ) {
			return;
		}

		$this->sync_events->enqueue(
			'email.' . $status,
			'mail_event',
			$event_id,
			$this->mail_event_payload( $event_id, $status )
		);
	}

	/**
	 * Record no-email-generated event.
	 *
	 * @param int $event_id Event id.
	 * @return void
	 */
	public function record_no_email_generated( $event_id ) {
		if ( ! $this->should_record() ) {
			return;
		}

		$this->sync_events->enqueue(
			'email.no_email_generated',
			'mail_event',
			$event_id,
			$this->mail_event_payload( $event_id, 'no_email_generated' )
		);
	}

	/**
	 * Build mail event payload.
	 *
	 * @param int    $event_id Event id.
	 * @param string $status Status.
	 * @return array<string,mixed>
	 */
	private function mail_event_payload( $event_id, $status ) {
		$event = $this->mail_events->get( $event_id );
		if ( ! $event ) {
			return array(
				'event_id' => absint( $event_id ),
				'status'   => sanitize_key( $status ),
			);
		}

		$mode = $this->data_mode();
		$base = array(
			'event_id'      => absint( $event->id ?? $event_id ),
			'lead_id'       => absint( $event->lead_id ?? 0 ),
			'source'        => sanitize_key( $event->source ?? '' ),
			'status'        => sanitize_key( $status ),
			'error_message' => $this->error_classifier->sanitize_technical_error( (string) ( $event->error_message ?? '' ) ),
			'created_at'    => sanitize_text_field( $event->created_at ?? '' ),
		);

		if ( 'full_lead_data' !== $mode ) {
			$base['to_email_hash'] = $this->hash_value( $event->to_email ?? '' );
			$base['subject_hash']  = $this->hash_value( $event->subject ?? '' );

			return $base;
		}

		$base['to_email'] = sanitize_textarea_field( $event->to_email ?? '' );
		$base['subject']  = sanitize_text_field( $event->subject ?? '' );

		return $base;
	}

	/**
	 * Build full lead payload.
	 *
	 * @param int $lead_id Lead id.
	 * @return array<string,mixed>
	 */
	private function lead_payload( $lead_id ) {
		$lead = $this->leads->get( $lead_id );
		if ( ! $lead ) {
			return array();
		}

		$mode = $this->data_mode();
		$base = array(
			'id'              => absint( $lead->id ),
			'provider'        => sanitize_key( $lead->provider ?? '' ),
			'form_id'         => sanitize_text_field( $lead->form_id ?? '' ),
			'form_name'       => sanitize_text_field( $lead->form_name ?? '' ),
			'status'          => sanitize_key( $lead->status ?? '' ),
			'email_status'    => sanitize_key( $lead->email_status ?? '' ),
			'workflow_status' => sanitize_key( $lead->workflow_status ?? 'open' ),
			'priority'        => sanitize_key( $lead->priority ?? 'normal' ),
			'submitted_at'    => sanitize_text_field( $lead->submitted_at ?? '' ),
		);

		if ( 'health_only' === $mode ) {
			return $base;
		}

		$summary = array_merge(
			$base,
			array(
				'customer_name'          => sanitize_text_field( $lead->customer_name ?? '' ),
				'customer_email_hash'    => $this->hash_value( $lead->customer_email ?? '' ),
				'customer_email_preview' => $this->email_preview( $lead->customer_email ?? '' ),
				'assigned_user_id' => absint( $lead->assigned_user_id ?? 0 ),
				'follow_up_at'     => sanitize_text_field( $lead->follow_up_at ?? '' ),
			)
		);

		if ( 'lead_summary' === $mode ) {
			return $summary;
		}

		$fields = array();
		foreach ( $this->fields->get_by_lead_id( $lead_id ) as $field ) {
			$is_sensitive = ! empty( $field->is_sensitive );
			$fields[] = array(
				'key'          => sanitize_text_field( $field->field_key ?? '' ),
				'label'        => sanitize_text_field( $field->field_label ?? '' ),
				'value'        => $is_sensitive ? '[redacted]' : sanitize_textarea_field( $field->field_value ?? '' ),
				'is_sensitive' => $is_sensitive,
			);
		}

		$notes = array();
		foreach ( $this->activity->get_notes( $lead_id ) as $note ) {
			$notes[] = array(
				'id'         => absint( $note->id ?? 0 ),
				'user_id'    => absint( $note->user_id ?? 0 ),
				'note'       => sanitize_textarea_field( $note->note ?? '' ),
				'created_at' => sanitize_text_field( $note->created_at ?? '' ),
			);
		}

		return array_merge(
			$summary,
			array(
				'customer_email'  => sanitize_email( $lead->customer_email ?? '' ),
				'customer_phone'  => sanitize_text_field( $lead->customer_phone ?? '' ),
				'message_preview' => sanitize_textarea_field( $lead->message_preview ?? '' ),
				'source_url'      => esc_url_raw( $lead->source_url ?? '' ),
				'referrer'        => esc_url_raw( $lead->referrer ?? '' ),
				'fields'          => $fields,
				'notes'           => $notes,
			)
		);
	}

	/**
	 * Whether local sync events should be recorded.
	 *
	 * @return bool
	 */
	private function should_record() {
		$settings = $this->settings->all();

		return ! empty( $settings['cloud_sync_enabled'] );
	}

	/**
	 * Configured local sync data mode.
	 *
	 * @return string
	 */
	private function data_mode() {
		$settings = $this->settings->all();
		$mode     = sanitize_key( (string) ( $settings['cloud_sync_allowed_mode'] ?? $settings['cloud_sync_data_mode'] ?? 'lead_summary' ) );

		if ( 'full_lead_data' === $mode && ( empty( $settings['cloud_sync_full_data_consent'] ) || empty( $settings['cloud_sync_full_data_enabled'] ) ) ) {
			return 'lead_summary';
		}

		return in_array( $mode, array( 'health_only', 'lead_summary', 'full_lead_data' ), true ) ? $mode : 'lead_summary';
	}

	/**
	 * Hash a scalar value for non-full sync modes.
	 *
	 * @param mixed $value Value.
	 * @return string
	 */
	private function hash_value( $value ) {
		$value = strtolower( trim( (string) $value ) );

		return '' === $value ? '' : hash( 'sha256', $value );
	}

	/**
	 * Mask an email for summary sync mode.
	 *
	 * @param mixed $email Email.
	 * @return string
	 */
	private function email_preview( $email ) {
		$email = sanitize_email( (string) $email );
		if ( '' === $email || false === strpos( $email, '@' ) ) {
			return '';
		}

		list( $local, $domain ) = explode( '@', $email, 2 );

		return substr( $local, 0, 2 ) . '***@' . $domain;
	}
}
