<?php
/**
 * REST API setup.
 *
 * @copyright (c) 2024, Code Atlantic LLC.
 * @package PopupMaker
 */

namespace PopupMaker\Controllers;

use PopupMaker\Plugin\Controller;
use PopupMaker\Plugin\Container;
use WP_Error;
use WP_REST_Server;
use WP_REST_Request;

/**
 * REST controller.
 *
 * @since 1.21.0
 */
class RestAPI extends Controller {

	/**
	 * Init controller.
	 *
	 * @return void
	 */
	public function init() {
		// Register custom REST API fields.
		add_action( 'init', [ $this, 'register_popup_rest_fields' ] );
		add_action( 'init', [ $this, 'register_cta_rest_fields' ] );

		// Register custom REST API endpoints.
		add_action( 'rest_api_init', [ $this, 'register_routes' ] );

		// Authentication.
		add_filter( 'rest_pre_dispatch', [ $this, 'rest_pre_dispatch' ], 10, 3 );

		// Sanitize and validate filters.
		add_filter( 'popup_maker/sanitize_popup_settings', [ $this, 'sanitize_popup_settings' ], 10, 2 );
		// add_filter( 'popup_maker/validate_popup_settings', [ $this, 'validate_popup_settings' ], 10, 2 );
		add_filter( 'popup_maker/sanitize_call_to_action_settings', [ $this, 'sanitize_call_to_action_settings' ], 10, 2 );
		add_filter( 'popup_maker/validate_call_to_action_settings', [ $this, 'validate_call_to_action_settings' ], 10, 2 );
	}

	/**
	 * Register Rest API routes.
	 *
	 * @return void
	 */
	public function register_routes() {
		( new \PopupMaker\RestAPI\Addons() )->register_routes();

		if ( $this->container->should_run_legacy_license_compatibility() ) {
			( new \PopupMaker\RestAPI\License() )->register_routes();
		}

		( new \PopupMaker\RestAPI\ObjectSearch() )->register_routes();
		( new \PopupMaker\RestAPI\Notifications() )->register_routes();
	}

	protected function register_data_version_field( $post_type, $update_permission ) {
		register_rest_field( $post_type, 'data_version', [
			'get_callback'        => function ( $obj ) {
				return get_post_meta( $obj['id'], 'data_version', true );
			},
			'update_callback'     => function ( $value, $obj ) {
				// Update the field/meta value.
				update_post_meta( $obj->ID, 'data_version', $value );
			},
			'permission_callback' => function () use ( $update_permission ) {
				return current_user_can( $update_permission );
			},
		] );
	}

