<?php
/**
 * REST API endpoints.
 *
 * @package TrustLens
 * @since   1.0.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * REST API class.
 *
 * Provides REST endpoints for external integrations.
 *
 * @since 1.0.0
 */
class TrustLens_REST_API {

	/**
	 * Single instance.
	 *
	 * @var TrustLens_REST_API|null
	 */
	private static ?TrustLens_REST_API $instance = null;

	/**
	 * API namespace.
	 *
	 * @var string
	 */
	private string $namespace = 'trustlens/v1';

	/**
	 * Get instance.
	 *
	 * @since 1.0.0
	 * @return TrustLens_REST_API
	 */
	public static function instance(): TrustLens_REST_API {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 */
	private function __construct() {
		$this->init_hooks();
	}

	/**
	 * Initialize hooks.
	 *
	 * @since 1.0.0
	 */
	private function init_hooks(): void {
		add_action( 'rest_api_init', array( $this, 'register_routes' ) );
	}

	/**
	 * Register REST routes.
	 *
	 * @since 1.0.0
	 */
	public function register_routes(): void {
		// Get customer by email hash.
		register_rest_route(
			$this->namespace,
			'/customers/(?P<email_hash>[a-f0-9]{32}(?:[a-f0-9]{32})?)',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_customer' ),
				'permission_callback' => array( $this, 'check_permission' ),
				'args'                => array(
					'email_hash' => array(
						'required'          => true,
						'type'              => 'string',
						'validate_callback' => 'wstl_is_email_hash',
						'sanitize_callback' => 'wstl_normalize_hash',
					),
				),
			)
		);

		// Get customer by email.
		register_rest_route(
			$this->namespace,
			'/customers/lookup',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'lookup_customer' ),
				'permission_callback' => array( $this, 'check_permission' ),
				'args'                => array(
					'email' => array(
						'required'          => true,
						'type'              => 'string',
						'format'            => 'email',
						'sanitize_callback' => 'sanitize_email',
					),
				),
			)
		);

		// List customers.
		register_rest_route(
			$this->namespace,
			'/customers',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'list_customers' ),
				'permission_callback' => array( $this, 'check_permission' ),
				'args'                => array(
					'segment'  => array(
						'type'              => 'string',
						'enum'              => array( 'vip', 'trusted', 'normal', 'caution', 'risk', 'critical' ),
						'sanitize_callback' => 'sanitize_text_field',
					),
					'blocked'  => array(
						'type' => 'boolean',
					),
					'per_page' => array(
						'type'    => 'integer',
						'default' => 20,
						'minimum' => 1,
						'maximum' => 100,
					),
					'page'     => array(
						'type'    => 'integer',
						'default' => 1,
						'minimum' => 1,
					),
					'orderby'  => array(
						'type'    => 'string',
						'default' => 'trust_score',
						'enum'    => array( 'trust_score', 'total_orders', 'return_rate', 'created_at' ),
					),
					'order'    => array(
						'type'    => 'string',
						'default' => 'ASC',
						'enum'    => array( 'ASC', 'DESC' ),
					),
				),
			)
		);

		// Update customer (block/unblock/allowlist).
		register_rest_route(
			$this->namespace,
			'/customers/(?P<email_hash>[a-f0-9]{32}(?:[a-f0-9]{32})?)',
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $this, 'update_customer' ),
				'permission_callback' => array( $this, 'check_permission' ),
				'args'                => array(
					'email_hash'     => array(
						'required'          => true,
						'type'              => 'string',
						'validate_callback' => 'wstl_is_email_hash',
						'sanitize_callback' => 'wstl_normalize_hash',
					),
					'is_blocked'     => array(
						'type' => 'boolean',
					),
					'is_allowlisted' => array(
						'type' => 'boolean',
					),
				),
			)
		);

		// Get customer events.
		register_rest_route(
			$this->namespace,
			'/customers/(?P<email_hash>[a-f0-9]{32}(?:[a-f0-9]{32})?)/events',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_customer_events' ),
				'permission_callback' => array( $this, 'check_permission' ),
				'args'                => array(
					'email_hash' => array(
						'required'          => true,
						'type'              => 'string',
						'validate_callback' => 'wstl_is_email_hash',
						'sanitize_callback' => 'wstl_normalize_hash',
					),
					'per_page'   => array(
						'type'    => 'integer',
						'default' => 50,
						'minimum' => 1,
						'maximum' => 100,
					),
					'page'       => array(
						'type'    => 'integer',
						'default' => 1,
						'minimum' => 1,
					),
				),
			)
		);

		// Recalculate customer score.
		register_rest_route(
			$this->namespace,
			'/customers/(?P<email_hash>[a-f0-9]{32}(?:[a-f0-9]{32})?)/recalculate',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'recalculate_score' ),
				'permission_callback' => array( $this, 'check_permission' ),
				'args'                => array(
					'email_hash' => array(
						'required'          => true,
						'type'              => 'string',
						'validate_callback' => 'wstl_is_email_hash',
						'sanitize_callback' => 'wstl_normalize_hash',
					),
				),
			)
		);

		// Get statistics.
		register_rest_route(
			$this->namespace,
			'/stats',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_stats' ),
				'permission_callback' => array( $this, 'check_permission' ),
			)
		);

		// Get segment distribution.
		register_rest_route(
			$this->namespace,
			'/stats/segments',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_segment_stats' ),
				'permission_callback' => array( $this, 'check_permission' ),
			)
		);
	}

	/**
	 * Check API permission.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request Request object.
	 * @return bool|WP_Error True if allowed.
	 */
	public function check_permission( WP_REST_Request $request ) {
		// Check for WooCommerce capability.
		if ( current_user_can( 'manage_woocommerce' ) ) {
			return true;
		}

		// Check for API key in header.
		$api_key = $request->get_header( 'X-TrustLens-API-Key' );
		$stored_key = get_option( 'trustlens_api_key', '' );

		if ( ! empty( $stored_key ) && ! empty( $api_key ) ) {
			$hashed_input = hash( 'sha256', $api_key );
			if ( hash_equals( $stored_key, $hashed_input ) ) {
				return true;
			}
		}

		return new WP_Error(
			'rest_forbidden',
			__( 'You do not have permission to access this resource.', 'trustlens' ),
			array( 'status' => 403 )
		);
	}

	/**
	 * Get single customer.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function get_customer( WP_REST_Request $request ) {
		$email_hash = $request->get_param( 'email_hash' );
		$customer = wstl_get_customer( $email_hash );

		if ( ! $customer ) {
			return new WP_Error(
				'customer_not_found',
				__( 'Customer not found.', 'trustlens' ),
				array( 'status' => 404 )
			);
		}

		return rest_ensure_response( $this->prepare_customer_response( $customer ) );
	}

	/**
	 * Lookup customer by email.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function lookup_customer( WP_REST_Request $request ) {
		$email = $request->get_param( 'email' );
		$email_hash = wstl_get_email_hash( $email );
		$customer = wstl_get_customer( $email_hash );

		if ( ! $customer ) {
			return new WP_Error(
				'customer_not_found',
				__( 'Customer not found.', 'trustlens' ),
				array( 'status' => 404 )
			);
		}

		return rest_ensure_response( $this->prepare_customer_response( $customer ) );
	}

	/**
	 * List customers.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function list_customers( WP_REST_Request $request ): WP_REST_Response {
		global $wpdb;

		$per_page = $request->get_param( 'per_page' );
		$page = $request->get_param( 'page' );
		$segment = $request->get_param( 'segment' );
		$blocked = $request->get_param( 'blocked' );
		$orderby = $request->get_param( 'orderby' );
		$order = $request->get_param( 'order' );

		$table = $wpdb->prefix . 'trustlens_customers';
		$where = array( '1=1' );
		$values = array();

		if ( $segment ) {
			$where[] = 'segment = %s';
			$values[] = $segment;
		}

		if ( null !== $blocked ) {
			$where[] = 'is_blocked = %d';
			$values[] = $blocked ? 1 : 0;
		}

		$where_clause = implode( ' AND ', $where );
		$offset = ( $page - 1 ) * $per_page;

		// Whitelist orderby and order.
		$allowed_orderby = array( 'trust_score', 'total_orders', 'return_rate', 'created_at' );
		$orderby = in_array( $orderby, $allowed_orderby, true ) ? $orderby : 'trust_score';
		$order = 'DESC' === strtoupper( $order ) ? 'DESC' : 'ASC';

		// Get total count.
		$count_sql = "SELECT COUNT(*) FROM {$table} WHERE {$where_clause}";
		if ( ! empty( $values ) ) {
			$count_sql = $wpdb->prepare( $count_sql, $values ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		}
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- SQL prepared above, table from $wpdb->prefix, safe.
		$total = (int) $wpdb->get_var( $count_sql );

		// Get customers.
		$sql = "SELECT * FROM {$table} WHERE {$where_clause} ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d";
		$values[] = $per_page;
		$values[] = $offset;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- SQL prepared below, table from $wpdb->prefix, orderby/order whitelisted.
		$customers = $wpdb->get_results( $wpdb->prepare( $sql, $values ) );

		$data = array();
		foreach ( $customers as $customer ) {
			$data[] = $this->prepare_customer_response( $customer );
		}

		$response = rest_ensure_response( $data );

		// Add pagination headers.
		$response->header( 'X-WP-Total', $total );
		$response->header( 'X-WP-TotalPages', ceil( $total / $per_page ) );

		return $response;
	}

	/**
	 * Update customer.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function update_customer( WP_REST_Request $request ) {
		$email_hash = $request->get_param( 'email_hash' );
		$customer = wstl_get_customer( $email_hash );

		if ( ! $customer ) {
			return new WP_Error(
				'customer_not_found',
				__( 'Customer not found.', 'trustlens' ),
				array( 'status' => 404 )
			);
		}

		$is_blocked_param = $request->get_param( 'is_blocked' );
		$is_allowlisted_param = $request->get_param( 'is_allowlisted' );

		if ( null !== $is_blocked_param ) {
			if ( $is_blocked_param ) {
				wstl_block_customer( $email_hash, __( 'Blocked via API', 'trustlens' ) );
			} else {
				wstl_unblock_customer( $email_hash );
			}
		}

		if ( null !== $is_allowlisted_param ) {
			if ( $is_allowlisted_param ) {
				wstl_allowlist_customer( $email_hash );
			} else {
				wstl_remove_from_allowlist( $email_hash );
			}
		}

		$customer = wstl_get_customer( $email_hash );

		return rest_ensure_response( $this->prepare_customer_response( $customer ) );
	}

	/**
	 * Get customer events.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function get_customer_events( WP_REST_Request $request ) {
		global $wpdb;

		$email_hash = $request->get_param( 'email_hash' );
		$customer = wstl_get_customer( $email_hash );

		if ( ! $customer ) {
			return new WP_Error(
				'customer_not_found',
				__( 'Customer not found.', 'trustlens' ),
				array( 'status' => 404 )
			);
		}

		$per_page = $request->get_param( 'per_page' );
		$page = $request->get_param( 'page' );
		$offset = ( $page - 1 ) * $per_page;

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

		// Get total count.
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name from $wpdb->prefix, safe.
		$total = (int) $wpdb->get_var( $wpdb->prepare(
			"SELECT COUNT(*) FROM {$table} WHERE email_hash = %s",
			$email_hash
		) );
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

		// Get events.
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name from $wpdb->prefix, safe.
		$events = $wpdb->get_results( $wpdb->prepare(
			"SELECT * FROM {$table} WHERE email_hash = %s ORDER BY created_at DESC LIMIT %d OFFSET %d",
			$email_hash,
			$per_page,
			$offset
		) );
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

		$data = array();
		foreach ( $events as $event ) {
			$data[] = array(
				'id'         => (int) $event->id,
				'event_type' => $event->event_type,
				'event_data' => json_decode( $event->event_data, true ),
				'order_id'   => $event->order_id ? (int) $event->order_id : null,
				'created_at' => $event->created_at,
			);
		}

		$response = rest_ensure_response( $data );
		$response->header( 'X-WP-Total', $total );
		$response->header( 'X-WP-TotalPages', ceil( $total / $per_page ) );

		return $response;
	}

	/**
	 * Recalculate customer score.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function recalculate_score( WP_REST_Request $request ) {
		$email_hash = $request->get_param( 'email_hash' );
		$customer = wstl_get_customer( $email_hash );

		if ( ! $customer ) {
			return new WP_Error(
				'customer_not_found',
				__( 'Customer not found.', 'trustlens' ),
				array( 'status' => 404 )
			);
		}

		// Use the canonical persisting path so an API-triggered recalculation
		// fires the same trustlens/score_updated (and segment_changed) actions
		// as an in-app one — keeping webhooks, automation and notifications
		// consistent across both entry points.
		$result = TrustLens_Score_Calculator::instance()->recalculate_score( $email_hash );

		if ( $result ) {
			$customer = wstl_get_customer( $email_hash );
		}

		return rest_ensure_response( $this->prepare_customer_response( $customer ) );
	}

	/**
	 * Get overall statistics.
	 *
	 * @since 1.0.0
	 * @return WP_REST_Response Response object.
	 */
	public function get_stats(): WP_REST_Response {
		global $wpdb;

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

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- REST API stats query, table name from $wpdb->prefix is safe.
		$stats = $wpdb->get_row(
			"SELECT
				COUNT(*) as total_customers,
				AVG(trust_score) as avg_trust_score,
				SUM(is_blocked) as blocked_count,
				SUM(is_allowlisted) as allowlisted_count,
				AVG(return_rate) as avg_return_rate,
				SUM(total_orders) as total_orders,
				SUM(total_refunds) as total_refunds
			FROM {$table}"
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

		return rest_ensure_response( self::shape_stats( $stats ) );
	}

	/**
	 * Map the aggregate stats row into the response payload.
	 *
	 * Tolerates a null row: get_row() returns null when the customers table is
	 * absent (e.g. mid data-reset or an install race), which would otherwise
	 * fatal on PHP 8 with "read property on null". A null row — and the SQL-NULL
	 * AVG/SUM columns of an empty table — both collapse to zeros.
	 *
	 * @since 1.3.0
	 * @param object|null $row Aggregate row from get_stats(), or null.
	 * @return array Stats payload.
	 */
	private static function shape_stats( ?object $row ): array {
		return array(
			'total_customers'   => (int) ( $row->total_customers ?? 0 ),
			'avg_trust_score'   => round( (float) ( $row->avg_trust_score ?? 0 ), 1 ),
			'blocked_count'     => (int) ( $row->blocked_count ?? 0 ),
			'allowlisted_count' => (int) ( $row->allowlisted_count ?? 0 ),
			'avg_return_rate'   => round( (float) ( $row->avg_return_rate ?? 0 ), 1 ),
			'total_orders'      => (int) ( $row->total_orders ?? 0 ),
			'total_refunds'     => (int) ( $row->total_refunds ?? 0 ),
		);
	}

	/**
	 * Get segment distribution.
	 *
	 * @since 1.0.0
	 * @return WP_REST_Response Response object.
	 */
	public function get_segment_stats(): WP_REST_Response {
		global $wpdb;

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

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- REST API segment stats, table name from $wpdb->prefix is safe.
		$segments = $wpdb->get_results(
			"SELECT segment, COUNT(*) as count FROM {$table} GROUP BY segment"
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

		$data = array(
			'vip'      => 0,
			'trusted'  => 0,
			'normal'   => 0,
			'caution'  => 0,
			'risk'     => 0,
			'critical' => 0,
		);

		foreach ( $segments as $segment ) {
			if ( isset( $data[ $segment->segment ] ) ) {
				$data[ $segment->segment ] = (int) $segment->count;
			}
		}

		return rest_ensure_response( $data );
	}

	/**
	 * Prepare customer for API response.
	 *
	 * @since 1.0.0
	 * @param object $customer Customer object.
	 * @return array Prepared customer data.
	 */
	private function prepare_customer_response( object $customer ): array {
		return array(
			'id'                 => (int) $customer->id,
			'email_hash'         => $customer->email_hash,
			'trust_score'        => (int) $customer->trust_score,
			'segment'            => $customer->segment,
			'segment_label'      => wstl_get_segment_label( $customer->segment ),
			'total_orders'       => (int) $customer->total_orders,
			'total_refunds'      => (int) $customer->total_refunds,
			'return_rate'        => round( (float) $customer->return_rate, 1 ),
			'total_order_value'  => (float) $customer->total_order_value,
			'total_refund_value' => (float) $customer->total_refund_value,
			'is_blocked'         => (bool) $customer->is_blocked,
			'is_allowlisted'     => (bool) $customer->is_allowlisted,
			'is_flagged'         => (bool) ( $customer->is_flagged ?? 0 ),
			'first_order_date'   => $customer->first_order_date,
			'last_order_date'    => $customer->last_order_date,
			'created_at'         => $customer->created_at,
			'updated_at'         => $customer->updated_at,
		);
	}
}
