<?php
/**
 * Cart Recovery Email Logs Exporter
 *
 * Exports abandoned cart recovery email logs with cart and recipient details.
 *
 * @package     EDD\Pro\CartRecovery\Admin\Exports\Exporters
 * @copyright   Copyright (c) 2026, Sandhills Development, LLC
 * @license     https://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       3.6.5
 */

namespace EDD\Pro\CartRecovery\Admin\Exports\Exporters;

use EDD\Admin\Exports\Exporters\Exporter;
use EDD\Pro\CartRecovery\Emails\Repository as Emails;
use EDD\Pro\CartRecovery\Carts\Repository as Carts;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore

/**
 * EmailLogs Exporter Class
 *
 * Handles batch export of abandoned cart recovery email logs.
 *
 * @since 3.6.5
 */
class EmailLogs extends Exporter {

	/**
	 * The number of items to process per step.
	 *
	 * @since 3.6.5
	 * @var int
	 */
	protected $per_step = 100;

	/**
	 * Email repository.
	 *
	 * @since 3.6.5
	 * @var Emails|null
	 */
	private $email_repo;

	/**
	 * Cart repository.
	 *
	 * @since 3.6.5
	 * @var Carts|null
	 */
	private $cart_repo;

	/**
	 * Cache for cart data to prevent N+1 queries.
	 *
	 * @since 3.6.5
	 * @var array
	 */
	private $cart_cache = array();

	/**
	 * The status filter.
	 *
	 * @since 3.6.5
	 * @var string
	 */
	private $status = '';

	/**
	 * Constructor.
	 *
	 * @since 3.6.5
	 * @param int $step The step to start at.
	 */
	public function __construct( $step = 1 ) {
		parent::__construct( $step );

		$this->email_repo = new Emails();
		try {
			$this->cart_repo = new Carts();
		} catch ( \Exception $e ) {
			$this->cart_repo = null;
		}
	}

	/**
	 * Set the properties specific to the email logs export.
	 *
	 * @since 3.6.5
	 * @param array $request The form data.
	 */
	public function set_properties( $request ): void {
		if ( isset( $request['email-logs-export-start'] ) ) {
			$this->start = sanitize_text_field( $request['email-logs-export-start'] );
		}
		if ( isset( $request['email-logs-export-end'] ) ) {
			$this->end = sanitize_text_field( $request['email-logs-export-end'] );
		}
		if ( ! empty( $request['status'] ) ) {
			$this->status = sanitize_text_field( $request['status'] );
		}
	}

	/**
	 * Get the export type.
	 *
	 * @since 3.6.5
	 * @return string
	 */
	protected function get_export_type(): string {
		return 'acr_email_logs';
	}

	/**
	 * Get the data headers.
	 *
	 * @since 3.6.5
	 * @return array
	 */
	protected function get_data_headers(): array {
		return array(
			'id'              => __( 'ID', 'easy-digital-downloads' ),
			'template'        => __( 'Template', 'easy-digital-downloads' ),
			'cart_id'         => __( 'Cart ID', 'easy-digital-downloads' ),
			'recipient'       => __( 'Recipient', 'easy-digital-downloads' ),
			'subject'         => __( 'Subject', 'easy-digital-downloads' ),
			'delivery_status' => __( 'Delivery Status', 'easy-digital-downloads' ),
			'sent_date'       => __( 'Sent Date', 'easy-digital-downloads' ),
			'opened_date'     => __( 'Opened Date', 'easy-digital-downloads' ),
			'converted_date'  => __( 'Converted Date', 'easy-digital-downloads' ),
		);
	}

	/**
	 * Get the export data.
	 *
	 * @since 3.6.5
	 * @return array
	 */
	protected function get_data(): array {
		if ( ! $this->email_repo ) {
			return array();
		}

		$args   = $this->get_query_args();
		$result = $this->email_repo->get_emails_for_admin( $args );

		if ( empty( $result['items'] ) ) {
			return array();
		}

		// Prime cart cache to prevent N+1 queries.
		$this->prime_cart_cache( $result['items'] );

		$data = array();
		foreach ( $result['items'] as $item ) {
			$cart            = $this->get_cached_cart( $item->cart_id );
			$recipient_email = ( $cart && isset( $cart->email ) && ! empty( $cart->email ) ) ? $cart->email : '';

			// Prevent CSV injection.
			$safe_email   = $this->sanitize_csv_field( $recipient_email );
			$safe_subject = $this->sanitize_csv_field( $item->email_subject ?? '' );
			$template     = ! empty( $item->template_id ) ? $item->template_id : 'Immediate Recovery';

			$data[] = array(
				'id'              => $item->id,
				'template'        => $template,
				'cart_id'         => $item->cart_id,
				'recipient'       => $safe_email,
				'subject'         => $safe_subject,
				'delivery_status' => $item->delivery_status ?? '',
				'sent_date'       => $item->date_sent ?? '',
				'opened_date'     => $item->opened_date ?? '',
				'converted_date'  => $item->date_converted ?? '',
			);
		}

		return $data;
	}

