<?php
/**
 * Campaign Orders Exporter
 *
 * @package     EDD\Pro\CampaignTracker\Admin\Exports
 * @copyright   Copyright (c) 2025, Sandhills Development, LLC
 * @license     https://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       3.6.3
 */

namespace EDD\Pro\CampaignTracker\Admin\Exports;

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

use EDD\Admin\Exports\Exporters\Exporter;

/**
 * Campaign Orders Exporter Class
 *
 * Exports orders filtered by campaign name and date range.
 *
 * @since 3.6.3
 */
class CampaignOrders extends Exporter {

	/**
	 * Number of orders to process per batch.
	 *
	 * @var int
	 */
	protected $per_step = 100;

	/**
	 * Campaign name filter.
	 *
	 * @var string
	 */
	private $campaign = '';

	/**
	 * Set export properties from request data.
	 *
	 * @since 3.6.3
	 * @param array $request Request data from export form.
	 */
	public function set_properties( $request ): void {
		// Handle date filters - check both form field names and simple keys.
		if ( isset( $request['campaign-export-start'] ) ) {
			$this->start = sanitize_text_field( $request['campaign-export-start'] );
		} elseif ( isset( $request['start'] ) ) {
			$this->start = sanitize_text_field( $request['start'] );
		}

		if ( isset( $request['campaign-export-end'] ) ) {
			$this->end = sanitize_text_field( $request['campaign-export-end'] );
		} elseif ( isset( $request['end'] ) ) {
			$this->end = sanitize_text_field( $request['end'] );
		}

		// Handle campaign filter.
		if ( ! empty( $request['campaign_name'] ) ) {
			$this->campaign = sanitize_text_field( $request['campaign_name'] );
		}
	}

	/**
	 * Get the export type identifier.
	 *
	 * @since 3.6.3
	 * @return string Export type.
	 */
	protected function get_export_type(): string {
		return 'campaign_orders';
	}

	/**
	 * Get the CSV column headers.
	 *
	 * @since 3.6.3
	 * @return array Column headers.
	 */
	protected function get_data_headers(): array {
		$columns = array(
			'order_id'         => __( 'Order ID', 'easy-digital-downloads' ),
			'order_number'     => __( 'Order Number', 'easy-digital-downloads' ),
			'customer_email'   => __( 'Customer Email', 'easy-digital-downloads' ),
			'customer_name'    => __( 'Customer Name', 'easy-digital-downloads' ),
			'campaign_name'    => __( 'Campaign Name', 'easy-digital-downloads' ),
			'campaign_source'  => __( 'Campaign Source', 'easy-digital-downloads' ),
			'campaign_medium'  => __( 'Campaign Medium', 'easy-digital-downloads' ),
			'campaign_term'    => __( 'Campaign Term', 'easy-digital-downloads' ),
			'campaign_content' => __( 'Campaign Content', 'easy-digital-downloads' ),
			'order_total'      => __( 'Order Total', 'easy-digital-downloads' ),
			'order_date'       => __( 'Order Date', 'easy-digital-downloads' ),
			'order_status'     => __( 'Order Status', 'easy-digital-downloads' ),
		);

		if ( ! edd_get_option( 'enable_sequential' ) ) {
			unset( $columns['order_number'] );
		}

		return $columns;
	}

	/**
	 * Get the export data for the current batch.
	 *
	 * @since 3.6.3
	 * @return array Export data rows.
	 */
	protected function get_data(): array {
		$data = array();
		$args = $this->get_order_query_args();

		// Add filter to use GROUP BY in the query to avoid duplicate orders from OR meta_query JOINs.
		// GROUP BY is applied before LIMIT/OFFSET, ensuring correct pagination.
		add_filter( 'edd_orders_query_clauses', array( $this, 'use_distinct_select' ), 10, 2 );

		$orders = edd_get_orders( $args );

		// Remove filter after query.
		remove_filter( 'edd_orders_query_clauses', array( $this, 'use_distinct_select' ), 10 );

		foreach ( $orders as $order ) {
			// Get campaign data from individual meta fields.
			$campaign_name    = edd_get_order_meta( $order->id, '_eddct_campaign_name', true );
			$campaign_source  = edd_get_order_meta( $order->id, '_eddct_campaign_source', true );
			$campaign_medium  = edd_get_order_meta( $order->id, '_eddct_campaign_medium', true );
			$campaign_term    = edd_get_order_meta( $order->id, '_eddct_campaign_term', true );
			$campaign_content = edd_get_order_meta( $order->id, '_eddct_campaign_content', true );

			// Skip orders without campaign data (shouldn't happen with our query, but safety check).
			if ( empty( $campaign_name ) && empty( $campaign_source ) && empty( $campaign_medium ) ) {
				continue;
			}

			// Get customer name.
			$customer_name = '';
			if ( $order->customer_id > 0 ) {
				$customer = edd_get_customer( $order->customer_id );
				if ( $customer ) {
					$customer_name = $customer->name;
				}
			}

			$data[] = array(
				'order_id'         => $order->id,
				'order_number'     => $order->get_number(),
				'customer_email'   => $order->email,
				'customer_name'    => $customer_name,
				'campaign_name'    => $campaign_name,
				'campaign_source'  => $campaign_source,
				'campaign_medium'  => $campaign_medium,
				'campaign_term'    => $campaign_term,
				'campaign_content' => $campaign_content,
				'order_total'      => edd_format_amount( $order->total ),
				'order_date'       => $order->date_created,
				'order_status'     => $order->status,
			);
		}

		return $data;
	}

