<?php
/**
 * Attachments Endpoints
 *
 * @package Nisje
 */

namespace Dekode\Nisje\Components\Rest;

defined( 'ABSPATH' ) || die( 'Shame on you' );

/**
 * Attachments Rest Class
 */
class Attachments_Controller extends \WP_REST_Controller {
	/**
	 * Constructor
	 */
	public function __construct() {
		$this->namespace = nisje_get_rest_namespace();
		$this->rest_base = 'attachments';
		$this->hook_base = strtolower( $this->rest_base );
	}

	/**
	 * Get Post Attachments
	 *
	 * @since 0.0.40
	 *
	 * @param WP_Post $post The object from the response.
	 *
	 * @return mixed
	 */
	public function get_attachments( $post ) {
		$post_id = false;

		$retval = [
			'attachments' => [],
			'total'       => 0,

		];

		if ( is_object( $post ) ) {
			$post_id = $post->ID;
		}

		if ( is_array( $post ) ) {
			$post_id = $post['id'];
		}

		if ( ! $post_id ) {
			return $retval;
		}

		$attachment_ids = maybe_unserialize( get_post_meta( $post_id, 'attachments', true ) );
		$attachments    = [];

		if ( ! $attachment_ids ) {
			return $retval;
		}

		foreach ( $attachment_ids as $id ) {
			$attachment = get_post( $id );

			$attachments[] = [
				'id'    => $id,
				'url'   => wp_get_attachment_url( $id ),
				'title' => $attachment->post_title,
				'type'  => $attachment->post_mime_type,
			];
		}

		$retval = [
			'attachments' => $attachments,
			'total'       => count( $attachment_ids ),
		];

		return $retval;
	}

	/**
	 * Update Attachments
	 *
	 * @param mixed   $field The object from the response.
	 * @param WP_Post $post  The object from the response.
	 *
	 * @return mixed
	 */
	public function update_attachments( $field, $post ) {
		$post_id = false;

		$retval = [
			'attachments' => [],
			'total'       => 0,

		];

		if ( is_object( $post ) ) {
			$post_id = $post->ID;
		}

		if ( is_array( $post ) ) {
			$post_id = $post['id'];
		}

		if ( ! $post_id ) {
			return $retval;
		}

		update_post_meta( $post_id, 'attachments', maybe_serialize( $field ) );

		$retval = [
			'attachments' => $field,
			'total'       => count( $field ),
		];

		return $retval;

	}


