<?php
/**
 * Base Group Post Endpoints
 *
 * @package Nisje
 */

namespace Dekode\Nisje\Components\Rest;

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

/**
 * Base Group Post Rest Class
 */
class Base_Group_Post_Controller extends \WP_REST_Posts_Controller {
	/**
	 * Current request
	 *
	 * @var WP_REST_Request $request Full data about the request.
	 */
	private $request = null;

	/**
	 * Validate base permissions.
	 *
	 * @param WP_REST_Request $request  Full data about the request.
	 * @param string          $type     Create/Basic.
	 * @param int             $user_id  User ID to validate against.
	 * @param int             $group_id Group ID to validate against.
	 *
	 * @return bool|WP_Error True if user is logged in and defeats the checks else WP_Error
	 */
	private function base_permissions( $request, $type, $user_id = 0, $group_id = 0 ) {
		$auth = nisje_validate_rest_authentication();
		if ( is_wp_error( $auth ) ) {
			return $auth;
		}

		if ( ! $user_id ) {
			$user_id = bp_loggedin_user_id();
		}

		if ( ! $group_id ) {
			if ( $request instanceof \WP_REST_Request && isset( $request['group_id'] ) ) {
				$group_id = (int) $request['group_id'];
			} elseif ( 'get_items' === $type && isset( $request['user_content'] ) ) {
				$group_ids = groups_get_user_groups( $user_id );

				$groups = [];

				foreach ( $group_ids['groups'] as $group_id ) {
					$groups[] = $group_id;
				}

				if ( empty( $groups ) ) {
					return new \WP_Error( 'nisje_rest_no_groups', esc_html__( 'User not member of any groups.', 'nisje' ), [
						'status' => 401,
					] );
				}

				// ByPass if we are looking for group content based on the groups a user is member of.
				return apply_filters( "nisje_validate_group_post_permissions_{$type}_{$this->post_type}", true, $request, $user_id, $group_id );
			} else {
				return new \WP_Error( 'nisje_rest_invalid_group_id', esc_html__( 'Invalid group id.', 'nisje' ), [
					'status' => 404,
				] );
			}
		}

		if ( in_array( $type, [ 'create_item', 'update_item' ], true ) ) {
			$excluded = nisje_validate_rest_excluded( $user_id );
			if ( is_wp_error( $excluded ) ) {
				return $excluded;
			}
		}

		$group = nisje_get_group( $group_id );
		if ( is_wp_error( $group ) ) {
			return $group;
		}

		if ( in_array( $type, [ 'get_item', 'update_item', 'delete_item' ], true ) ) {
			$post = $this->get_post( $request['id'] );
			if ( is_wp_error( $post ) ) {
				return $post;
			}

			$group_id = (int) $request['group_id'];

			if ( $post->post_parent !== $group_id ) {
				return new \WP_Error( 'nisje_rest_not_connected_to_group', esc_html__( 'The post is not connected to this group.', 'nisje' ), [
					'status' => 403,
				] );
			}
		}

		if ( groups_is_user_banned( $user_id, $group->id ) ) {
			return new \WP_Error( 'nisje_rest_banned_from_group', esc_html__( 'Sorry, you are banned from this group.', 'nisje' ), [
				'status' => 403,
			] );
		}

		$admin = apply_filters( "nisje_group_post_validate_against_admin_{$this->post_type}", true, $group, $user_id, $this->post_type );
		if ( in_array( $type, [ 'create_item', 'update_item', 'delete_item' ], true ) ) {
			if ( $admin && ! groups_is_user_admin( $user_id, $group->id ) ) {
				return new \WP_Error( 'nisje_rest_not_admin', esc_html__( 'Sorry, you need to be an administrator of the group to manage posts.', 'nisje' ), [
					'status' => rest_authorization_required_code(),
				] );
			} else {
				if ( ! groups_is_user_member( $user_id, $group->id ) ) {
					return new \WP_Error( 'nisje_rest_not_member', esc_html__( 'Sorry, you do not have access to manage posts for this group. Membership is required. Please join the group and try again.', 'nisje' ), [
						'status' => rest_authorization_required_code(),
					] );
				}
			}
		}

		if ( in_array( $group->status, [ 'hidden', 'private' ], true ) && ! groups_is_user_member( $user_id, $group->id ) ) {
			return new \WP_Error( 'nisje_rest_not_member', esc_html__( 'Sorry, you need to be a member of the group to see this post.', 'nisje' ), [
				'status' => rest_authorization_required_code(),
			] );
		}

		return apply_filters( "nisje_validate_group_post_permissions_{$type}_{$this->post_type}", true, $request, $user_id, $group_id );
	}

