<?php
/**
 * Transactional lead capture service.
 *
 * @package SubmissionInbox
 */

namespace SUBIN\Leads;

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

/**
 * Persists a lead and all of its fields as one logical operation.
 */
class LeadCaptureService {
	/**
	 * Lead repository.
	 *
	 * @var LeadRepository
	 */
	private $leads;

	/**
	 * Lead field repository.
	 *
	 * @var LeadFieldRepository
	 */
	private $fields;

	/**
	 * Constructor.
	 *
	 * @param LeadRepository      $leads Lead repository.
	 * @param LeadFieldRepository $fields Lead field repository.
	 */
	public function __construct( LeadRepository $leads, LeadFieldRepository $fields ) {
		$this->leads  = $leads;
		$this->fields = $fields;
	}

	/**
	 * Save a complete lead.
	 *
	 * @param array<string,mixed> $lead_data Lead data.
	 * @param array<int,array>    $fields Lead fields.
	 * @return int|\WP_Error
	 */
	public function capture( array $lead_data, array $fields ) {
		global $wpdb;

		$transaction_started = false !== $wpdb->query( 'START TRANSACTION' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$lead_id             = $this->leads->create( $lead_data );

		if ( $lead_id <= 0 ) {
			$this->rollback( $transaction_started );
			return $this->failure( 'subin_lead_insert_failed', __( 'The submission could not be stored.', 'submission-inbox' ) );
		}

		$fields_result = $this->fields->insert_fields( $lead_id, $fields );
		if ( is_wp_error( $fields_result ) ) {
			$this->rollback_and_compensate( $lead_id, $transaction_started );
			do_action( 'subin_lead_capture_failed', $fields_result, $lead_id );
			return $fields_result;
		}

		if ( $transaction_started && false === $wpdb->query( 'COMMIT' ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$this->rollback_and_compensate( $lead_id, true );
			return $this->failure( 'subin_lead_commit_failed', __( 'The submission could not be stored completely.', 'submission-inbox' ) );
		}

		do_action( 'subin_lead_created', $lead_id );

		return $lead_id;
	}

	/**
	 * Roll back a transaction when one was started.
	 *
	 * @param bool $transaction_started Whether START TRANSACTION succeeded.
	 * @return void
	 */
	private function rollback( $transaction_started ) {
		if ( ! $transaction_started ) {
			return;
		}

		global $wpdb;
		$wpdb->query( 'ROLLBACK' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	/**
	 * Roll back and remove rows left by a non-transactional table engine.
	 *
	 * @param int  $lead_id Lead id.
	 * @param bool $transaction_started Whether START TRANSACTION succeeded.
	 * @return void
	 */
	private function rollback_and_compensate( $lead_id, $transaction_started ) {
		$this->rollback( $transaction_started );
		$this->fields->delete_by_lead_id( $lead_id );
		$this->leads->delete( $lead_id );
	}

	/**
	 * Build a safe storage error.
	 *
	 * @param string $code Error code.
	 * @param string $message Safe message.
	 * @return \WP_Error
	 */
	private function failure( $code, $message ) {
		global $wpdb;

		$error = new \WP_Error(
			sanitize_key( $code ),
			sanitize_text_field( $message ),
			array( 'db_error' => sanitize_text_field( (string) $wpdb->last_error ) )
		);

		do_action( 'subin_lead_capture_failed', $error, 0 );

		return $error;
	}
}