	/**
	 * Registers the routes for the objects of the controller.
	 */
	public function register_routes() {
		register_rest_route( $this->namespace, '/' . $this->rest_base, [
			[
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'create_item' ],
				'permission_callback' => [ $this, 'create_item_permissions_check' ],
				'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ),
			],
			'schema' => [ $this, 'get_public_item_schema' ],
		] );
	}

	/**
	 * Checks if a given request has access to create an attachment.
	 *
	 * @since 4.7.0
	 * @access public
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_Error|true Boolean true if the attachment may be created, or a WP_Error if not.
	 */
	public function create_item_permissions_check( $request ) {
		$auth = nisje_validate_rest_authentication( $this->hook_base, 'create_item' );
		if ( is_wp_error( $auth ) ) {
			return $auth;
		}

		return true;
	}

	/**
	 * Creates a single bp attachment.
	 *
	 * @since 4.7.0
	 * @access public
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_Error|WP_REST_Response Response object on success, WP_Error object on failure.
	 */
	public function create_item( $request ) {
		// Get the file via $_FILES or raw data.
		$files   = $request->get_file_params();
		$headers = $request->get_headers();

		// Verify hash, if given.
		if ( ! empty( $headers['content_md5'] ) ) {
			$content_md5 = array_shift( $headers['content_md5'] );
			$expected    = trim( $content_md5 );
			$actual      = md5_file( $files['dk_attachment_file']['tmp_name'] );

			if ( $expected !== $actual ) {
				return new \WP_Error( 'nisje_rest_upload_hash_mismatch', esc_html__( 'Content hash did not match expected.', 'nisje' ), [
					'status' => 412,
				] );
			}
		}

		if ( ! empty( $files ) ) {
			$args = [
				'action'     => 'dk_attachments_upload',
				'file_input' => 'dk_attachment_file',
			];

			$_POST['action'] = $args['action'];

			$upload_type = '';

			if ( isset( $_POST['upload_type'] ) ) { // phpcs:ignore
				$upload_type = sanitize_key( wp_unslash( $_POST['upload_type'] ) ); // phpcs:ignore
			}

			if ( 'expire' === $upload_type ) {
				$attachment = new \Dekode_Expire_BP_Attachment( $args );
			} elseif ( 'secure' === $upload_type ) {
				$attachment = new \Dekode_Secure_BP_Attachment( $args );
			} else {
				$attachment = new \Dekode_BP_Attachment( $args );
			}

			$retobj = $attachment->upload( $files );
		} else {
			return new \WP_Error( 'nisje_rest_upload_no_files', esc_html__( 'No files supplied', 'nisje' ), [
				'status' => 400,
			] );
		}

		/**
		 * Fires after a single attachment is created or updated via the REST API.
		 */
		do_action( "nisje_rest_insert_{$this->hook_base}", $retobj, $request, true );

		$fields_update = $this->update_additional_fields_for_object( $retobj, $request );

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

		$request->set_param( 'context', 'edit' );
		$response = $retobj;
		$response = rest_ensure_response( $response );
		$response->set_status( 201 );
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, 1 ) ) );

		return $response;
	}

	/**
	 * Prepares data for return as an object.
	 *
	 * @param stdClass        $item    Item data.
	 * @param WP_REST_Request $request The request.
	 * @param boolean         $is_raw  Optional, not used. Defaults to false.
	 *
	 * @return WP_REST_Response
	 */
	public function prepare_item_for_response( $item, $request, $is_raw = false ) {
		$schema = $this->get_item_schema();

		if ( ! empty( $schema['properties']['id'] ) ) {
			$data['id'] = (int) $item->id;
		}

		if ( ! empty( $schema['properties']['component'] ) ) {
			foreach ( $item->components as $component ) {
				$data['component'][] = $component->slug;
			}
		}

		if ( ! empty( $schema['properties']['item_id'] ) ) {
			$data['item_id'] = $item->item_ids;
		}

		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
		$data    = $this->add_additional_fields_to_object( $data, $request );
		$data    = $this->filter_response_by_context( $data, $context );

		$response = rest_ensure_response( $data );

		/**
		 * Filter value returned from the API.
		 *
		 * @param array $response
		 * @param WP_REST_Request $request Request used to generate the response.
		 */
		return apply_filters( "nisje_rest_prepare_{$this->hook_base}_value", $response, $item, $request );
	}

	/**
	 * Sanitize components.
	 *
	 * @param string $value Comma-separated list of components.
	 *
	 * @return array|null
	 */
	public function sanitize_components( $value ) {
		if ( ! empty( $value ) ) {
			$components            = explode( ',', $value );
			$registered_components = array_keys( bp_core_get_components() );
			$valid_components      = array_intersect( $components, $registered_components );

			if ( ! empty( $valid_components ) ) {
				return $valid_components;
			}
		}

		return '';
	}

	/**
	 * Validate components.
	 *
	 * @param mixed           $value   Value.
	 * @param WP_REST_Request $request The Reqeust.
	 * @param string          $param   Params.
	 *
	 * @return WP_Error|boolean
	 */
	public function validate_components( $value, $request, $param ) {
		if ( ! empty( $value ) ) {
			$components            = explode( ',', $value );
			$registered_components = array_keys( bp_core_get_components() );

			foreach ( $components as $component ) {
				if ( ! in_array( $component, $registered_components, true ) ) {
					// translators: %1$s: Current component. %2$s: Registered components.
					return new \WP_Error( 'nisje_rest_invalid_component', sprintf( esc_html__( 'The component(s) you provided, %1$s, is not one of %2$s.', 'nisje' ), $component, implode( ', ', $registered_components ) ) );
				}
			}
		}

		return true;
	}

	/**
	 * Retrieves the attachment's schema, conforming to JSON Schema.
	 *
	 * @return array Item schema as an array.
	 */
	public function get_item_schema() {

		$schema = [
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
			'title'      => $this->hook_base,
			'type'       => 'object',
			'properties' => [
				'id'        => [
					'context'     => [ 'view', 'edit' ],
					'description' => esc_html__( 'The ID.', 'nisje' ),
					'type'        => 'integer',
					'readonly'    => true,
				],
				'component' => [
					'description'       => esc_html__( 'Limit result set to items with a specific BuddyPress component.', 'nisje' ),
					'type'              => 'string',
					'default'           => '',
					'sanitize_callback' => [ $this, 'sanitize_components' ],
					'validate_callback' => [ $this, 'validate_components' ],
				],
				'item_id'   => [
					'context'     => [ 'view', 'edit' ],
					'default'     => 0,
					'description' => esc_html__( 'The ID of some other object primarily associated with this one.', 'nisje' ),
					'type'        => 'integer',
					'required'    => true,
				],
			],
		];

		return $this->add_additional_fields_schema( $schema );
	}
}