	/**
	 * Register common REST fields for a given post type
	 *
	 * @return void
	 */
	public function register_popup_rest_fields() {
		$post_type       = $this->container->get_controller( 'PostTypes' )->get_type_key( 'popup' );
		$edit_permission = $this->container->get_permission( 'edit_popups' );

		register_rest_field( $post_type, 'enabled', [
			'get_callback'        => function ( $obj ) {
				return get_post_meta( $obj['id'], 'enabled', true );
			},
			'update_callback'     => function ( $value, $obj ) {
				update_post_meta( $obj->ID, 'enabled', $value );
			},
			'permission_callback' => function () use ( $edit_permission ) {
				return current_user_can( $edit_permission );
			},
		] );

		// Register popup_title as REST field with explicit update_callback.
		// This ensures saves work correctly with custom REST namespace.
		register_rest_field( $post_type, 'popup_title', [
			'get_callback'        => function ( $obj ) {
				$popup = pum_get_popup( $obj['id'] );
				return $popup ? $popup->get_meta( 'popup_title' ) : '';
			},
			'update_callback'     => function ( $value, $obj ) {
				$popup = pum_get_popup( $obj->ID );
				if ( $popup ) {
					$popup->update_meta( 'popup_title', sanitize_text_field( $value ) );
				}
			},
			'schema'              => [
				'type'        => 'string',
				'description' => __( 'The popup title displayed inside the popup.', 'popup-maker' ),
			],
			'permission_callback' => function () use ( $edit_permission ) {
				return current_user_can( $edit_permission );
			},
		] );

		register_rest_field( $post_type, 'settings', [
			'get_callback'        => function ( $obj, $field, $request ) {
				$popup = pum_get_popup( $obj['id'] );

				// If edit context, return the current settings.
				if ( 'edit' === $request['context'] ) {
					$settings = $popup->get_settings();
				} else {
					// Otherwise, return the public settings.
					$settings = $popup->get_public_settings();
				}

				return $settings;
			},
			'update_callback'     => function ( $value, $obj ) {
				$popup  = pum_get_popup( $obj->ID );
				$result = $popup->update_settings( $value );

				// Surface a refused destructive write to the editor instead
				// of silently reporting success.
				if ( is_wp_error( $result ) ) {
					return $result;
				}
			},
			'schema'              => [
				'type'        => 'object',
				'arg_options' => [
					'sanitize_callback' => function ( $settings, $request ) {
						/**
						 * Sanitize the popup settings.
						 *
						 * @param array<string,mixed> $settings The settings to sanitize.
						 * @param int   $id       The popup ID.
						 * @param \WP_REST_Request $request The request object.
						 *
						 * @return array<string,mixed> The sanitized settings.
						 */
						return apply_filters( 'popup_maker/sanitize_popup_settings', $settings, $request->get_param( 'id' ), $request );
					},
					'validate_callback' => function ( $settings, $request ) {
						/**
						 * Validate the popup settings.
						 *
						 * @param array<string,mixed> $settings The settings to validate.
						 * @param int   $id       The popup ID.
						 * @param \WP_REST_Request $request The request object.
						 *
						 * @return bool|\WP_Error True if valid, WP_Error if not.
						 */
						return apply_filters( 'popup_maker/validate_popup_settings', $settings, $request->get_param( 'id' ), $request );
					},
				],
			],
			'permission_callback' => function () use ( $edit_permission ) {
				return current_user_can( $edit_permission );
			},
		] );

		register_rest_field( $post_type, 'priority', [
			'get_callback'        => function ( $obj ) {
				return (int) get_post_field( 'menu_order', $obj['id'], 'raw' );
			},
			'update_callback'     => function ( $value, $obj ) {
				wp_update_post( [
					'ID'         => $obj->ID,
					'menu_order' => $value,
				] );
			},
			'permission_callback' => function () use ( $edit_permission ) {
				return current_user_can( $edit_permission );
			},
			'schema'              => [
				'type'        => 'integer',
				'arg_options' => [
					'sanitize_callback' => function ( $priority ) {
						return absint( $priority );
					},
					'validate_callback' => function ( $priority ) {
						return is_int( $priority );
					},
				],
			],
		] );

		// Register data version field.
		$this->register_data_version_field( $post_type, $edit_permission );
	}

	/**
	 * Sanitize popup settings.
	 *
	 * @param array<string,mixed> $settings The settings to sanitize.
	 * @param int                 $id       The popup ID.
	 *
	 * @return array<string,mixed> The sanitized settings.
	 */
	public function sanitize_popup_settings( $settings, $id ) {
		return $settings;
	}

	/**
	 * Validate popup settings.
	 *
	 * @param array<string,mixed> $settings The settings to validate.
	 * @param int                 $id       The popup ID.
	 *
	 * @return bool|\WP_Error True if valid, WP_Error if not.
	 */
	public function validate_popup_settings( $settings, $id ) {
		// TODO Validate all known settings by type.
		return true;
	}

