<?php
/**
 * REST API controller for Cart Recovery Heartbeat.
 *
 * Handles periodic heartbeat requests to update cart timestamps,
 * preventing active users from being marked as abandoned.
 *
 * @package EDD\Pro\CartRecovery\REST\Controllers
 * @copyright Copyright (c) 2026, Sandhills Development, LLC
 * @license https://opensource.org/licenses/GPL-3.0 GNU General Public License v3.0
 * @since 3.6.5
 */

namespace EDD\Pro\CartRecovery\REST\Controllers;

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

use EDD\REST\Security;
use EDD\Pro\CartRecovery\Carts\Repository;
use EDD\Pro\CartRecovery\Database\Queries\RecoveryCart as Query;

/**
 * Heartbeat controller class.
 *
 * @since 3.6.5
 */
final class Heartbeat {

	/**
	 * Security instance.
	 *
	 * @since 3.6.5
	 * @var Security
	 */
	private Security $security;

	/**
	 * Constructor.
	 *
	 * @since 3.6.5
	 * @param Security $security Security instance.
	 */
	public function __construct( Security $security ) {
		$this->security = $security;
	}

	/**
	 * Handles heartbeat request to update cart timestamp.
	 *
	 * @since 3.6.5
	 * @param \WP_REST_Request $request Request object.
	 * @return \WP_REST_Response Response object.
	 */
	public function handle_heartbeat( \WP_REST_Request $request ): \WP_REST_Response {
		// Check if ACR is enabled.
		if ( ! edd_get_option( 'acr_enabled', false ) ) {
			return new \WP_REST_Response(
				array( 'error' => 'Cart recovery is not enabled' ),
				403
			);
		}

		// Extract cart identifiers from session.
		$identifiers = $this->get_cart_identifiers();

		if ( empty( $identifiers['cart_id'] ) && empty( $identifiers['session_id'] ) && empty( $identifiers['customer_id'] ) && empty( $identifiers['email'] ) ) {
			return new \WP_REST_Response(
				array(
					'success' => false,
					'message' => 'No cart identifiers found',
				),
				200
			);
		}

		// Locate the cart.
		$cart = $this->find_cart( $identifiers );

		if ( ! $cart ) {
			return new \WP_REST_Response(
				array( 'error' => 'Cart not found' ),
				404
			);
		}

		// Validate cart status - only update carts in valid tracking states.
		// Allow: tracking (active), abandoned (user returned), restored (via recovery link).
		// Disallow: recovered (already completed), expired (too old), converted (purchased).
		$trackable_statuses = array( 'tracking', 'abandoned' );
		if ( ! in_array( $cart->status, $trackable_statuses, true ) ) {
			return new \WP_REST_Response(
				array( 'error' => 'Cart is not in a trackable state' ),
				400
			);
		}

		// If the date_modified is within the last 90 seconds, don't update the cart.
		if ( time() - strtotime( $cart->date_modified ) < 90 ) {
			return new \WP_REST_Response(
				array( 'success' => true ),
				200
			);
		}

		// Update only the date_modified timestamp using Query class.
		$query   = new Query();
		$updated = $query->update_item(
			$cart->id,
			array(
				'date_modified' => edd_get_utc_date_string(),
			)
		);

		if ( false === $updated ) {
			return new \WP_REST_Response(
				array( 'error' => 'Failed to update cart timestamp' ),
				500
			);
		}

		$timestamp = Security::get_current_timestamp();

		return new \WP_REST_Response(
			array(
				'success'   => true,
				'token'     => $this->security->generate_token( $timestamp ),
				'timestamp' => $timestamp,
			),
			200
		);
	}

	/**
	 * Extracts cart identifiers from EDD session.
	 *
	 * @since 3.6.5
	 * @return array Cart identifiers.
	 */
	private function get_cart_identifiers(): array {
		$identifiers = array();

		// Try to get cart ID from session (most reliable).
		$cart_id = EDD()->session->get( 'acr_cart' );
		if ( ! empty( $cart_id ) ) {
			$identifiers['cart_id'] = (int) $cart_id;
		}

		// Get session ID.
		$cookie = EDD()->session->get_session_cookie();
		if ( ! empty( $cookie['session_id'] ) ) {
			$identifiers['session_id'] = sanitize_text_field( $cookie['session_id'] );
		}

		// Get customer ID if logged in.
		if ( is_user_logged_in() ) {
			$customer = edd_get_customer_by( 'user_id', get_current_user_id() );
			if ( $customer && ! empty( $customer->id ) ) {
				$identifiers['customer_id'] = (int) $customer->id;
				$identifiers['email']       = sanitize_email( $customer->email );
			}
		}

		if ( empty( $identifiers['email'] ) ) {
			// Get email from session if available.
			$email = EDD()->session->get( 'email_validated' );
			if ( ! empty( $email ) && is_email( $email ) ) {
				$identifiers['email'] = sanitize_email( $email );
			}
		}

		return $identifiers;
	}

	/**
	 * Finds a cart based on available identifiers.
	 *
	 * @since 3.6.5
	 * @param array $identifiers Cart identifiers.
	 * @return object|null Cart object or null if not found.
	 */
	private function find_cart( array $identifiers ) {
		// If we have cart ID, use it directly (most efficient).
		if ( ! empty( $identifiers['cart_id'] ) ) {
			$query = new Query();
			$cart  = $query->get_item( (int) $identifiers['cart_id'] );

			if ( $cart && ! empty( $cart->id ) ) {
				return $cart;
			}
		}

		// Fall back to finding by identifiers.
		$repository = new Repository();
		return $repository->find_primary_cart(
			array(
				'session_id'  => $identifiers['session_id'] ?? null,
				'customer_id' => $identifiers['customer_id'] ?? null,
				'email'       => $identifiers['email'] ?? null,
			)
		);
	}
}
