<?php

namespace WC_Affiliate\API;

defined( 'ABSPATH' ) || exit;

use WC_Affiliate\Models\Affiliate;
use WC_Affiliate\Models\Commission;
use WC_Affiliate\Models\Referral as ReferralModel;
use WC_Affiliate\Traits\Auth;
use WC_Affiliate\Traits\Date;
use WC_Affiliate\Traits\Rest;
use WC_Affiliate\Traits\Validation;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

/**
 * Referral API Controller
 *
 * Handles REST API requests for referral management including CRUD operations,
 * bulk updates, status changes, and statistics retrieval. Provides endpoints
 * for managing referral data with proper validation and access control.
 *
 * @since   1.0.0
 * @package WC_Affiliate\API
 */
class Referral {

	use Auth;
	use Date;
	use Rest;
	use Validation;

	/**
	 * Get referrals list.
	 *
	 * @since 1.0.0
	 *
	 * @param WP_REST_Request $request The REST request object containing filter parameters.
	 *
	 * @return WP_REST_Response|WP_Error The response with referrals list and pagination headers.
	 */
	public function list( WP_REST_Request $request ) {
		$pagination   = $this->get_pagination_params( $request );
		$affiliate_id = (int) $request->get_param( 'affiliate_id' );
		$order_id     = (int) $request->get_param( 'order_id' );
		$product_id   = (int) $request->get_param( 'product_id' );
		$status       = array_map( 'sanitize_text_field', $request->get_param( 'status' ) );
		$start_date   = sanitize_text_field( $request->get_param( 'start_date' ) );
		$end_date     = sanitize_text_field( $request->get_param( 'end_date' ) );
		$search       = sanitize_text_field( $request->get_param( 'search' ) );

		$args = array(
			'limit'   => $pagination['limit'],
			'offset'  => $pagination['offset'],
			'orderby' => 'time',
			'order'   => 'DESC',
		);

		if ( $affiliate_id ) {
			$args['affiliate_id'] = $affiliate_id;
		}

		if ( $status ) {
			$args['status'] = $status;
		}

		if ( $start_date ) {
			$args['start_date'] = $start_date;
		}

		if ( $end_date ) {
			$args['end_date'] = $end_date;
		}

		if ( $order_id ) {
			$args['order_id'] = $order_id;
		}

		if ( $product_id ) {
			$args['product_id'] = $product_id;
		}

		if ( $search ) {
			$args['search'] = $search;
		}

		$referral_model  = new ReferralModel();
		$referrals       = $referral_model->get_referrals( $args );
		$total_referrals = $referral_model->get_count( $args );

		// Format response data.
		$data = array();
		foreach ( $referrals as $referral ) {
			// Collect data for each referral.
			$referral_data = $referral->to_array();

			// Update the formatted order total and commission.
			$referral_data['order_total_formatted'] = wca_format_price( $referral->get_order_total() );
			$referral_data['commission_formatted']  = wca_format_price( $referral->get_commission() );

			$data[] = $referral_data;
		}

		/**
		 * Filter referrals list data before response.
		 *
		 * Allows modification of the referrals list data.
		 *
		 * @since 1.0.0
		 *
		 * @param array           $data    Array of referral data.
		 * @param array           $args    Query arguments.
		 * @param WP_REST_Request $request The original request object.
		 */
		$data = apply_filters( 'wc_affiliate_rest_api_referrals_list_data', $data, $args, $request );

		$response = $this->response_success( $data );

		return $this->add_pagination_headers( $response, $total_referrals, $pagination['page'], $pagination['per_page'] );
	}