	/**
	 * Registers custom REST API fields for call to action post type.
	 *
	 * @return void
	 */
	public function register_cta_rest_fields() {
		$post_type       = $this->container->get_controller( 'PostTypes' )->get_type_key( 'pum_cta' );
		$edit_permission = $this->container->get_permission( 'edit_ctas' );

		$ctas = $this->container->get( 'ctas' );

		$valid_statuses = [ 'publish', 'future', 'draft', 'pending', 'private', 'trash' ];

		register_rest_field( $post_type, 'status', [
			'get_callback'        => function ( $obj ) {
				return get_post_status( $obj['id'] );
			},
			'update_callback'     => function ( $value, $obj ) {
				// Trashing is a delete action; the field only checks edit_ctas, so
				// require delete rights for this object before allowing it.
				if ( 'trash' === $value && ! current_user_can( 'delete_post', $obj->ID ) ) {
					return new \WP_Error(
						'rest_cannot_delete',
						__( 'You do not have permission to trash this call to action.', 'popup-maker' ),
						[ 'status' => rest_authorization_required_code() ]
					);
				}

				wp_update_post( [
					'ID'          => $obj->ID,
					'post_status' => $value,
				] );
			},
			'permission_callback' => function () use ( $edit_permission ) {
				return current_user_can( $edit_permission );
			},
			'schema'              => [
				'type'        => 'string',
				'enum'        => [ 'publish', 'trash', 'draft' ],
				'default'     => 'publish',
				'arg_options' => [
					'sanitize_callback' => function ( $status ) use ( $valid_statuses ) {
						return in_array( $status, $valid_statuses, true ) ? $status : 'publish';
					},
					'validate_callback' => function ( $status ) use ( $valid_statuses ) {
						return in_array( $status, $valid_statuses, true );
					},
				],
			],
		] );

		// Register uuid field. Should be read-only &restricted to admins similar to edit only props.
		register_rest_field( $post_type, 'uuid', [
			'get_callback'        => function ( $obj ) {
				$cta = \PopupMaker\get_cta_by_id( $obj['id'] );

				if ( ! $cta ) {
					return null;
				}

				$uuid = $cta->get_uuid();

				return $uuid;
			},
			'permission_callback' => function () use ( $edit_permission ) {
				return current_user_can( $edit_permission );
			},
		] );

		// Register conversion counts field.
		register_rest_field(
			$post_type,
			'stats',
			[
				'get_callback'    => function ( $obj ) {
					$cta = \PopupMaker\get_cta_by_id( $obj['id'] );
					return [
						'conversions' => $cta->get_event_count( 'conversion' ),
					];
				},
				'update_callback' => null,
				'schema'          => [
					'description' => __( 'Stats for this CTA.', 'popup-maker' ),
					'type'        => 'object',
					'properties'  => [
						'conversions' => [
							'type'    => 'integer',
							'minimum' => 0,
						],
					],
				],
			]
		);

		// Register settings field.
		register_rest_field( $post_type, 'settings', [
			'get_callback'        => function ( $obj, $field, $request ) use ( $ctas ) {
				$cta = \PopupMaker\get_cta_by_id( $obj['id'] );

				if ( ! $cta ) {
					return [];
				}

				$settings = [];

				// If edit context, return the current settings.
				if ( 'edit' === $request['context'] ) {
					$settings = get_post_meta( $obj['id'], 'cta_settings', true );

					if ( empty( $settings ) ) {
						$settings = \PopupMaker\get_default_call_to_action_settings();
					}
				} else {
					// Otherwise, return the public settings.
					$settings = $cta->get_public_settings();
				}

				return $settings;
			},
			'update_callback'     => function ( $value, $obj ) {
				update_post_meta( $obj->ID, 'cta_settings', $value );
			},
			'schema'              => [
				'type'        => 'object',
				'arg_options' => [
					'sanitize_callback' => function ( $settings, $request ) {
						/**
						 * Sanitize the call to action settings.
						 *
						 * @param array<string,mixed> $settings The settings to sanitize.
						 * @param int   $id       The call to action ID.
						 * @param \WP_REST_Request $request The request object.
						 *
						 * @return array<string,mixed> The sanitized settings.
						 */
						return apply_filters( 'popup_maker/sanitize_call_to_action_settings', $settings, $request->get_param( 'id' ), $request );
					},
					'validate_callback' => function ( $settings, $request ) {
						/**
						 * Validate the popup settings.
						 *
						 * @param array<string,mixed> $settings The settings to validate.
						 * @param int   $id       The popup ID.
						 * @param \WP_REST_Request $request The request object.
						 *
						 * @return bool|\WP_Error True if valid, WP_Error if not.
						 */
						return apply_filters( 'popup_maker/validate_call_to_action_settings', $settings, $request->get_param( 'id' ), $request );
					},
				],
			],
			'permission_callback' => function () use ( $edit_permission ) {
				return current_user_can( $edit_permission );
			},
		] );

		// Register data version field.
		$this->register_data_version_field( $post_type, $edit_permission );
	}