	/**
	 * Get the total number of orders to export.
	 *
	 * @since 3.6.3
	 * @return int Total order count.
	 */
	protected function get_total(): int {
		$args = $this->get_order_query_args();
		unset( $args['number'], $args['offset'] );

		// Add filter to use DISTINCT count when counting with OR meta_query to avoid duplicates.
		add_filter( 'edd_orders_query_clauses', array( $this, 'use_distinct_count' ), 10, 2 );

		$count = edd_count_orders( $args );

		// Remove filter after count.
		remove_filter( 'edd_orders_query_clauses', array( $this, 'use_distinct_count' ), 10 );

		return $count;
	}

	/**
	 * Filter query clauses to use DISTINCT count to avoid duplicates from meta_query JOINs.
	 *
	 * @since 3.6.3
	 * @param array                       $clauses Query clauses.
	 * @param \EDD\Database\Queries\Order $query   Order query object.
	 * @return array Modified clauses.
	 */
	public function use_distinct_count( $clauses, $query ) {
		// Only modify if this is a count query.
		if ( ! empty( $query->query_vars['count'] ) && ! empty( $query->query_vars['groupby'] ) === false ) {
			// Replace COUNT(*) with COUNT(DISTINCT o.id) to avoid counting duplicate rows from JOINs.
			if ( 'COUNT(*)' === $clauses['fields'] ) {
				$clauses['fields'] = 'COUNT(DISTINCT edd_o.id)';
			}
		}

		return $clauses;
	}

	/**
	 * Filter query clauses to use GROUP BY to avoid duplicate orders from meta_query JOINs.
	 *
	 * @since 3.6.3
	 * @param array                       $clauses Query clauses.
	 * @param \EDD\Database\Queries\Order $query   Order query object.
	 * @return array Modified clauses.
	 */
	public function use_distinct_select( $clauses, $query ) {
		// Only modify if this is NOT a count query.
		if ( empty( $query->query_vars['count'] ) ) {
			// Use GROUP BY instead of DISTINCT to properly handle OFFSET with duplicates.
			// GROUP BY is applied before LIMIT/OFFSET, ensuring correct pagination.
			if ( empty( $clauses['groupby'] ) || false === strpos( $clauses['groupby'], 'edd_o.id' ) ) {
				$clauses['groupby'] = 'edd_o.id';
			}
		}

		return $clauses;
	}

	/**
	 * Build the query arguments for fetching orders.
	 *
	 * @since 3.6.3
	 * @return array Query arguments.
	 */
	private function get_order_query_args(): array {
		$per_step = $this->get_per_step();

		$args = array(
			'type'       => 'sale',
			'status__in' => array( 'complete', 'revoked', 'partially_refunded' ),
			'number'     => $per_step,
			'offset'     => ( $this->step * $per_step ) - $per_step,
			'order'      => 'ASC',
			'orderby'    => 'date_created',
		);

		// Add date filters if provided.
		if ( ! empty( $this->start ) || ! empty( $this->end ) ) {
			$args['date_query'] = $this->get_date_query();
		}

		// Add meta query for campaign filtering.
		$args['meta_query'] = $this->get_campaign_meta_query();

		return $args;
	}

	/**
	 * Build the meta query for campaign filtering.
	 *
	 * @since 3.6.3
	 * @return array Meta query array.
	 */
	private function get_campaign_meta_query(): array {
		// Specific campaign: only query for that campaign name.
		if ( ! empty( $this->campaign ) ) {
			return array(
				array(
					'key'   => '_eddct_campaign_name',
					'value' => $this->campaign,
				),
			);
		}

		// All campaigns: get orders with at least one campaign field (name, source, or medium).
		// This creates multiple JOINs, but we handle duplicates with GROUP BY and DISTINCT.
		return array(
			array(
				'relation' => 'OR',
				array(
					'key'     => '_eddct_campaign_name',
					'compare' => 'EXISTS',
				),
				array(
					'key'     => '_eddct_campaign_source',
					'compare' => 'EXISTS',
				),
				array(
					'key'     => '_eddct_campaign_medium',
					'compare' => 'EXISTS',
				),
			),
		);
	}
}