	/**
	 * Check if a given request has access to see news items.

	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_Error|bool
	 */
	public function get_items_permissions_check( $request ) {
		$permission = nisje_override_rest_permission_check( $this->post_type, 'get_items', $request );
		if ( true === $permission ) {
			// Filter Query.
			add_filter( "rest_{$this->post_type}_query", [ $this, 'filter_rest_query_shortinit_group_args' ], 10, 2 );

			return true;
		}

		$check_base = parent::get_items_permissions_check( $request );
		if ( is_wp_error( $check_base ) ) {
			return $check_base;
		}

		$group_permission_check = $this->base_permissions( $request, 'get_items' );
		if ( is_wp_error( $group_permission_check ) ) {
			return $group_permission_check;
		}

		// Filter Query.
		add_filter( "rest_{$this->post_type}_query", [ $this, 'filter_rest_query_group_args' ], 10, 2 );

		return true;
	}

	/**
	 * Check if a given request has access to news items.
	 *
	 * @param WP_REST_Request $request Full data about the request.
	 * @return WP_Error|bool
	 */
	public function get_item_permissions_check( $request ) {
		$permission = nisje_override_rest_permission_check( $this->post_type, 'get_item', $request );
		if ( true === $permission ) {
			return true;
		}

		$this->request = $request;

		add_filter( 'map_meta_cap', [ $this, 'allow_group_admin_to_manage_posts' ], 11, 4 );

		$check_base = parent::get_item_permissions_check( $request );
		if ( is_wp_error( $check_base ) ) {
			return $check_base;
		}

		$group_permission_check = $this->base_permissions( $request, 'get_item' );
		if ( is_wp_error( $group_permission_check ) ) {
			return $group_permission_check;
		}

		return true;
	}

	/**
	 * Checks if a given request has access to create an news.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
	 */
	public function create_item_permissions_check( $request ) {
		$this->request = $request;

		// Set post<->group connections.
		add_filter( "rest_pre_insert_{$this->post_type}", [ $this, 'set_group_connection' ], 10, 2 );
		add_filter( "rest_insert_{$this->post_type}", [ $this, 'set_meta_group_connection' ], 10, 3 );

		// Map taxonomies if group type isset.
		add_filter( "rest_insert_{$this->post_type}", [ $this, 'map_group_types' ], 10, 3 );

		$permission = nisje_override_rest_permission_check( $this->post_type, 'create_item', $request );
		if ( true === $permission ) {
			return true;
		}

		add_filter( 'map_meta_cap', [ $this, 'allow_group_admin_to_manage_posts' ], 11, 4 );

		$check_base = parent::create_item_permissions_check( $request );
		if ( is_wp_error( $check_base ) ) {
			return $check_base;
		}

		$group_permission_check = $this->base_permissions( $request, 'create_item' );
		if ( is_wp_error( $group_permission_check ) ) {
			return $group_permission_check;
		}

		return true;
	}

	/**
	 * Checks if a given request has access to update an news.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
	 */
	public function update_item_permissions_check( $request ) {
		$this->request = $request;

		// Set post<->group connections.
		add_filter( "rest_pre_insert_{$this->post_type}", [ $this, 'set_group_connection' ], 10, 2 );
		add_filter( "rest_insert_{$this->post_type}", [ $this, 'set_meta_group_connection' ], 10, 3 );

		// Map taxonomies if group type isset.
		add_filter( "rest_insert_{$this->post_type}", [ $this, 'map_group_types' ], 10, 3 );

		$permission = nisje_override_rest_permission_check( $this->post_type, 'update_item', $request );
		if ( true === $permission ) {
			return true;
		}

		add_filter( 'map_meta_cap', [ $this, 'allow_group_admin_to_manage_posts' ], 11, 4 );

		$check_base = parent::update_item_permissions_check( $request );
		if ( is_wp_error( $check_base ) ) {
			return $check_base;
		}

		$group_permission_check = $this->base_permissions( $request, 'update_item' );
		if ( is_wp_error( $group_permission_check ) ) {
			return $group_permission_check;
		}

		return true;
	}