	/**
	 * Get the total number of items to process.
	 *
	 * @since 3.6.5
	 * @return int
	 */
	protected function get_total(): int {
		if ( ! $this->email_repo ) {
			return 0;
		}

		$args = $this->get_query_args();
		// Remove pagination args for count.
		unset( $args['number'], $args['offset'] );

		$result = $this->email_repo->get_emails_for_admin( $args );

		return isset( $result['total'] ) ? (int) $result['total'] : 0;
	}

	/**
	 * Get the query arguments for fetching emails.
	 *
	 * @since 3.6.5
	 * @return array
	 */
	private function get_query_args(): array {
		$per_step = $this->get_per_step();
		$args     = array(
			'number'  => $per_step,
			'offset'  => ( $this->step * $per_step ) - $per_step,
			'orderby' => 'date_sent',
			'order'   => 'ASC',
		);

		// Add status filter.
		if ( ! empty( $this->status ) ) {
			$args['status'] = $this->status;
		}

		// Add date filters.
		if ( ! empty( $this->start ) ) {
			$start_date_string = EDD()->utils->get_date_string( $this->start );
			$args['date_from'] = edd_get_utc_date_string( $start_date_string );
		}

		if ( ! empty( $this->end ) ) {
			$end_date_string = EDD()->utils->get_date_string( $this->end, 23, 59, 59 );
			$args['date_to'] = edd_get_utc_date_string( $end_date_string );
		}

		return $args;
	}

	/**
	 * Prime the cart cache for the given email items.
	 *
	 * @since 3.6.5
	 * @param array $items Email items.
	 */
	private function prime_cart_cache( array $items ): void {
		if ( ! $this->cart_repo ) {
			return;
		}

		$cart_ids = array();
		foreach ( $items as $item ) {
			if ( ! empty( $item->cart_id ) ) {
				$cart_ids[] = (int) $item->cart_id;
			}
		}

		if ( empty( $cart_ids ) ) {
			return;
		}

		// Batch load carts from database.
		global $wpdb;
		if ( ! ( $wpdb instanceof \wpdb ) ) {
			return;
		}

		$table        = $wpdb->prefix . 'edd_acr_carts';
		$placeholders = implode( ',', array_fill( 0, count( $cart_ids ), '%d' ) );
		$limit        = count( $cart_ids );

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $table is prefixed, $placeholders contains only %d.
		$query = $wpdb->prepare(
			"SELECT id, email FROM {$table} WHERE id IN ({$placeholders}) LIMIT %d",
			array_merge( $cart_ids, array( $limit ) )
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

		if ( false === $query ) {
			return;
		}

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared above.
		$results = $wpdb->get_results( $query );

		// Store in cache.
		foreach ( (array) $results as $cart ) {
			$this->cart_cache[ $cart->id ] = $cart;
		}
	}

	/**
	 * Get cart from cache.
	 *
	 * @since 3.6.5
	 * @param int $cart_id Cart ID.
	 * @return object|null
	 */
	private function get_cached_cart( int $cart_id ): ?object {
		return isset( $this->cart_cache[ $cart_id ] ) ? $this->cart_cache[ $cart_id ] : null;
	}

	/**
	 * Sanitizes CSV fields to prevent injection attacks.
	 *
	 * @since 3.6.5
	 * @param string $field The field to sanitize.
	 * @return string
	 */
	private function sanitize_csv_field( string $field ): string {
		// Prevent CSV injection by neutralizing formula characters.
		$first_char = substr( $field, 0, 1 );
		if ( in_array( $first_char, array( '=', '+', '-', '@' ), true ) ) {
			return "'" . $field;
		}

		return $field;
	}
}
