<?php
/**
 * Analytics REST API Controller.
 *
 * Handles:
 *  - Public event-tracking endpoint (views / clicks from frontend widgets)
 *  - Admin dashboard overview endpoint (aggregated metrics + chart data)
 *
 * @package Ugcify\Admin\API
 */

namespace Ugcify\Admin\API;

use WP_REST_Controller;
use WP_REST_Server;
use WP_REST_Request;
use WP_Error;
use wpdb;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class AnalyticsAPI

 * phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value
 */
class AnalyticsAPI extends WP_REST_Controller {

	/**
	 * Namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'ugcify/v1';

	/**
	 * Rest base.
	 *
	 * @var string
	 */
	protected $rest_base = 'analytics';

	/**
	 * Allowed event types.
	 *
	 * @var string[]
	 */
	private const ALLOWED_EVENTS = array( 'view', 'click' );

	/**
	 * Register routes.
	 *
	 * @return void
	 */
	public function register_routes() {
		// Public: record a view or click event from the frontend widget.
		// No authentication required — this is a lightweight analytics ping from
		// any site visitor. Input is strictly validated via the args schema above.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/event',
			array(
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'record_event' ),
					'permission_callback' => '__return_true', // Public: anonymous frontend analytics event.
					'args'                => array(
						'event_type' => array(
							'type'              => 'string',
							'required'          => true,
							'sanitize_callback' => 'sanitize_key',
							'validate_callback' => function ( $value ) {
								return in_array( $value, self::ALLOWED_EVENTS, true );
							},
						),
						'product_id' => array(
							'type'              => 'integer',
							'required'          => false,
							'default'           => 0,
							'sanitize_callback' => 'absint',
						),
					),
				),
			)
		);

		// Admin: aggregated dashboard overview (new metrics + chart data).
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/overview',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'get_overview' ),
					'permission_callback' => array( $this, 'check_admin_permission' ),
					'args'                => array(
						'start_date' => array(
							'type'              => 'string',
							'required'          => false,
							'sanitize_callback' => 'sanitize_text_field',
							'validate_callback' => array( $this, 'validate_date_param' ),
						),
						'end_date'   => array(
							'type'              => 'string',
							'required'          => false,
							'sanitize_callback' => 'sanitize_text_field',
							'validate_callback' => array( $this, 'validate_date_param' ),
						),
					),
				),
			)
		);
	}

	// ─────────────────────────────────────────────────────────────────
	// Permissions
	// ─────────────────────────────────────────────────────────────────

	/**
	 * Require manage_options for admin endpoints.
	 *
	 * @return bool|WP_Error
	 */
	public function check_admin_permission() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return new WP_Error(
				'rest_forbidden',
				__( 'Sorry, you are not allowed to access this resource.', 'ugcify' ),
				array( 'status' => 403 )
			);
		}

		return true;
	}

	// ─────────────────────────────────────────────────────────────────
	// Validation helpers
	// ─────────────────────────────────────────────────────────────────

	/**
	 * Validate YYYY-MM-DD date string.
	 *
	 * @param mixed           $value   Raw param value.
	 * @param WP_REST_Request $request Request object.
	 * @param string          $param   Param name.
	 * @return bool
	 */
	public function validate_date_param( $value, $request, $param ) {
		unset( $request, $param );

		if ( null === $value || '' === $value ) {
			return true;
		}

		$value = (string) $value;

		if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $value ) ) {
			return false;
		}

		$date = \DateTime::createFromFormat( 'Y-m-d', $value );

		return $date instanceof \DateTime && $date->format( 'Y-m-d' ) === $value;
	}

	// ─────────────────────────────────────────────────────────────────
	// Endpoint: record event (public)
	// ─────────────────────────────────────────────────────────────────

	/**
	 * Record a single view or click event.
	 *
	 * Deduplication: one (event_type, product_id, session_hash) combination
	 * per calendar day is stored. Subsequent identical hits are silently ignored.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function record_event( WP_REST_Request $request ) {
		global $wpdb;

		if ( ! ( $wpdb instanceof wpdb ) ) {
			return rest_ensure_response( array( 'success' => false ) );
		}

		$event_type = sanitize_key( (string) $request->get_param( 'event_type' ) );
		$product_id = absint( $request->get_param( 'product_id' ) );

		if ( ! in_array( $event_type, self::ALLOWED_EVENTS, true ) ) {
			return new WP_Error(
				'invalid_event_type',
				__( 'Invalid event type.', 'ugcify' ),
				array( 'status' => 400 )
			);
		}

		$today = gmdate( 'Y-m-d' );

		// Build a session hash: hashed (IP + User-Agent + date) so one view per
		// browser per day is counted, without storing PII.
		$raw_ip  = isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) : '';
		if ( ! $raw_ip ) {
			$raw_ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
		}
		// Use only the first IP if forwarded list.
		$ip          = trim( explode( ',', $raw_ip )[0] );
		$ua          = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
		$session_hash = hash( 'sha256', $ip . '|' . $ua . '|' . $today );

		// Deduplicate: only insert if this exact combination hasn't been recorded today.
		$table_name = $wpdb->prefix . 'ugcify_analytics';
		$existing   = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT id FROM {$table_name} WHERE event_type = %s AND product_id = %d AND session_hash = %s AND event_date = %s LIMIT 1",
				$event_type,
				$product_id,
				$session_hash,
				$today
			)
		);

		if ( $existing ) {
			return rest_ensure_response( array( 'success' => true, 'recorded' => false ) );
		}

		$inserted = $wpdb->insert(
			$table_name,
			array(
				'event_type'   => $event_type,
				'product_id'   => $product_id,
				'session_hash' => $session_hash,
				'event_date'   => $today,
				'created_at'   => current_time( 'mysql', true ),
			),
			array( '%s', '%d', '%s', '%s', '%s' )
		);

		return rest_ensure_response( array( 'success' => (bool) $inserted, 'recorded' => (bool) $inserted ) );
	}

	// ─────────────────────────────────────────────────────────────────
	// Endpoint: overview (admin)
	// ─────────────────────────────────────────────────────────────────

	/**
	 * Get dashboard overview metrics.
	 *
	 * Returns current-period metrics plus previous-period values for trend
	 * calculation, the daily chart series, the top-performing UGC items, and
	 * the current pending-review count for the Quick Actions badge.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return \WP_REST_Response
	 */
	public function get_overview( WP_REST_Request $request ) {
		global $wpdb;

		// ─── Date range ──────────────────────────────────────────────
		$start_date = sanitize_text_field( (string) $request->get_param( 'start_date' ) );
		$end_date   = sanitize_text_field( (string) $request->get_param( 'end_date' ) );

		// Default: last 30 days.
		if ( ! $start_date || ! $end_date ) {
			$end_date   = gmdate( 'Y-m-d' );
			$start_date = gmdate( 'Y-m-d', strtotime( '-29 days' ) );
		}

		$start_datetime = $start_date . ' 00:00:00';
		$end_datetime   = $end_date . ' 23:59:59';

		// Previous period of the same length.
		$period_days       = (int) round( ( strtotime( $end_date ) - strtotime( $start_date ) ) / DAY_IN_SECONDS ) + 1;
		$prev_end_date     = gmdate( 'Y-m-d', strtotime( $start_date ) - DAY_IN_SECONDS );
		$prev_start_date   = gmdate( 'Y-m-d', strtotime( $prev_end_date ) - ( $period_days - 1 ) * DAY_IN_SECONDS );
		$prev_start_dt     = $prev_start_date . ' 00:00:00';
		$prev_end_dt       = $prev_end_date . ' 23:59:59';

		// ─── Core metrics ─────────────────────────────────────────────
		$review_metrics      = $this->get_review_submission_metrics( $start_datetime, $end_datetime );
		$prev_review_metrics = $this->get_review_submission_metrics( $prev_start_dt, $prev_end_dt );

		$view_click_metrics      = $this->get_view_click_totals( $start_date, $end_date );
		$prev_view_click_metrics = $this->get_view_click_totals( $prev_start_date, $prev_end_date );

		$conversion_lift      = $this->get_conversion_lift( $start_datetime, $end_datetime );
		$prev_conversion_lift = $this->get_conversion_lift( $prev_start_dt, $prev_end_dt );

		$pending_count = $this->get_pending_count();

		// ─── Chart data ───────────────────────────────────────────────
		$chart_data = $this->get_chart_data( $start_date, $end_date );

		// ─── Top performing UGC ───────────────────────────────────────
		$top_ugc = $this->get_top_ugc( $start_date, $end_date );

		return rest_ensure_response(
			array(
				'success' => true,
				'data'    => array(
					// Current period.
					'new_submissions'      => $review_metrics['total'],
					'approval_rate'        => $review_metrics['approval_rate'],
					'total_views'          => $view_click_metrics['views'],
					'total_clicks'         => $view_click_metrics['clicks'],
					'conversion_lift'      => $conversion_lift,
					'pending_count'        => $pending_count,
					// Previous period (for trend arrows).
					'prev_new_submissions' => $prev_review_metrics['total'],
					'prev_approval_rate'   => $prev_review_metrics['approval_rate'],
					'prev_total_views'     => $prev_view_click_metrics['views'],
					'prev_total_clicks'    => $prev_view_click_metrics['clicks'],
					'prev_conversion_lift' => $prev_conversion_lift,
					// Chart + table.
					'chart_data'           => $chart_data,
					'top_ugc'              => $top_ugc,
				),
			)
		);
	}

	// ─────────────────────────────────────────────────────────────────
	// Private helpers
	// ─────────────────────────────────────────────────────────────────

	/**
	 * Count all reviews submitted in the period and compute approval rate.
	 *
	 * @param string $start_dt Start datetime (Y-m-d H:i:s).
	 * @param string $end_dt   End datetime (Y-m-d H:i:s).
	 * @return array{total: int, approval_rate: float}
	 */
	private function get_review_submission_metrics( $start_dt, $end_dt ) {
		global $wpdb;

		if ( ! ( $wpdb instanceof wpdb ) ) {
			return array( 'total' => 0, 'approval_rate' => 0.0 );
		}

		$table = $wpdb->prefix . 'ugcify_reviews';

		$conditions = array( "type NOT IN ('instagram', 'youtube', 'tiktok')" );
		$values     = array();

		if ( $start_dt ) {
			$conditions[] = 'created_at >= %s';
			$values[]     = $start_dt;
		}
		if ( $end_dt ) {
			$conditions[] = 'created_at <= %s';
			$values[]     = $end_dt;
		}

		$where = 'WHERE ' . implode( ' AND ', $conditions );
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is a trusted internal constant.
		$sql   = "SELECT status, COUNT(*) AS cnt FROM {$table} {$where} GROUP BY status";

		if ( ! empty( $values ) ) {
			// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL is built from trusted table name and prepared values.
			$sql = $wpdb->prepare( $sql, $values );
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table; values are prepared above.
		$rows    = $wpdb->get_results( $sql, ARRAY_A );
		$total   = 0;
		$approved = 0;

		foreach ( (array) $rows as $row ) {
			$cnt    = (int) $row['cnt'];
			$total += $cnt;
			if ( 'approved' === $row['status'] ) {
				$approved = $cnt;
			}
		}

		$approval_rate = $total > 0 ? round( ( $approved / $total ) * 100, 1 ) : 0.0;

		return array(
			'total'         => $total,
			'approval_rate' => $approval_rate,
		);
	}

	/**
	 * Sum views and clicks for a date range from the analytics table.
	 *
	 * @param string $start_date YYYY-MM-DD.
	 * @param string $end_date   YYYY-MM-DD.
	 * @return array{views: int, clicks: int}
	 */
	private function get_view_click_totals( $start_date, $end_date ) {
		global $wpdb;

		if ( ! ( $wpdb instanceof wpdb ) ) {
			return array( 'views' => 0, 'clicks' => 0 );
		}

		$table    = $wpdb->prefix . 'ugcify_analytics';
		$rows     = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT event_type, COUNT(*) AS cnt FROM {$table}
				WHERE event_date >= %s AND event_date <= %s
				GROUP BY event_type",
				$start_date,
				$end_date
			),
			ARRAY_A
		);

		$views  = 0;
		$clicks = 0;
		foreach ( (array) $rows as $row ) {
			if ( 'view' === $row['event_type'] ) {
				$views = (int) $row['cnt'];
			} elseif ( 'click' === $row['event_type'] ) {
				$clicks = (int) $row['cnt'];
			}
		}

		return array( 'views' => $views, 'clicks' => $clicks );
	}

	/**
	 * Compute conversation lift: % of completed WooCommerce orders that were
	 * UGC-attributed (have _ugcify_has_lightbox_sale meta) in the period.
	 *
	 * Falls back to 0 if WooCommerce is not active.
	 *
	 * @param string $start_dt Start datetime.
	 * @param string $end_dt   End datetime.
	 * @return float Percentage (0–100), rounded to 1 decimal.
	 */
	private function get_conversion_lift( $start_dt, $end_dt ) {
		if ( ! function_exists( 'wc_get_orders' ) ) {
			return 0.0;
		}

		$args = array(
			'return' => 'ids',
			'limit'  => -1,
			'type'   => 'shop_order',
			'status' => array( 'wc-completed' ),
		);

		if ( $start_dt ) {
			$args['date_created'] = '>=' . strtotime( $start_dt );
		}

		$all_ids  = wc_get_orders( $args );
		$total    = count( $all_ids );

		if ( 0 === $total ) {
			return 0.0;
		}

		$ugcify_ids = wc_get_orders(
			array_merge(
				$args,
				array(
					'meta_key'   => '_ugcify_has_lightbox_sale',
					'meta_value' => 'yes',
				)
			)
		);
		$ugcify_count = count( $ugcify_ids );

		return round( ( $ugcify_count / $total ) * 100, 1 );
	}

	/**
	 * Count reviews currently in 'pending' status.
	 *
	 * @return int
	 */
	private function get_pending_count() {
		global $wpdb;

		if ( ! ( $wpdb instanceof wpdb ) ) {
			return 0;
		}

		$table = $wpdb->prefix . 'ugcify_reviews';
		$count = $wpdb->get_var(
			"SELECT COUNT(*) FROM {$table} WHERE status = 'pending'"
		);

		return (int) $count;
	}

	/**
	 * Build chart data (views + clicks) for the given date range.
	 *
	 * Short ranges are returned daily. Larger ranges are aggregated monthly so
	 * the dashboard chart remains readable and representative.
	 *
	 * @param string $start_date YYYY-MM-DD.
	 * @param string $end_date   YYYY-MM-DD.
	 * @return array[] Array of {label, views, clicks} objects.
	 */
	private function get_chart_data( $start_date, $end_date ) {
		global $wpdb;

		if ( ! ( $wpdb instanceof wpdb ) ) {
			return array();
		}

		$table = $wpdb->prefix . 'ugcify_analytics';

		$rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT event_date, event_type, COUNT(*) AS cnt
				FROM {$table}
				WHERE event_date >= %s AND event_date <= %s
				GROUP BY event_date, event_type
				ORDER BY event_date ASC",
				$start_date,
				$end_date
			),
			ARRAY_A
		);

		$start_ts    = strtotime( $start_date );
		$end_ts      = strtotime( $end_date );
		$period_days = (int) round( ( $end_ts - $start_ts ) / DAY_IN_SECONDS ) + 1;

		// For large ranges, aggregate by month instead of plotting dense daily points.
		if ( $period_days > 62 ) {
			$monthly_map = array();

			foreach ( (array) $rows as $row ) {
				$month_key = substr( (string) $row['event_date'], 0, 7 );
				if ( ! isset( $monthly_map[ $month_key ] ) ) {
					$monthly_map[ $month_key ] = array( 'views' => 0, 'clicks' => 0 );
				}

				if ( 'view' === $row['event_type'] ) {
					$monthly_map[ $month_key ]['views'] += (int) $row['cnt'];
				} elseif ( 'click' === $row['event_type'] ) {
					$monthly_map[ $month_key ]['clicks'] += (int) $row['cnt'];
				}
			}

			$result          = array();
			$cursor_month_ts = strtotime( gmdate( 'Y-m-01', $start_ts ) );
			$end_month_ts    = strtotime( gmdate( 'Y-m-01', $end_ts ) );
			$max_months      = 36;
			$month_idx       = 0;

			while ( $cursor_month_ts <= $end_month_ts && $month_idx < $max_months ) {
				$month_key = gmdate( 'Y-m', $cursor_month_ts );
				$result[]  = array(
					'date'   => $month_key . '-01',
					'label'  => gmdate( 'M Y', $cursor_month_ts ),
					'views'  => isset( $monthly_map[ $month_key ] ) ? $monthly_map[ $month_key ]['views'] : 0,
					'clicks' => isset( $monthly_map[ $month_key ] ) ? $monthly_map[ $month_key ]['clicks'] : 0,
				);

				$cursor_month_ts = strtotime( '+1 month', $cursor_month_ts );
				++$month_idx;
			}

			return $result;
		}

		// Build a map date => {views, clicks}.
		$map = array();
		foreach ( (array) $rows as $row ) {
			$d = $row['event_date'];
			if ( ! isset( $map[ $d ] ) ) {
				$map[ $d ] = array( 'views' => 0, 'clicks' => 0 );
			}
			if ( 'view' === $row['event_type'] ) {
				$map[ $d ]['views'] = (int) $row['cnt'];
			} elseif ( 'click' === $row['event_type'] ) {
				$map[ $d ]['clicks'] = (int) $row['cnt'];
			}
		}

		// Fill every day in the range so the chart has no gaps.
		$result    = array();
		$cursor    = $start_ts;
		$day_names = array( 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' );

		// Limit to 90 days to keep response size reasonable.
		$max_days = 90;
		$day_idx  = 0;

		while ( $cursor <= $end_ts && $day_idx < $max_days ) {
			$date_str    = gmdate( 'Y-m-d', $cursor );
			$day_of_week = (int) gmdate( 'w', $cursor );
			$month_day   = gmdate( 'M j', $cursor );

			// Use day-of-week labels when range ≤ 7 days, otherwise short date.
			$label       = $period_days <= 7 ? $day_names[ $day_of_week ] : $month_day;

			$result[] = array(
				'date'   => $date_str,
				'label'  => $label,
				'views'  => isset( $map[ $date_str ] ) ? $map[ $date_str ]['views']  : 0,
				'clicks' => isset( $map[ $date_str ] ) ? $map[ $date_str ]['clicks'] : 0,
			);

			$cursor += DAY_IN_SECONDS;
			++$day_idx;
		}

		return $result;
	}

	/**
	 * Get top-performing UGC items (by view count) in the period.
	 *
	 * @param string $start_date YYYY-MM-DD.
	 * @param string $end_date   YYYY-MM-DD.
	 * @return array[]
	 */
	private function get_top_ugc( $start_date, $end_date ) {
		global $wpdb;

		if ( ! ( $wpdb instanceof wpdb ) ) {
			return array();
		}

		$analytics_table = $wpdb->prefix . 'ugcify_analytics';
		$reviews_table   = $wpdb->prefix . 'ugcify_reviews';

		// Aggregate views and clicks per product, only products that have had views.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table names are trusted internal constants; values are prepared.
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT
					a.product_id,
					SUM(CASE WHEN a.event_type = 'view'  THEN 1 ELSE 0 END) AS views,
					SUM(CASE WHEN a.event_type = 'click' THEN 1 ELSE 0 END) AS clicks
				FROM {$analytics_table} a
				WHERE a.event_date >= %s
				  AND a.event_date <= %s
				  AND a.product_id > 0
				GROUP BY a.product_id
				ORDER BY views DESC
				LIMIT 10",
				$start_date,
				$end_date
			),
			ARRAY_A
		);

		if ( empty( $rows ) ) {
			return array();
		}

		$result = array();
		foreach ( $rows as $row ) {
			$product_id = absint( $row['product_id'] );
			$views      = (int) $row['views'];
			$clicks     = (int) $row['clicks'];
			$conv_rate  = $views > 0 ? round( ( $clicks / $views ) * 100, 1 ) : 0.0;

			$product_name = '';
			$product_url  = '';
			$thumbnail    = '';

			if ( function_exists( 'wc_get_product' ) ) {
				$product = wc_get_product( $product_id );
				if ( $product ) {
					$product_name = $product->get_name();
					$product_url  = $product->get_permalink();
					$img_id       = $product->get_image_id();
					if ( $img_id ) {
						$img_src   = wp_get_attachment_image_src( $img_id, 'thumbnail' );
						$thumbnail = $img_src ? esc_url( $img_src[0] ) : '';
					}
				}
			}

			if ( ! $product_name ) {
				/* translators: %d: product ID number */
				$product_name = sprintf( __( 'Product #%d', 'ugcify' ), $product_id );
			}

			// Fetch the most-recent approved review for this product (for item preview).
			$review = $wpdb->get_row(
				$wpdb->prepare(
					"SELECT id, content FROM {$reviews_table}
					WHERE product_id = %d AND status = 'approved'
					ORDER BY created_at DESC LIMIT 1",
					$product_id
				)
			);

			$result[] = array(
				'product_id'   => $product_id,
				'product_name' => $product_name,
				'product_url'  => $product_url,
				'thumbnail'    => $thumbnail,
				'review_id'    => $review ? (int) $review->id : 0,
				'review_excerpt' => $review ? wp_trim_words( $review->content, 8 ) : '',
				'views'        => $views,
				'conv_rate'    => $conv_rate,
			);
		}

		return $result;
	}
}