	/**
	 * Create new referral
	 *
	 * @param WP_REST_Request $request REST request object containing referral data.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function create( WP_REST_Request $request ) {
		$affiliate_id = (int) $request->get_param( 'affiliate_id' );
		$order_id     = (int) $request->get_param( 'order_id' );
		$commission   = (float) $request->get_param( 'commission' );
		$order_total  = (float) $request->get_param( 'order_total' );
		$visit_id     = (int) $request->get_param( 'visit_id' );
		$type         = sanitize_text_field( $request->get_param( 'type' ) );
		$products     = array_map( 'sanitize_text_field', $request->get_param( 'products' ) );

		// Validate affiliate exists.
		$affiliate = new Affiliate( $affiliate_id );
		if ( ! $affiliate->exists() ) {
			return $this->response_error( __( 'Invalid affiliate ID', 'wc-affiliate' ), 400 );
		}

		// Check if referral already exists for this order.
		$referral_model     = new ReferralModel();
		$existing_referrals = $referral_model->get_referrals(
			array(
				'order_id' => $order_id,
				'limit'    => 1,
			)
		);

		if ( ! empty( $existing_referrals ) ) {
			return $this->response_error( __( 'Referral already exists for this order', 'wc-affiliate' ), 409 );
		}

		$referral_data = array(
			'affiliate'   => $affiliate_id,
			'order_id'    => $order_id,
			'commission'  => $commission,
			'order_total' => $order_total,
			'type'        => $type ?? ReferralModel::TYPE_SALE,
		);

		if ( $visit_id ) {
			$referral_data['visit'] = $visit_id;
		}

		if ( count( $products ) > 0 ) {
			$referral_data['products'] = $products;
		}

		$result = $referral_model->create( $referral_data );

		if ( is_wp_error( $result ) ) {
			return $this->response_error( $result->get_error_message(), 400 );
		}

		// Fire action after referral creation.
		do_action( 'wc_affiliate_referral_created', $result->get_id(), $referral_data, $request );

		return $this->response_success(
			$result->to_array(),
			__( 'Referral created successfully', 'wc-affiliate' ),
			201
		);
	}

	/**
	 * Get single referral
	 *
	 * @param WP_REST_Request $request REST request object containing the referral ID.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function get( WP_REST_Request $request ) {
		$id       = (int) $request->get_param( 'referral_id' );
		$referral = new ReferralModel( $id );

		if ( ! $referral->exists() ) {
			return $this->response_error( __( 'Referral not found', 'wc-affiliate' ), 404 );
		}

		// Check permissions.
		if ( ! current_user_can( 'manage_options' ) ) {
			$affiliate = $referral->get_affiliate();
			if ( ! $affiliate->exists() || get_current_user_id() !== $affiliate->user_id ) {
				return $this->response_error( __( 'Permission denied', 'wc-affiliate' ), 403 );
			}
		}

		$data = $referral->to_array();
		$data = apply_filters( 'wc_affiliate_rest_api_referral_get_data', $data, $id, $request );

		return $this->response_success( $data );
	}

	/**
	 * Update referral status
	 *
	 * @param WP_REST_Request $request REST request object containing the referral ID and new status.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function update_status( WP_REST_Request $request ) {
		$id     = (int) $request->get_param( 'referral_id' );
		$status = sanitize_text_field( $request->get_param( 'status' ) );

		$referral = new ReferralModel( $id );

		if ( ! $referral->exists() ) {
			return $this->response_error( __( 'Referral not found', 'wc-affiliate' ), 404 );
		}

		$result = $referral->update_status( $status );

		if ( is_wp_error( $result ) ) {
			return $this->response_error( $result->get_error_message(), 400 );
		}

		return $this->response_success(
			$referral->to_array(),
			__( 'Referral status updated successfully', 'wc-affiliate' )
		);
	}

	/**
	 * Bulk update referral statuses
	 *
	 * @param WP_REST_Request $request REST request object containing referral IDs and new status.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function bulk_update_status( WP_REST_Request $request ) {
		$ids    = array_map( 'intval', $request->get_param( 'ids' ) );
		$status = sanitize_text_field( $request->get_param( 'status' ) );

		if ( empty( $ids ) ) {
			return $this->response_error( __( 'No referral IDs provided', 'wc-affiliate' ), 400 );
		}

		$referral_model = new ReferralModel();
		$updated_count  = $referral_model->bulk_update_status( $ids, $status );

		if ( is_wp_error( $updated_count ) ) {
			return $this->response_error( $updated_count->get_error_message(), 400 );
		}

		return $this->response_success(
			array(
				'updated_count' => $updated_count,
				'total_count'   => count( $ids ),
			),
			sprintf(
			/* translators: %d is the number of referrals */
				__( '%d referrals updated successfully', 'wc-affiliate' ),
				$updated_count
			)
		);
	}

	/**
	 * Delete referral
	 *
	 * @param WP_REST_Request $request REST request object containing the referral ID.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function delete( WP_REST_Request $request ) {
		$id       = (int) $request->get_param( 'referral_id' );
		$referral = new ReferralModel( $id );

		if ( ! $referral->exists() ) {
			return $this->response_error( __( 'Referral not found', 'wc-affiliate' ), 404 );
		}

		$result = $referral->delete();

		if ( is_wp_error( $result ) ) {
			return $this->response_error( $result->get_error_message(), 400 );
		}

		return $this->response_success(
			null,
			__( 'Referral deleted successfully', 'wc-affiliate' )
		);
	}

	/**
	 * Get referral statistics
	 *
	 * @param WP_REST_Request $request REST request object containing parameters for filtering.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_stats( WP_REST_Request $request ) {
		$affiliate_id = (int) $request->get_param( 'affiliate_id' );
		$start_date   = sanitize_text_field( $request->get_param( 'start_date' ) );
		$end_date     = sanitize_text_field( $request->get_param( 'end_date' ) );
		$period       = sanitize_text_field( $request->get_param( 'period' ) );

		// Check permissions.
		if ( ! current_user_can( 'manage_options' ) && $affiliate_id ) {
			$affiliate = new Affiliate( $affiliate_id );
			if ( ! $affiliate->exists() || get_current_user_id() !== (int) $affiliate->user_id ) {
				return $this->response_error( __( 'Permission denied', 'wc-affiliate' ), 403 );
			}
		}

		$args = array();

		if ( $affiliate_id ) {
			$args['affiliate_id'] = $affiliate_id;
		}

		// Handle period-based date ranges.
		if ( $period ) {
			$date_range         = $this->get_date_range( $period, $start_date, $end_date );
			$args['start_date'] = $date_range['start_date'];
			$args['end_date']   = $date_range['end_date'];
		} else {
			if ( $start_date ) {
				$args['start_date'] = $start_date;
			}
			if ( $end_date ) {
				$args['end_date'] = $end_date;
			}
		}

		$referral_model = new ReferralModel();
		$stats          = $referral_model->get_statistics( $args );

		return $this->response_success( $stats );
	}

	/**
	 * Get top products by referrals
	 *
	 * @param WP_REST_Request $request REST request object containing parameters for filtering.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_top_products( WP_REST_Request $request ) {
		$affiliate_id = (int) $request->get_param( 'affiliate_id' );
		$limit        = min( 50, max( 5, (int) $request->get_param( 'limit' ) ) );
		$days         = max( 1, (int) $request->get_param( 'days' ) );

		// Check permissions.
		if ( ! current_user_can( 'manage_options' ) && $affiliate_id ) {
			$affiliate = new Affiliate( $affiliate_id );
			if ( ! $affiliate->exists() || get_current_user_id() !== $affiliate->user_id ) {
				return $this->response_error( __( 'Permission denied', 'wc-affiliate' ), 403 );
			}
		}

		$referral_model = new ReferralModel();
		$top_products   = $referral_model->get_top_products( $limit, $affiliate_id, $days );

		return $this->response_success(
			array(
				'affiliate_id' => $affiliate_id,
				'period_days'  => $days,
				'products'     => $top_products,
			)
		);
	}

	/**
	 * Get commission breakdown for referral
	 *
	 * @param WP_REST_Request $request REST request object containing the referral ID.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_commission_breakdown( WP_REST_Request $request ) {
		$id       = (int) $request->get_param( 'referral_id' );
		$referral = new ReferralModel( $id );

		if ( ! $referral->exists() ) {
			return $this->response_error( __( 'Referral not found', 'wc-affiliate' ), 404 );
		}

		// Check permissions.
		if ( ! current_user_can( 'manage_options' ) ) {
			$affiliate = $referral->get_affiliate();
			if ( ! $affiliate->exists() || get_current_user_id() !== $affiliate->user_id ) {
				return $this->response_error( __( 'Permission denied', 'wc-affiliate' ), 403 );
			}
		}

		// Get order and calculate a detailed commission breakdown.
		$order = $referral->get_order();
		if ( ! $order ) {
			return $this->response_error( __( 'Order not found for this referral', 'wc-affiliate' ), 404 );
		}

		$affiliate            = $referral->get_affiliate();
		$commission_breakdown = Commission::calculate_for_wc_order( $order, $affiliate->user_id );

		$response_data = array(
			'referral_id'          => $id,
			'order_id'             => $referral->get_order_id(),
			'affiliate_id'         => $affiliate->user_id,
			'commission_breakdown' => $commission_breakdown,
			'current_commission'   => $referral->get_commission(),
			'order_total'          => $referral->get_order_total(),
		);

		return $this->response_success( $response_data );
	}

	/**
	 * Recalculate referral commission
	 *
	 * @param WP_REST_Request $request REST request object containing the referral ID.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function recalculate_commission( WP_REST_Request $request ) {
		$id       = (int) $request->get_param( 'referral_id' );
		$referral = new ReferralModel( $id );

		if ( ! $referral->exists() ) {
			return $this->response_error( __( 'Referral not found', 'wc-affiliate' ), 404 );
		}

		// Get order and recalculate commission.
		$order = $referral->get_order();
		if ( ! $order ) {
			return $this->response_error( __( 'Order not found for this referral', 'wc-affiliate' ), 404 );
		}

		$affiliate       = $referral->get_affiliate();
		$commission_data = Commission::calculate_for_wc_order( $order, $affiliate->user_id );

		// Update referral with a new commission.
		$result = $referral->update(
			array(
				'commission' => $commission_data['total_commission'],
			)
		);

		if ( is_wp_error( $result ) ) {
			return $this->response_error( $result->get_error_message(), 400 );
		}

		return $this->response_success(
			array(
				'old_commission' => $referral->get_commission(),
				'new_commission' => $commission_data['total_commission'],
				'breakdown'      => $commission_data['breakdown'],
			),
			__( 'Commission recalculated successfully', 'wc-affiliate' )
		);
	}

	/**
	 * Get pending payouts (referrals ready for payout)
	 *
	 * @param WP_REST_Request $request REST request object containing optional filter parameters.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_pending_payouts( WP_REST_Request $request ) {
		if ( ! current_user_can( 'manage_options' ) ) {
			return $this->response_error( __( 'Permission denied', 'wc-affiliate' ), 403 );
		}

		$pagination   = $this->get_pagination_params( $request );
		$affiliate_id = (int) $request->get_param( 'affiliate_id' );
		$min_amount   = (float) $request->get_param( 'min_amount' );

		$args = array(
			'status'  => ReferralModel::STATUS_PAID,
			'limit'   => $pagination['limit'],
			'offset'  => $pagination['offset'],
			'orderby' => 'time',
			'order'   => 'ASC',
		);

		if ( $affiliate_id ) {
			$args['affiliate_id'] = $affiliate_id;
		}

		// Get payable referrals using Referral model.
		$referral_model = new \WC_Affiliate\Models\Referral();
		$results        = $referral_model->get_payable_referrals(
			$args['affiliate_id'],
			$min_amount,
			$args['limit'],
			$args['offset']
		);

		// Group by affiliate for easier payout processing.
		$payouts_by_affiliate = array();
		$total_commission     = 0;

		foreach ( $results as $result ) {
			$affiliate_id = (int) $result->affiliate;

			if ( ! isset( $payouts_by_affiliate[ $affiliate_id ] ) ) {
				$payouts_by_affiliate[ $affiliate_id ] = array(
					'affiliate_id'     => $affiliate_id,
					'affiliate_name'   => $result->affiliate_name,
					'affiliate_email'  => $result->affiliate_email,
					'total_commission' => 0,
					'referral_count'   => 0,
					'referrals'        => array(),
				);
			}

			$referral_data             = (array) $result;
			$referral_data['products'] = maybe_unserialize( $result->products );

			$referral = new ReferralModel();
			$referral->set_id( (int) $result->id );

			// Sanitize referral data before setting.
			$sanitized_data = $this->sanitize_referral_data( $referral_data );
			$referral->set( 'data', $sanitized_data );

			$payouts_by_affiliate[ $affiliate_id ]['referrals'][]       = $referral->to_array();
			$payouts_by_affiliate[ $affiliate_id ]['total_commission'] += (float) $result->commission;
			++$payouts_by_affiliate[ $affiliate_id ]['referral_count'];
			$total_commission += (float) $result->commission;
		}

		return $this->response_success(
			array(
				'payouts_by_affiliate' => array_values( $payouts_by_affiliate ),
				'summary'              => array(
					'total_affiliates' => count( $payouts_by_affiliate ),
					'total_referrals'  => array_sum( array_column( $payouts_by_affiliate, 'referral_count' ) ),
					'total_commission' => $total_commission,
				),
			)
		);
	}

	/**
	 * Sanitize referral data to prevent XSS and other issues
	 *
	 * @param array $data Raw referral data.
	 *
	 * @return array Sanitized referral data
	 */
	protected function sanitize_referral_data( array $data ): array {
		$sanitized = array();

		$field_types = array(
			'id'              => 'int',
			'affiliate'       => 'int',
			'type'            => 'text',
			'visit'           => 'int',
			'order_id'        => 'int',
			'order_total'     => 'float',
			'commission'      => 'float',
			'payment_status'  => 'text',
			'transaction_id'  => 'int',
			'time'            => 'int',
			'affiliate_name'  => 'text',
			'affiliate_email' => 'email',
		);

		foreach ( $data as $key => $value ) {
			$safe_key = sanitize_key( $key );

			if ( isset( $field_types[ $safe_key ] ) ) {
				switch ( $field_types[ $safe_key ] ) {
					case 'int':
						$sanitized[ $safe_key ] = (int) $value;
						break;
					case 'float':
						$sanitized[ $safe_key ] = (float) $value;
						break;
					case 'email':
						$sanitized[ $safe_key ] = sanitize_email( $value );
						break;
					case 'text':
					default:
						$sanitized[ $safe_key ] = sanitize_text_field( $value );
						break;
				}
			} else {
				// For unknown fields, sanitize as text.
				$sanitized[ $safe_key ] = sanitize_text_field( $value );
			}
		}

		// Special handling for products array.
		if ( isset( $data['products'] ) && is_array( $data['products'] ) ) {
			$sanitized['products'] = $this->sanitize_products_array( $data['products'] );
		}

		return $sanitized;
	}

	/**
	 * Sanitize products array
	 *
	 * @param array $products Products array.
	 *
	 * @return array Sanitized products array
	 */
	protected function sanitize_products_array( array $products ): array {
		$sanitized_products = array();

		foreach ( $products as $product ) {
			if ( is_array( $product ) ) {
				$sanitized_product = array();
				foreach ( $product as $key => $value ) {
					$safe_key = sanitize_key( $key );
					if ( in_array( $safe_key, array( 'id', 'quantity' ), true ) ) {
						$sanitized_product[ $safe_key ] = (int) $value;
					} elseif ( in_array( $safe_key, array( 'price', 'total' ), true ) ) {
						$sanitized_product[ $safe_key ] = (float) $value;
					} else {
						$sanitized_product[ $safe_key ] = sanitize_text_field( $value );
					}
				}
				$sanitized_products[] = $sanitized_product;
			}
		}

		return $sanitized_products;
	}

	/**
	 * Get approved referrals grouped by affiliates
	 *
	 * Retrieves approved and paid referrals grouped by each affiliate, with summary statistics.
	 *
	 * @param WP_REST_Request $request REST request object containing optional filter parameters.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_approved_referrals_by_affiliate( WP_REST_Request $request ) {
		$pagination = $this->get_pagination_params( $request );

		// Use the Referral model to get the data with pagination.
		$referral_model = new ReferralModel();
		$result         = $referral_model->get_approved_referrals_by_affiliate( $pagination['page'], $pagination['per_page'] );

		$affiliates_data = $result['affiliates'];
		$pagination_info = $result['pagination'];

		// Calculate summary statistics.
		$summary = array(
			'total_affiliates' => (int) $pagination_info['total_count'],
			'total_referrals'  => array_sum( array_column( $affiliates_data, 'referral_count' ) ),
			'total_commission' => array_sum( array_column( $affiliates_data, 'total_commission' ) ),
			'total_sales'      => array_sum( array_column( $affiliates_data, 'total_sales' ) ),
			'avg_commission'   => count( $affiliates_data ) > 0 ? array_sum( array_column( $affiliates_data, 'total_commission' ) ) / count( $affiliates_data ) : 0,
		);

		$response_data = array(
			'affiliates' => $affiliates_data,
			'summary'    => $summary,
		);

		/**
		 * Filter approved referrals by affiliate data before response.
		 *
		 * Allows modification of the grouped referrals data.
		 *
		 * @since 1.0.0
		 *
		 * @param array           $response_data Response data containing affiliates and summary.
		 * @param WP_REST_Request $request       The original request object.
		 */
		$response_data = apply_filters( 'wc_affiliate_rest_api_referrals_payable_affiliates_data', $response_data, $request );

		$response = $this->response_success( $response_data );

		return $this->add_pagination_headers( $response, $pagination_info['total_count'], $pagination['page'], $pagination['per_page'] );
	}

	/**
	 * Get referral status counts
	 *
	 * @param WP_REST_Request $request REST request object containing optional filter parameters.
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function get_status_counts( WP_REST_Request $request ) {
		$affiliate_id = (int) $request->get_param( 'affiliate_id' );
		$start_date   = sanitize_text_field( $request->get_param( 'start_date' ) );
		$end_date     = sanitize_text_field( $request->get_param( 'end_date' ) );

		$args = array();

		// Check permissions and get authorized affiliate ID.
		if ( $affiliate_id ) {
			$authorized_affiliate_id = $this->check_affiliate_access( $affiliate_id, $request );
			if ( is_wp_error( $authorized_affiliate_id ) ) {
				return $this->response_error( $authorized_affiliate_id->get_error_message(), 403 );
			}

			if ( $authorized_affiliate_id ) {
				$args['affiliate_id'] = $authorized_affiliate_id;
			}
		}

		if ( $start_date ) {
			$args['start_date'] = $start_date;
		}

		if ( $end_date ) {
			$args['end_date'] = $end_date;
		}

		$referral_model = new ReferralModel();
		$status_counts  = $referral_model->get_status_counts( $args );

		return $this->response_success( $status_counts );
	}
}
