<?php

namespace WC_Affiliate\API;

defined( 'ABSPATH' ) || exit;

use WC_Affiliate\Models\Affiliate;
use WC_Affiliate\Models\Referral;
use WC_Affiliate\Models\Transaction;
use WC_Affiliate\Models\Visit;
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;

/**
 * Affiliate Dashboard API.
 *
 * Handles requests related to the affiliate dashboard functionality:
 * - Provides statistics and metrics for overall affiliate program performance
 * - Generates chart data for visits, referrals, commissions and conversions
 * - Retrieves top performers across affiliates, products, pages and referrers
 * - Manages individual affiliate dashboards with personalized data
 *
 * Uses proper data validation, caching and access control for secure and optimized performance.
 * Implements modular methods for different dashboard components that can be customized via filters.
 *
 * @since   1.0.0
 * @package WC_Affiliate\API
 */
class Dashboard {

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

	/**
	 * Get dashboard statistics.
	 *
	 * Retrieves comprehensive statistics for the affiliate dashboard, including affiliate, visit, referral, and transaction data within a specified period.
	 *
	 * @param WP_REST_Request $request The REST request object containing period, start_date, and end_date parameters.
	 *
	 * @return WP_REST_Response|WP_Error|null A successful response with dashboard data or a WP_Error on failure.
	 */
	public function get_stats( WP_REST_Request $request ): WP_REST_Response {
		$period     = sanitize_text_field( $request->get_param( 'period' ) );
		$start_date = sanitize_text_field( $request->get_param( 'start_date' ) );
		$end_date   = sanitize_text_field( $request->get_param( 'end_date' ) );

		// Calculate date range based on period.
		$date_range = $this->get_date_range( $period, $start_date, $end_date );

		// Get statistics from models.
		$affiliate_model   = new Affiliate();
		$visit_model       = new Visit();
		$referral_model    = new Referral();
		$transaction_model = new Transaction();

		$affiliate_stats   = $affiliate_model->get_statistics( $date_range );
		$visit_stats       = $visit_model->get_statistics( $date_range );
		$referral_stats    = $referral_model->get_statistics( $date_range );
		$transaction_stats = $transaction_model->get_statistics( $date_range );

		// Compile dashboard data.
		$dashboard_data = array(
			'period'          => array(
				'type'       => $period,
				'start_date' => $date_range['start_date'],
				'end_date'   => $date_range['end_date'],
			),
			'overview'        => array(
				'affiliate_stats'   => $affiliate_stats,
				'visit_stats'       => $visit_stats,
				'referral_stats'    => $referral_stats,
				'transaction_stats' => $transaction_stats,
			),
			'conversion'      => array(
				'conversion_rate' => (float) $visit_stats['conversion_rate'],
				'avg_commission'  => (float) $referral_stats['avg_commission'],
				'avg_order_value' => $this->calculate_avg_order_value( $referral_stats ),
			),
			'recent_activity' => $this->get_recent_activity( $date_range ),
		);

		/**
		 * Filter dashboard statistics data before response.
		 *
		 * Allows modification of the compiled dashboard data.
		 *
		 * @since 1.0.0
		 *
		 * @param array           $dashboard_data Compiled dashboard data.
		 * @param WP_REST_Request $request        The original request object.
		 */
		$dashboard_data = apply_filters( 'wc_affiliate_dashboard_stats_data', $dashboard_data, $request );

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

	/**
	 * Get dashboard chart data.
	 *
	 * Fetches chart data for various metrics such as visits, referrals, commissions, or conversions based on the specified type and period.
	 *
	 * @param WP_REST_Request $request The REST request object containing chart_type, period, start_date, end_date, and affiliate_id parameters.
	 *
	 * @return WP_REST_Response|WP_Error|null A successful response with chart data or a WP_Error on invalid chart type.
	 */
	public function get_charts( WP_REST_Request $request ) {
		$chart_type   = sanitize_text_field( $request->get_param( 'chart_type' ) );
		$period       = sanitize_text_field( $request->get_param( 'period' ) );
		$start_date   = sanitize_text_field( $request->get_param( 'start_date' ) );
		$end_date     = sanitize_text_field( $request->get_param( 'end_date' ) );
		$affiliate_id = intval( $request->get_param( 'affiliate_id' ) );

		$date_range = $this->get_date_range( $period, $start_date, $end_date );

		switch ( $chart_type ) {
			case 'visits':
				$chart_data = $this->get_visits_chart( $date_range, $period, $affiliate_id );
				break;

			case 'referrals':
				$chart_data = $this->get_referrals_chart( $date_range, $period, $affiliate_id );
				break;

			case 'commissions':
				$chart_data = $this->get_commissions_chart( $date_range, $period, $affiliate_id );
				break;

			case 'conversions':
				$chart_data = $this->get_conversions_chart( $date_range, $period, $affiliate_id );
				break;

			default:
				return $this->response_error( __( 'Invalid chart type', 'wc-affiliate' ), 400 );
		}

		$response_data = array(
			'chart_type' => $chart_type,
			'period'     => $period,
			'chart_data' => $chart_data,
		);

		/**
		 * Filter dashboard chart data before response.
		 *
		 * Allows modification of the chart response data.
		 *
		 * @since 1.0.0
		 *
		 * @param array           $response_data Chart response data.
		 * @param string          $chart_type    The requested chart type.
		 * @param WP_REST_Request $request       The original request object.
		 */
		$response_data = apply_filters( 'wc_affiliate_dashboard_charts_data', $response_data, $chart_type, $request );

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

	/**
	 * Get top performers.
	 *
	 * Retrieves top performers data for affiliates, products, pages, or referrers within the specified period and limit.
	 *
	 * @param WP_REST_Request $request The REST request object containing type, period, and limit parameters.
	 *
	 * @return WP_REST_Response|WP_Error|null A successful response with top performers data or a WP_Error on invalid type.
	 */
	public function get_top_performers( WP_REST_Request $request ) {
		$affiliate_id = intval( $request->get_param( 'affiliate_id' ) );
		$type         = sanitize_text_field( $request->get_param( 'type' ) );
		$period       = sanitize_text_field( $request->get_param( 'period' ) );
		$limit        = min( 20, max( 5, (int) $request->get_param( 'limit' ) ) );

		switch ( $type ) {
			case 'affiliates':
				// For affiliates type, affiliate_id filtering doesn't make sense.
				$affiliate_model = new Affiliate();
				$data            = $affiliate_model->get_leaderboard( $limit, $this->get_period_days( $period ) );
				break;

			case 'products':
				$referral_model = new Referral();
				$data           = $referral_model->get_top_products( $limit, $affiliate_id, $this->get_period_days( $period ) );
				break;

			case 'pages':
				$visit_model = new Visit();
				$data        = $visit_model->get_top_pages( $limit, $affiliate_id, $this->get_period_days( $period ) );
				break;

			case 'referrers':
				$visit_model = new Visit();
				$data        = $visit_model->get_top_referrers( $limit, $affiliate_id, $this->get_period_days( $period ) );
				break;

			default:
				return $this->response_error( __( 'Invalid performance type', 'wc-affiliate' ), 400 );
		}

		$response_data = array(
			'type'   => $type,
			'period' => $period,
			'items'  => $data,
		);

		/**
		 * Filter top performers data before response.
		 *
		 * Allows modification of the top performers response data.
		 *
		 * @since 1.0.0
		 *
		 * @param array           $response_data Top performers response data.
		 * @param string          $type          The requested performer type.
		 * @param WP_REST_Request $request       The original request object.
		 */
		$response_data = apply_filters( 'wc_affiliate_dashboard_top_performers_data', $response_data, $type, $request );

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

	/**
	 * Calculate average order value.
	 *
	 * Computes the average order value from referral statistics.
	 *
	 * @param array $referral_stats Referral statistics array with 'total_sales' and 'total_referrals' keys.
	 *
	 * @return float The calculated average order value.
	 */
	public function calculate_avg_order_value( array $referral_stats ) {
		// Use Cleaner trait to sanitize inputs.
		$referral_stats = array_map( 'sanitize_text_field', $referral_stats );

		if ( empty( $referral_stats['total_referrals'] ) ) {
			return 0;
		}

		$avg_order_value = ( (float) $referral_stats['total_sales'] / (float) $referral_stats['total_referrals'] );

		/**
		 * Filter the calculated average order value.
		 *
		 * Allows modification of the average order value in dashboard stats.
		 *
		 * @since 1.0.0
		 *
		 * @param float $avg_order_value The calculated average order value.
		 * @param array $referral_stats  The referral statistics used in calculation.
		 */
		return apply_filters( 'wc_affiliate_dashboard_avg_order_value', $avg_order_value, $referral_stats );
	}

	/**
	 * Get recent activity.
	 *
	 * Retrieves a combined list of recent referrals and visits within the date range, limited to the specified number.
	 *
	 * @param array $date_range Date range array with 'start_date' and 'end_date' keys.
	 * @param int   $limit      Maximum number of activities to retrieve (default: 10).
	 *
	 * @return array Array of recent activity items, each with type, id, affiliate details, and timestamps.
	 */
	public function get_recent_activity( array $date_range, int $limit = 10 ): array {
		// Use Cleaner trait to sanitize inputs.
		$date_range = array_map( 'sanitize_text_field', $date_range );

		// Get recent referrals using Referral model.
		$referral_model = new Referral();
		$referrals      = $referral_model->get_referrals(
			array(
				'start_date' => $date_range['start_date'],
				'end_date'   => $date_range['end_date'],
				'limit'      => $limit,
				'orderby'    => 'time',
				'order'      => 'DESC',
			)
		);

		// Get recent visits using Visit model.
		$visit_model = new Visit();
		$visits      = $visit_model->get_visits(
			array(
				'start_date' => $date_range['start_date'],
				'end_date'   => $date_range['end_date'],
				'limit'      => $limit,
				'orderby'    => 'time',
				'order'      => 'DESC',
			)
		);

		// Combine and sort by time.
		$activity = array();

		// Add referrals to activity.
		foreach ( $referrals as $referral ) {
			$affiliate_data = $referral->get( 'affiliate' );
			$visit_id       = $referral->get( 'visit' );
			$visit_model    = new Visit( $visit_id );
			$visited_url    = ! empty( $visit_model->get_page_url() ) ? $visit_model->get_page_url() : '';
			$activity[]     = array(
				'type'           => 'referral',
				'id'             => $referral->get_id(),
				'affiliate_id'   => $affiliate_data['id'] ?? '',
				'affiliate_name' => $affiliate_data['display_name'] ?? $affiliate_data['first_name'] ?? __( 'No Name', 'wc-affiliate' ),
				'commission'     => (float) $referral->get( 'commission' ),
				'timestamp'      => (int) $referral->get( 'time' ),
				'visited_url'    => $visited_url,
				'time_formatted' => wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $referral->get( 'time' ) ),
			);
		}

		// Add visits to activity.
		foreach ( $visits as $visit ) {
			$affiliate_data = $visit->get( 'affiliate' );
			$activity[]     = array(
				'type'           => 'visit',
				'id'             => $visit->get_id(),
				'affiliate_id'   => $affiliate_data['id'] ?? '',
				'affiliate_name' => $affiliate_data['display_name'] ?? $affiliate_data['first_name'] ?? __( 'No Name', 'wc-affiliate' ),
				'commission'     => 0,
				'timestamp'      => (int) $visit->get( 'time' ),
				'visited_url'    => $visit->get( 'page_url' ),
				'time_formatted' => wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $visit->get( 'time' ) ),
			);
		}

		// Sort by timestamp descending and limit.
		usort(
			$activity,
			function ( $a, $b ) {
				return $b['timestamp'] <=> $a['timestamp'];
			}
		);

		$activity = array_slice( $activity, 0, $limit );

		/**
		 * Filter recent activity data.
		 *
		 * Allows modification of the recent activity list in the dashboard.
		 *
		 * @since 1.0.0
		 *
		 * @param array $activity   Array of recent activity items.
		 * @param array $date_range The date range used for querying.
		 * @param int   $limit      The limit applied to the query.
		 */
		return apply_filters( 'wc_affiliate_dashboard_recent_activity', $activity, $date_range, $limit );
	}

	/**
	 * Get visits chart data.
	 *
	 * Generates chart data for visits and conversions grouped by the appropriate time interval based on the period.
	 *
	 * @param array  $date_range   Date range array with 'start_date' and 'end_date' keys.
	 * @param string $period       The period type for determining grouping.
	 * @param int    $affiliate_id Optional affiliate ID to filter by. Default is 0 (all affiliates).
	 *
	 * @return array Array of chart data points with period, visits, conversions, and conversion rate.
	 */
	public function get_visits_chart( array $date_range, string $period, int $affiliate_id = 0 ): array {
		// Use Visit model to get chart data.
		$visit_model = new Visit();
		$chart_data  = $visit_model->get_chart_data( $date_range, $period, $affiliate_id );

		/**
		 * Filter visits chart data.
		 *
		 * Allows modification of the visits chart data points.
		 *
		 * @since 1.0.0
		 *
		 * @param array  $chart_data Array of chart data points.
		 * @param array  $date_range The date range used for querying.
		 * @param string $period     The period type.
		 */
		return apply_filters( 'wc_affiliate_dashboard_visits_chart_data', $chart_data, $date_range, $period );
	}

	/**
	 * Get referrals chart data.
	 *
	 * Generates chart data for referrals, commissions, and sales grouped by the appropriate time interval.
	 *
	 * @param array  $date_range   Date range array with 'start_date' and 'end_date' keys.
	 * @param string $period       The period type for determining grouping.
	 * @param int    $affiliate_id Optional affiliate ID to filter by. Default is 0 (all affiliates).
	 *
	 * @return array Array of chart data points with period, referrals, commissions, and sales.
	 */
	public function get_referrals_chart( array $date_range, string $period, int $affiliate_id = 0 ): array {
		// Use Referral model to get chart data.
		$referral_model = new Referral();
		$chart_data     = $referral_model->get_chart_data( $date_range, $period, $affiliate_id );

		/**
		 * Filter referrals chart data.
		 *
		 * Allows modification of the referrals chart data points.
		 *
		 * @since 1.0.0
		 *
		 * @param array  $chart_data Array of chart data points.
		 * @param array  $date_range The date range used for querying.
		 * @param string $period     The period type.
		 */
		return apply_filters( 'wc_affiliate_dashboard_referrals_chart_data', $chart_data, $date_range, $period );
	}

	/**
	 * Get commissions chart data.
	 *
	 * Generates chart data for pending, paid, and processing commissions grouped by the appropriate time interval.
	 *
	 * @param array  $date_range   Date range array with 'start_date' and 'end_date' keys.
	 * @param string $period       The period type for determining grouping.
	 * @param int    $affiliate_id Optional affiliate ID to filter by. Default is 0 (all affiliates).
	 *
	 * @return array Array of chart data points with period and commission breakdowns.
	 */
	public function get_commissions_chart( array $date_range, string $period, int $affiliate_id = 0 ): array {
		// Use Referral model to get commission status chart data.
		$referral_model = new Referral();
		$chart_data     = $referral_model->get_commission_status_chart_data( $date_range, $period, $affiliate_id );

		/**
		 * Filter commissions chart data.
		 *
		 * Allows modification of the commissions chart data points.
		 *
		 * @since 1.0.0
		 *
		 * @param array  $chart_data Array of chart data points.
		 * @param array  $date_range The date range used for querying.
		 * @param string $period     The period type.
		 */
		return apply_filters( 'wc_affiliate_dashboard_commissions_chart_data', $chart_data, $date_range, $period );
	}

	/**
	 * Get conversions chart data.
	 *
	 * Delegates to visits chart data, as it includes conversion metrics.
	 *
	 * @param array  $date_range   Date range array with 'start_date' and 'end_date' keys.
	 * @param string $period       The period type for determining grouping.
	 * @param int    $affiliate_id Optional affiliate ID to filter by. Default is 0 (all affiliates).
	 *
	 * @return array Array of chart data points from visits chart.
	 */
	public function get_conversions_chart( array $date_range, string $period, int $affiliate_id = 0 ): array {
		// Combine visits and referrals data.
		$chart_data = $this->get_visits_chart( $date_range, $period, $affiliate_id );

		/**
		 * Filter conversions chart data.
		 *
		 * Allows modification of the conversions chart data points.
		 *
		 * @since 1.0.0
		 *
		 * @param array  $chart_data Array of chart data points.
		 * @param array  $date_range The date range used for querying.
		 * @param string $period     The period type.
		 */
		return apply_filters( 'wc_affiliate_dashboard_conversions_chart_data', $chart_data, $date_range, $period );
	}

	/**
	 * Get chart group format based on period.
	 *
	 * Determines the SQL DATE_FORMAT for grouping chart data based on the period duration.
	 *
	 * @param string $period The period type (e.g., 'today', 'this_year').
	 *
	 * @return string SQL-compatible date format string for grouping.
	 */
	public function get_chart_group_format( string $period ): string {
		// Use Cleaner trait to sanitize input.
		$period = sanitize_text_field( $period );

		// Determine grouping based on period duration.
		$days = $this->get_period_days( $period );

		if ( $days <= 7 ) {
			// Group by day for periods up to 7 days.
			return "DATE_FORMAT(FROM_UNIXTIME(time), '%%Y-%%m-%%d')";
		} elseif ( $days <= 90 ) {
			// Group by day for periods up to 90 days.
			return "DATE_FORMAT(FROM_UNIXTIME(time), '%%Y-%%m-%%d')";
		} else {
			// Group by month for longer periods.
			return "DATE_FORMAT(FROM_UNIXTIME(time), '%%Y-%%m')";
		}
	}
}