	/**
	 * Checks if a given request has access to delete a group news article.
	 *
	 * @param WP_REST_Request $request Full details about the request.
	 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
	 */
	public function delete_item_permissions_check( $request ) {
		$this->request = $request;

		$permission = nisje_override_rest_permission_check( $this->post_type, 'delete_item', $request );
		if ( true === $permission ) {
			return true;
		}

		add_filter( 'map_meta_cap', [ $this, 'allow_group_admin_to_manage_posts' ], 11, 4 );

		$check_base = parent::delete_item_permissions_check( $request );
		if ( is_wp_error( $check_base ) ) {
			return $check_base;
		}

		$group_permission_check = $this->base_permissions( $request, 'delete_item' );
		if ( is_wp_error( $group_permission_check ) ) {
			return $group_permission_check;
		}

		return true;
	}

	/**
	 * Filters the query arguments for a request.
	 *
	 * @param array           $args    Key value array of query var to query value.
	 * @param WP_REST_Request $request The request used.
	 */
	public function filter_rest_query_shortinit_group_args( $args, $request ) {
		$group_id = $request->get_param( 'group_id' );
		if ( null !== $group_id ) {
			$args['post_parent'] = (int) $request['group_id'];
		}

		return $args;
	}

	/**
	 * Filters the query arguments for a request.
	 *
	 * @param array           $args    Key value array of query var to query value.
	 * @param WP_REST_Request $request The request used.
	 */
	public function filter_rest_query_group_args( $args, $request ) {
		if ( isset( $request['user_content'] ) ) {
			$user_id = bp_loggedin_user_id();

			$group_ids = groups_get_user_groups( $user_id );
			foreach ( $group_ids['groups'] as $group_id ) {
				$groups[] = $group_id;
			}

			$args['post_parent__in'] = $groups;
		} else {
			$args['post_parent'] = (int) $request['group_id'];
		}

		return $args;
	}

	/**
	 * Filters a post before it is put into db.
	 *
	 * @param stdClass        $prepared_post An object representing a single post prepare for inserting or updating the database.
	 * @param WP_REST_Request $request       Request object.
	 *
	 * @return stdClass $prepared_post An object representing a single post prepare
	 */
	public function set_group_connection( $prepared_post, $request ) {
		$prepared_post->post_parent = (int) $request['group_id'];

		return $prepared_post;
	}

	/**
	 * Map meta group connection
	 *
	 * @param WP_Post         $post     Inserted or updated post object.
	 * @param WP_REST_Request $request  Request object.
	 * @param bool            $creating True when creating a post, false when updating.
	 */
	public function set_meta_group_connection( $post, $request, $creating ) {
		$group_id = (int) $request['group_id'];

		if ( $creating ) {
			add_post_meta( $post->ID, '_nisje_group_connection', $group_id );
		} else {
			update_post_meta( $post->ID, '_nisje_group_connection', $group_id );
		}
	}

	/**
	 * Map group type
	 *
	 * @param WP_Post         $post     Inserted or updated post object.
	 * @param WP_REST_Request $request  Request object.
	 * @param bool            $creating True when creating a post, false when updating.
	 */
	public function map_group_types( $post, $request, $creating ) {
		$group_id    = (int) $request['group_id'];
		$group_types = bp_groups_get_group_type( $group_id, false );
		if ( $group_types ) {
			wp_set_post_terms( $post->ID, $group_types, 'nisje-map-group-types' );
		}
	}

	/**
	 * Allow users create group posts. We handle caps in the base permissionss.
	 * This is needed to allow group admins to post content to groups without group caps.
	 *
	 * @param array  $caps    Returns the user's actual capabilities.
	 * @param string $cap     Capability name.
	 * @param int    $user_id The user ID.
	 * @param array  $args    Adds the context to the cap. Typically the object ID.
	 */
	public function allow_group_admin_to_manage_posts( $caps, $cap, $user_id, $args ) {
		$post_type = get_post_type_object( $this->post_type );
		$group_id  = $this->request->get_param( 'group_id' );
		$group     = nisje_get_group( $group_id );
		if ( ! is_wp_error( $group ) ) {
			$admin = apply_filters( "nisje_group_post_validate_against_admin_{$this->post_type}", true, $group, $user_id, $this->post_type );

			if ( $admin && groups_is_user_admin( $user_id, $group->id ) ) {
				return [];
			} elseif ( ! $admin && groups_is_user_member( $user_id, $group->id ) ) {
				return [];
			}
		}

		return [ 'do_not_allow' ];
	}
}