	/**
	 * Sanitize call to action settings.
	 *
	 * @param array<string,mixed> $settings The settings to sanitize.
	 * @param int                 $id       The call to action ID.
	 *
	 * @return array<string,mixed> The sanitized settings.
	 */
	public function sanitize_call_to_action_settings( $settings, $id ) {
		return $settings;
	}

	/**
	 * Validate call to action settings.
	 *
	 * @param array<string,mixed> $settings The settings to validate.
	 * @param int                 $id       The call to action ID.
	 *
	 * @return bool|\WP_Error True if valid, WP_Error if not.
	 */
	public function validate_call_to_action_settings( $settings, $id ) {
		if ( empty( $settings['type'] ) ) {
			return new \WP_Error( 'missing_type', __( 'CTA type is required', 'popup-maker' ) );
		}

		// Get the CTA type handler
		$cta_types = $this->container->get( 'cta_types' );
		$cta_type  = $cta_types->get( $settings['type'] );

		if ( ! $cta_type ) {
			return new \WP_Error( 'invalid_type', __( 'Invalid CTA type', 'popup-maker' ), [ 'status' => 400 ] );
		}

		// Validate settings using the CTA type's validation method
		$validation_result = $cta_type->validate_settings( $settings );

		if ( is_array( $validation_result ) ) {
			// Merge each field error into a single error.
			$error = new \WP_Error();
			foreach ( $validation_result as $field_error ) {
				$error->add( $field_error->get_error_code(), $field_error->get_error_message(), $field_error->get_error_data() );
			}

			return $error;
		}

		if ( is_wp_error( $validation_result ) ) {
			return $validation_result;
		}

		return true;
	}

	/**
	 * Prevent access to the popups endpoint.
	 *
	 * @param mixed                                 $result Response to replace the requested version with.
	 * @param \WP_REST_Server                       $server Server instance.
	 * @param \WP_REST_Request<array<string,mixed>> $request  Request used to generate the response.
	 * @return mixed
	 */
	public function rest_pre_dispatch( $result, $server, $request ) {
		// Get the route being requested.
		$route = $request->get_route();

		if ( false === strpos( $route, '/popup-maker/v2' ) ) {
			return $result;
		}

		$current_user_can = true;

		// Only proceed if the current user has permission.
		if ( false !== strpos( $route, '/popup-maker/v2/popups' ) ) {
			$current_user_can = current_user_can( $this->container->get_permission( 'edit_popups' ) );
		} elseif ( false !== strpos( $route, '/popup-maker/v2/ctas' ) ) {
			$current_user_can = current_user_can( $this->container->get_permission( 'edit_ctas' ) );
		}

		// Prevent discovery of the endpoints data from unauthorized users.
		if ( ! $current_user_can ) {
			return new \WP_Error(
				'rest_forbidden',
				__( 'Access to this endpoint requires authorization.', 'popup-maker' ),
				[
					'status' => rest_authorization_required_code(),
				]
			);
		}

		// Return data to the client to parse.
		return $result;
	}
}
