<?php

namespace GemBoards\API;

use GemBoards\Entities\Role;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WP_User_Query;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Users extends WP_REST_Controller {

	public static function init() {
		$self            = new self();
		$self->namespace = 'gemboards/v1';
		$self->rest_base = 'users';
		add_action( 'rest_api_init', [ $self, 'register_routes' ] );
	}

	public function register_routes() {
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			[
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_items' ],
				'permission_callback' => function () {
					return current_user_can( 'read' );
				},
				'args'                => [
					'page'           => [
						'type'    => 'integer',
						'default' => 1,
					],
					'per_page'       => [
						'type'    => 'integer',
						'default' => 10,
					],
					'search'         => [
						'type'     => 'string',
						'required' => false,
					],
					'only_assignees' => [
						'type'     => 'boolean',
						'required' => false,
						'default'  => false,
					],
				],
				'schema'              => [ $this, 'get_collection_schema' ],
			],
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>\d+)',
			[
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_item' ],
				'permission_callback' => function () {
					return current_user_can( 'read' );
				},
				'args'                => [
					'id' => [
						'type'        => 'integer',
						'required'    => true,
						'description' => __( 'User ID', 'gemboards' ),
					],
				],
				'schema'              => [ $this, 'get_item_schema' ],
			],
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/me',
			[
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_current_user' ],
				'permission_callback' => function () {
					if ( ! current_user_can( 'read' ) ) {
						return new WP_Error( 'rest_forbidden_context', esc_html__( 'Sorry, you are not allowed to access this API.', 'gemboards' ), array( 'status' => rest_authorization_required_code() ) );
					}

					return true;
				},
				'args'                => [],
				'schema'              => [ $this, 'get_item_schema' ],
			]
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<id>\d+)/update-role',
			[
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => [ $this, 'update_role' ],
				'permission_callback' => function () {
					return current_user_can( 'promote_users' );
				},
				'args'                => [
					'id' => [
						'type'        => 'integer',
						'required'    => true,
						'description' => __( 'User ID', 'gemboards' ),
					],
				],
				'schema'              => [ $this, 'get_item_schema' ],
			]
		);
	}

	public function get_collection_schema(): array {
		return [
			'$schema' => 'http://json-schema.org/draft-04/schema#',
			'title'   => __( 'Users', 'gemboards' ),
			'type'    => 'array',
			'items'   => [
				'type'       => 'object',
				'properties' => [
					'id'          => [
						'type'        => 'integer',
						'description' => __( 'User ID', 'gemboards' ),
					],
					'first_name'  => [
						'type'        => 'string',
						'description' => __( 'User\'s First Name', 'gemboards' ),
					],
					'last_name'   => [
						'type'        => 'string',
						'description' => __( 'User\'s Last Name', 'gemboards' ),
					],
					'name'        => [
						'type'        => 'string',
						'description' => __( 'User\'s Display Name', 'gemboards' ),
					],
					'email'       => [
						'type'        => 'string',
						'description' => __( 'User\'s Email', 'gemboards' ),
					],
					'url'         => [
						'type'        => 'string',
						'description' => __( 'User\'s URL', 'gemboards' ),
					],
					'description' => [
						'type'        => 'string',
						'description' => __( 'User\'s Description', 'gemboards' ),
					],
					'avatar_url'  => [
						'type'        => 'string',
						'description' => __( 'User\'s avatar URL', 'gemboards' ),
					],
					'registered'  => [
						'type'        => 'string',
						'description' => __( 'User\'s registered date', 'gemboards' ),
					],
					'permissions' => [
						'type'  => 'array',
						'items' => [
							'type'       => 'object',
							'properties' => [
								'permission_id'  => [
									'type'        => 'integer',
									'description' => __( 'Permission ID', 'gemboards' ),
								],
								'project_id'     => [
									'type'        => 'integer',
									'description' => __( 'Project ID', 'gemboards' ),
								],
								'user_id'        => [
									'type'        => 'integer',
									'description' => __( 'User ID', 'gemboards' ),
								],
								'role'           => [
									'type'        => 'string',
									'description' => __( 'Project\'s role', 'gemboards' ),
								],
								'type'           => [
									'type'        => 'string',
									'description' => __( 'Permission\'s type', 'gemboards' ),
								],
								'owner_id'       => [
									'type'        => 'integer',
									'description' => __( 'Project\'s Owner Id', 'gemboards' ),
								],
								'title'          => [
									'type'        => 'string',
									'description' => __( 'Project\'s title', 'gemboards' ),
								],
								'slug'           => [
									'type'        => 'string',
									'description' => __( 'Project\'s slug', 'gemboards' ),
								],
								'project_status' => [
									'type'        => 'string',
									'description' => __( 'Project\'s status', 'gemboards' ),
								],
								'description'    => [
									'type'        => 'string',
									'description' => __( 'Project\'s description', 'gemboards' ),
								],
								'icon'           => [
									'type'        => 'string',
									'description' => __( 'Project\'s icon', 'gemboards' ),
								],
								'color'          => [
									'type'        => 'string',
									'description' => __( 'Project\'s color', 'gemboards' ),
								],
								'created_at'     => [
									'type'        => 'string',
									'description' => __( 'Project\'s created date', 'gemboards' ),
								],
								'updated_at'     => [
									'type'        => 'string',
									'description' => __( 'Project\'s updated date', 'gemboards' ),
								],
							],
						],
					],
				],
			],
		];
	}

	/**
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_items( $request ) {
		$paged    = max( 1, intval( $request->get_param( 'page' ) ) );
		$per_page = intval( $request->get_param( 'per_page' ) );
		$search   = sanitize_text_field( $request->get_param( 'search' ) );

		$args     = [
			'number'         => $per_page,
			'offset'         => ( $paged - 1 ) * $per_page,
			'search'         => '*' . esc_attr( $search ) . '*',
			'search_columns' => [ 'user_login', 'user_nicename', 'user_email', 'display_name' ],
			'orderby'        => 'registered',
			'order'          => 'DESC',
		];

		if ( $request->get_param('only_assignees') ) {
			global $wpdb;
			$user_ids = $wpdb->get_col( "SELECT DISTINCT user_id FROM {$wpdb->prefix}gemboards_roles WHERE object_type = 'project'" );
			$user_ids = array_map( 'intval', $user_ids );
			$args['include'] = $user_ids;
		}

		$user_query = new WP_User_Query( $args );

		$users = [];
		foreach ( $user_query->get_results() as $user ) {
			$users[ $user->ID ] = [
				'id'              => $user->ID,
				'username'        => $user->user_login,
				'first_name'      => get_user_meta( $user->ID, 'first_name', true ),
				'last_name'       => get_user_meta( $user->ID, 'last_name', true ),
				'name'            => $user->display_name,
				'email'           => $user->user_email,
				'url'             => $user->user_url,
				'gemboards_admin' => user_can( $user, 'manage_gemboards' ),
				'description'     => get_user_meta( $user->ID, 'description', true ),
				'avatar_url'      => get_avatar_url( $user->ID ),
				'registered'      => $user->user_registered,
				'projects'        => [],
			];
		}

		$total_users = $user_query->get_total();
		$total_pages = ceil( $total_users / $per_page );

		$user_ids           = array_map( fn( $user ) => $user['id'], $users );
		$user_ids_formatter = implode( ',', array_fill( 0, count( $user_ids ), '%d' ) );

		$cache_key   = 'projects_with_roles_for_users_' . md5( wp_json_encode( $user_ids_formatter ) );
		$cache_group = 'gemboards_roles';
		$projects    = wp_cache_get( $cache_key, $cache_group );
		if ( false === $projects ) {
			global $wpdb;
			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			$projects = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT
						p.id AS role_id,
						pr.id AS project_id,
						p.user_id AS user_id,
						p.`role`,
						p.object_type AS type,
						pr.owner_id,
						pr.title,
						pr.slug,
						pr.project_status,
						pr.description,
						pr.icon,
						pr.color,
						pr.created_at,
						pr.updated_at
					FROM
						{$wpdb->prefix}gemboards_roles p
						INNER JOIN {$wpdb->prefix}gemboards_projects pr ON p.object_id = pr.id
					WHERE
						p.object_type = 'project' AND p.user_id IN ($user_ids_formatter)", ...$user_ids
				)
			);
			// phpcs:enable

			wp_cache_set( $cache_key, $projects, $cache_group );
		}


		foreach ( (array) $projects as $project ) {
			$project->role_id                         = absint( $project->role_id );
			$project->project_id                      = absint( $project->project_id );
			$project->user_id                         = absint( $project->user_id );
			$project->owner_id                        = absint( $project->owner_id );
			$users[ $project->user_id ]['projects'][] = $project;
		}

		return rest_ensure_response( [
			'users'      => array_values( $users ),
			'pagination' => [
				'total'        => $total_users,
				'total_pages'  => $total_pages,
				'current_page' => $paged,
				'per_page'     => $per_page,
			],
		] );
	}

	public function get_item_schema(): array {
		return [
			'type'       => 'object',
			'properties' => [
				'id'          => [
					'type'        => 'integer',
					'description' => __( 'User ID', 'gemboards' ),
				],
				'first_name'  => [
					'type'        => 'string',
					'description' => __( 'User\'s First Name', 'gemboards' ),
				],
				'last_name'   => [
					'type'        => 'string',
					'description' => __( 'User\'s Last Name', 'gemboards' ),
				],
				'name'        => [
					'type'        => 'string',
					'description' => __( 'User\'s Display Name', 'gemboards' ),
				],
				'email'       => [
					'type'        => 'string',
					'description' => __( 'User\'s Email', 'gemboards' ),
				],
				'url'         => [
					'type'        => 'string',
					'description' => __( 'User\'s URL', 'gemboards' ),
				],
				'description' => [
					'type'        => 'string',
					'description' => __( 'User\'s Description', 'gemboards' ),
				],
				'avatar_url'  => [
					'type'        => 'string',
					'description' => __( 'User\'s avatar URL', 'gemboards' ),
				],
				'registered'  => [
					'type'        => 'string',
					'description' => __( 'User\'s registered date', 'gemboards' ),
				],
				'permissions' => [
					'type'  => 'array',
					'items' => [
						'type'       => 'object',
						'properties' => [
							'permission_id'  => [
								'type'        => 'integer',
								'description' => __( 'Permission ID', 'gemboards' ),
							],
							'project_id'     => [
								'type'        => 'integer',
								'description' => __( 'Project ID', 'gemboards' ),
							],
							'user_id'        => [
								'type'        => 'integer',
								'description' => __( 'User ID', 'gemboards' ),
							],
							'role'           => [
								'type'        => 'string',
								'description' => __( 'Project\'s role', 'gemboards' ),
							],
							'type'           => [
								'type'        => 'string',
								'description' => __( 'Permission\'s type', 'gemboards' ),
							],
							'owner_id'       => [
								'type'        => 'integer',
								'description' => __( 'Project\'s Owner Id', 'gemboards' ),
							],
							'title'          => [
								'type'        => 'string',
								'description' => __( 'Project\'s title', 'gemboards' ),
							],
							'slug'           => [
								'type'        => 'string',
								'description' => __( 'Project\'s slug', 'gemboards' ),
							],
							'project_status' => [
								'type'        => 'string',
								'description' => __( 'Project\'s status', 'gemboards' ),
							],
							'description'    => [
								'type'        => 'string',
								'description' => __( 'Project\'s description', 'gemboards' ),
							],
							'icon'           => [
								'type'        => 'string',
								'description' => __( 'Project\'s icon', 'gemboards' ),
							],
							'color'          => [
								'type'        => 'string',
								'description' => __( 'Project\'s color', 'gemboards' ),
							],
							'created_at'     => [
								'type'        => 'string',
								'description' => __( 'Project\'s created date', 'gemboards' ),
							],
							'updated_at'     => [
								'type'        => 'string',
								'description' => __( 'Project\'s updated date', 'gemboards' ),
							],
						],
					],
				],
			],
		];
	}

	/**
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function get_item( $request ) {
		$user_id = $request->get_param( 'id' );

		return $this->get_user( $user_id );
	}

	public function get_current_user() {
		return $this->get_user( get_current_user_id() );
	}

	/**
	 * @param WP_REST_Request $request Full details about the request.
	 *
	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
	 */
	public function update_role( WP_REST_Request $request ) {
		$user_id     = $request->get_param( 'id' );
		$permissions = $request->get_param( 'permissions' );

		if ( empty( $permissions ) || ! is_array( $permissions ) ) {
			return new WP_Error( 'permissions_required', __( 'Permissions required.', 'gemboards' ) );
		}

		foreach ( $permissions as $permission ) {
			if ( ! isset( $permission['role'], $permission['permission_id'] ) ) {
				return new WP_Error( 'invalid_permission', __( 'Invalid permissions data provided.', 'gemboards' ) );
			}

			if ( ! in_array( $permission['role'], [ 'admin', 'member', 'manager', 'guest' ], true ) ) {
				return new WP_Error( 'invalid-role', __( 'Invalid role provided.', 'gemboards' ) );
			}
		}

		$user = get_user( $user_id );
		if ( ! $user ) {
			return new WP_Error( 'invalid-user', __( 'Invalid user.', 'gemboards' ) );
		}

		$cases = [];
		$ids   = [];
		global $wpdb;
		foreach ( $permissions as $permission ) {
			$id      = (int) $permission['permission_id'];
			$ids[]   = $id;
			// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
			$cases[] = $wpdb->prepare( 'WHEN %d THEN %s', $id, $permission['role'] );
			// phpcs:enable
		}
		$case_sql      = implode( "\n\t\t    ", $cases );
		$ids_formatter = implode( ',', array_fill( 0, count( $ids ), '%d' ) );

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching -- Clearing cache with using `clear_cache` method of entity class.
		$wpdb->query(
			$wpdb->prepare(
				"UPDATE {$wpdb->prefix}gemboards_roles SET role = CASE id {$case_sql} END WHERE id IN ($ids_formatter)",
				...$ids
			)
		);
		// phpcs:enable

		( new Role() )->clear_cache();

		return rest_ensure_response( [
			'success' => true,
		] );
	}

	private function get_user( int $user_id ) {
		$user = get_user( $user_id );
		if ( ! $user ) {
			return new WP_Error( 'invalid-user', __( 'Invalid user.', 'gemboards' ) );
		}

		$user_data = [
			'id'          => $user->ID,
			'first_name'  => $user->first_name,
			'last_name'   => $user->last_name,
			'name'        => $user->display_name,
			'email'       => $user->user_email,
			'url'         => $user->user_url,
			'description' => $user->description,
			'avatar_url'  => get_avatar_url( $user->ID ),
			'registered'  => $user->user_registered,
			'projects'    => [],
		];

		$cache_key   = 'projects_with_roles_for_user_' . $user_id;
		$cache_group = 'gemboards_roles';
		$projects    = wp_cache_get( $cache_key, $cache_group );

		if ( false === $projects ) {
			global $wpdb;

			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
			$projects = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT
						p.id AS permission_id,
						pr.id AS project_id,
						p.user_id AS user_id,
						p.`role`,
						p.object_type AS type,
						pr.owner_id,
						pr.title,
						pr.slug,
						pr.project_status,
						pr.description,
						pr.icon,
						pr.color,
						pr.created_at,
						pr.updated_at
					FROM
						{$wpdb->prefix}gemboards_roles p
						INNER JOIN {$wpdb->prefix}gemboards_projects pr ON p.object_id = pr.id
					WHERE
						p.object_type = 'project' AND p.user_id = %d", $user->ID
				)
			);
			// phpcs:enable

			if ( is_array( $projects ) ) {
				wp_cache_set( $cache_key, $projects, $cache_group );
			}
		}

		if ( is_array( $projects ) ) {
			$user_data['projects'] = $projects;
		}

		return rest_ensure_response( $user_data );
	}
}
