<?php

namespace WeDevs\AppBridge\Core\REST\Auth;

use WeDevs\AppBridge\Core\Auth\PasswordResetOtp;
use WP_Error;
use WP_REST_Server;
use WP_REST_Users_Controller;

class UserController extends WP_REST_Users_Controller {
    /**
     * Endpoint namespace
     *
     * @var string
     */
    public function __construct() {
        parent::__construct();

        $this->namespace = 'app-bridge/v1';
		$this->rest_base = 'auth/users';

        add_action( 'rest_insert_user', array( $this, 'on_user_insert' ), 10, 3 );
    }

    /**
     * Handle user insertions to perform additional processing if needed.
     *
     * @param WP_User $user The user object being inserted.
     * @param WP_REST_Request $request The REST request object.
     * @param bool $creating Whether the user is being created (true) or updated (false).
     */
    public function on_user_insert( $user, $request, $creating ) {
        if ( ! $creating ) {
            return;
        }

        wp_set_current_user( $user->ID );
    }

    /**
     * Registers the meta fields for the user.
     * @see https://developer.wordpress.org/reference/functions/register_meta/
     *
     * @return void
     */
    public function register_routes() {
        $this->register_meta_fields();

        register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'create_item' ),
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
				),
				'allow_batch' => $this->allow_batch,
				'schema'      => array( $this, 'get_public_item_schema' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/me',
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'permission_callback' => '__return_true',
					'callback'            => array( $this, 'get_current_item' ),
					'args'                => array(
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
					),
				),
				array(
					'methods'             => WP_REST_Server::EDITABLE,
					'callback'            => array( $this, 'update_current_item' ),
					'permission_callback' => array( $this, 'update_current_item_permissions_check' ),
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				),
				array(
					'methods'             => WP_REST_Server::DELETABLE,
					'callback'            => array( $this, 'delete_current_item' ),
					'permission_callback' => array( $this, 'delete_my_account_permissions_check' ),
					'args'                => array(
						'force'    => array(
							'type'        => 'boolean',
							'default'     => false,
							'description' => __( 'Required to be true, as users do not support trashing.', 'app-bridge' ),
						),
						'reassign' => array(
							'type'              => 'integer',
							'description'       => __( 'Reassign the deleted user\'s posts and links to this user ID.', 'app-bridge' ),
							'required'          => true,
							'sanitize_callback' => array( $this, 'check_reassign' ),
						),
					),
				),
				'schema' => array( $this, 'get_public_item_schema' ),
			)
		);

        register_rest_route(
            $this->namespace, '/' . $this->rest_base . '/lostpassword', array(
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'lost_password' ),
					'args'                => array(
                        'username' => array(
							'type'              => 'string',
							'description'       => __( 'Login username or email for the user.', 'app-bridge' ),
							'required'          => true,
							'sanitize_callback' => 'sanitize_user',
						),
                    ),
					'permission_callback' => '__return_true',
				),
            )
        );

        register_rest_route(
            $this->namespace, '/' . $this->rest_base . '/lostpassword/otp', array(
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'send_lost_password_otp' ),
					'args'                => array(
						'username' => array(
							'type'              => 'string',
							'description'       => __( 'Login username or email for the user.', 'app-bridge' ),
							'required'          => true,
							'sanitize_callback' => 'sanitize_user',
						),
					),
					'permission_callback' => '__return_true',
				),
            )
        );

        register_rest_route(
            $this->namespace, '/' . $this->rest_base . '/lostpassword/otp/verify', array(
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'verify_lost_password_otp' ),
					'args'                => array(
						'username' => array(
							'type'              => 'string',
							'description'       => __( 'Login username or email for the user.', 'app-bridge' ),
							'required'          => true,
							'sanitize_callback' => 'sanitize_user',
						),
						'otp'      => array(
							'type'              => 'string',
							'description'       => __( '6-digit one-time password sent to the user.', 'app-bridge' ),
							'required'          => true,
							'sanitize_callback' => 'sanitize_text_field',
						),
					),
					'permission_callback' => '__return_true',
				),
            )
        );

        register_rest_route(
            $this->namespace, '/' . $this->rest_base . '/lostpassword/otp/reset', array(
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'reset_password_with_otp' ),
					'args'                => array(
						'username'     => array(
							'type'              => 'string',
							'description'       => __( 'Login username or email for the user.', 'app-bridge' ),
							'required'          => true,
							'sanitize_callback' => 'sanitize_user',
						),
						'reset_key'        => array(
							'type'              => 'string',
							'description'       => __( 'Reset key returned by the OTP verification endpoint.', 'app-bridge' ),
							'required'          => true,
							'sanitize_callback' => 'sanitize_text_field',
						),
						'new_password'     => array(
							'type'        => 'string',
							'description' => __( 'New account password.', 'app-bridge' ),
							'required'    => true,
						),
						'confirm_password' => array(
							'type'        => 'string',
							'description' => __( 'Confirm the new account password.', 'app-bridge' ),
							'required'    => true,
						),
					),
					'permission_callback' => '__return_true',
				),
            )
        );
    }

    /**
	 * Updates the current user.
	 *
	 * @since 4.7.0
	 *
	 * @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_current_item( $request ) {
		$request['id'] = get_current_user_id();
        $is_valid_password = $this->validate_by_current_password( $request );

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

        do_action( 'app_bridge_rest_before_profile_update', $request );

        $response = $this->update_item( $request );

        do_action( 'app_bridge_rest_after_profile_update', $response, $request );

		return $response;
	}

    /**
     * Validates the current password if a new password is being set.
     *
     * @param WP_REST_Request $request Full details about the request.
     * @return true|WP_Error True if validation passes, or WP_Error object if validation fails.
     */
    protected function validate_by_current_password( $request ) {
        $password     = $request->get_param( 'password' );
        $current_password = $request->get_param( 'current_password' ) ?? '';
        $user = get_user_by( 'id', $request['id'] );

        if ( ! empty( $password ) ) {
            if ( empty( $current_password ) ) {
                return new WP_Error(
                    'app_bridge_rest_users_missing_current_password',
                    esc_html__( 'The current password can not be empty.', 'app-bridge' ),
                    array( 'status' => 400 )
                );
            }

            if ( ! $user || ! wp_check_password( $current_password, $user->user_pass, $user->ID ) ) {
                return new WP_Error(
                    'app_bridge_rest_users_invalid_current_password',
                    esc_html__( 'The current password you entered is incorrect.', 'app-bridge' ),
                    array( 'status' => 400 )
                );
            }
        }

        return true;
    }

    /**
	 * Determines if the current user is allowed to make the desired roles change.
	 *
	 * @since 4.7.0
	 *
	 * @global WP_Roles $wp_roles WordPress role management object.
	 *
	 * @param int   $user_id User ID.
	 * @param array $roles   New user roles.
	 * @return true|WP_Error True if the current user is allowed to make the role change,
	 *                       otherwise a WP_Error object.
	 */
	protected function check_role_update( $user_id, $roles ) {
        $allowed_roles = apply_filters( 'app_bridge_rest_user_allowed_roles', [ 'customer', 'subscriber' ] );

        $filtered_roles = array_filter(
            $roles, function ( $role ) use ( $allowed_roles ) {
				return in_array( $role, $allowed_roles, true );
			}
        );

        if ( empty( $filtered_roles ) && ! empty( $roles ) ) {
            return new WP_Error(
                'rest_invalid_user_role',
                __( 'Sorry, you are not allowed to update user roles.', 'app-bridge' ),
                array( 'status' => rest_authorization_required_code() )
            );
        }

        return parent::check_role_update( $user_id, $filtered_roles );
    }

    /**
	 * Override the default permissions check for creating a user to allow it for unauthenticated requests, as the endpoint is meant for user registration.
	 *
	 * @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 ) {
		return apply_filters( 'app_bridge_rest_user_create_item_permissions_check', true, $request );
	}

    /**
	 * Registers the meta fields for the user.
	 * @see https://developer.wordpress.org/reference/functions/register_meta/
     *
     * @return void
	 */
    protected function register_meta_fields() {
        foreach ( $this->get_meta_fields() as $key => $args ) {
            register_meta( 'user', $key, $args );
        }
    }

    /**
     * Returns the meta fields to be registered for the user.
     * Each meta field should be defined as an array with the following
     * keys:
     * - type: The data type of the meta field (e.g., 'string', 'integer').
     * - single: Whether the meta field is single (true) or multiple (false).
     * - show_in_rest: Whether to expose the meta field in the REST API (true or false).
     * - auth_callback: (Optional) A callback function to determine if the meta field should be included in the REST response based on the current user's authentication status.
     * @return array An associative array of meta fields to be registered.
     */
    protected function get_meta_fields() {
		/**
		 * Get the list of user meta fields to register for the REST API.
		 *
		 * Returns an array of user meta definitions that can be passed to
		 * register_meta( 'user', $meta_key, $args ).
		 *
		 * Each field supports standard WordPress meta registration arguments such as:
		 * - type
		 * - description
		 * - default
		 * - single
		 * - sanitize_callback
		 * - auth_callback
		 * - show_in_rest
		 *
		 * Developers may modify or extend the fields using the
		 * `app_bridge_rest_user_meta_fields` filter.
		 *
		 * Example:
		 *
		 * add_filter( 'app_bridge_rest_user_meta_fields', function ( $fields ) {
		 *     $fields['city'] = [
		 *         'type'         => 'string',
		 *         'description'  => __( 'User city', 'my-plugin' ),
		 *         'single'       => true,
		 *         'show_in_rest' => true,
		 *     ];
		 *
		 *     return $fields;
		 * } );
		 *
		 * @since SINCE_TAG
		 *
		 * @return array<string, array<string, mixed>> Registered user meta fields.
		 */
        return apply_filters(
            'app_bridge_rest_user_meta_fields', [
				'phone' => [
					'type'              => 'string',
					'label'             => __( 'Phone', 'app-bridge' ),
					'description'       => __( 'User phone number', 'app-bridge' ),
					'default'           => '',
					'single'            => true,
					'sanitize_callback' => null,
					'auth_callback'     => '__return_true',
					'show_in_rest'      => true,
				],
				'country' => [
					'type'              => 'string',
					'label'             => __( 'Country', 'app-bridge' ),
					'description'       => __( 'User country', 'app-bridge' ),
					'default'           => '',
					'single'            => true,
					'show_in_rest'      => true,
				],
			]
        );
    }

    /**
	 *Override the default permissions check for deleting a user to allow it for authenticated requests, as the endpoint is meant for users to delete their own accounts.
     *
	 * @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_my_account_permissions_check( $request ) {
		$request['id'] = get_current_user_id();

		return apply_filters( 'app_bridge_rest_user_delete_my_account_permissions_check', true, $request );
	}

    /**
     * Lost password
     *
     * @since 1.0.0
     *
     * @return WP_REST_Response|WP_Error
     */
    public function lost_password( $request ) {
        $response = array();
        $user_login = sanitize_text_field( $request['username'] );

        if ( empty( $user_login ) ) {
            return new WP_Error( 'no_userlogin_info', __( 'The field username is required.', 'app-bridge' ), array( 'status' => 400 ) );
        } else {
            $user_id = username_exists( $user_login );

            if ( $user_id === false ) {
                $user_id = email_exists( $user_login );
                if ( $user_id === false ) {
                    return new WP_Error( 'no_user_found', __( 'No user found', 'app-bridge' ), array( 'status' => 400 ) );
                }
            }
        }

        // If WooCommerce is active, use its password reset function to ensure compatibility with WooCommerce's email templates and hooks.
        if ( function_exists( 'WC' ) ) {
            $user_data = get_user_by( 'email', $user_login );
            $user_login = $user_data->user_login;
            $key = get_password_reset_key( $user_data );

            // Send email notification.
            WC()->mailer();
            do_action( 'woocommerce_reset_password_notification', $user_login, $key );
        } else {
            $errors = retrieve_password( $user_login );

            if ( is_wp_error( $errors ) ) {
                return new WP_Error( 'password_reset_failed', __( 'Password reset failed. Please try again later.', 'app-bridge' ), array( 'status' => 500 ) );
            }
        }

        $response = array(
            'code'    => 200,
            'message' => __( 'Reset Password link had been send to your email.', 'app-bridge' ),
        );

        return rest_ensure_response( $response );
    }

    /**
     * Resolve a WP_User from a login or email.
     *
     * @param string $username
     * @return \WP_User|WP_Error
     */
    protected function resolve_user_for_password_reset( string $username ) {
        if ( '' === $username ) {
            return new WP_Error( 'no_userlogin_info', __( 'The field username is required.', 'app-bridge' ), array( 'status' => 400 ) );
        }

        $user = get_user_by( 'login', $username );

        if ( ! $user && is_email( $username ) ) {
            $user = get_user_by( 'email', $username );
        }

        if ( ! $user ) {
            return new WP_Error( 'no_user_found', __( 'No user found.', 'app-bridge' ), array( 'status' => 400 ) );
        }

        return $user;
    }

    /**
     * Issue an OTP and email it to the user.
     *
     * The email is delivered by PasswordResetOtpMailer via the
     * `app_bridge_password_reset_otp_notification` action, with no dependency
     * on WooCommerce or any other parent plugin.
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|WP_Error
     */
    public function send_lost_password_otp( $request ) {
        $user = $this->resolve_user_for_password_reset( sanitize_text_field( $request['username'] ) );

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

        $service = new PasswordResetOtp();
        $issued  = $service->issue( $user );

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

        do_action(
            'app_bridge_password_reset_otp_notification',
            $user->user_login,
            $issued['otp'],
            $issued['expires_at']
        );

        return rest_ensure_response(
            array(
                'code'       => 200,
                'message'    => __( 'A reset code has been sent to your email.', 'app-bridge' ),
                'expires_at' => mysql_to_rfc3339( gmdate( 'Y-m-d H:i:s', $issued['expires_at'] ) ),
            )
        );
    }

    /**
     * Verify a submitted OTP and exchange it for a one-shot reset key.
     *
     * The returned key is the standard WordPress `password_reset_key` and is
     * consumed by `reset_password_with_otp()` via `check_password_reset_key()`.
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|WP_Error
     */
    public function verify_lost_password_otp( $request ) {
        $user = $this->resolve_user_for_password_reset( sanitize_text_field( $request['username'] ) );

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

        $otp = preg_replace( '/\D/', '', (string) $request['otp'] );

        if ( strlen( $otp ) !== 6 ) {
            return new WP_Error(
                'app_bridge_otp_invalid',
                __( 'Invalid reset code.', 'app-bridge' ),
                array( 'status' => 400 )
            );
        }

        $service  = new PasswordResetOtp();
        $verified = $service->verify( $user, $otp );

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

        $reset_key = get_password_reset_key( $user );

        if ( is_wp_error( $reset_key ) ) {
            return new WP_Error(
                'app_bridge_password_reset_failed',
                $reset_key->get_error_message(),
                array( 'status' => 500 )
            );
        }

        return rest_ensure_response(
            array(
                'code'      => 200,
                'message'   => __( 'Reset code verified.', 'app-bridge' ),
                'username'  => $user->user_login,
                'reset_key' => $reset_key,
            )
        );
    }

    /**
     * Reset the password using a key issued by the OTP verification endpoint.
     *
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|WP_Error
     */
    public function reset_password_with_otp( $request ) {
        $user = $this->resolve_user_for_password_reset( sanitize_text_field( $request['username'] ) );

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

        $reset_key        = sanitize_text_field( (string) $request['reset_key'] );
        $new_password     = (string) $request['new_password'];
        $confirm_password = (string) $request['confirm_password'];

        if ( '' === $reset_key ) {
            return new WP_Error(
                'app_bridge_reset_key_required',
                __( 'Reset key is required.', 'app-bridge' ),
                array( 'status' => 400 )
            );
        }

        if ( '' === trim( $new_password ) ) {
            return new WP_Error(
                'app_bridge_password_required',
                __( 'New password is required.', 'app-bridge' ),
                array( 'status' => 400 )
            );
        }

        if ( ! hash_equals( $new_password, $confirm_password ) ) {
            return new WP_Error(
                'app_bridge_password_mismatch',
                __( 'Passwords do not match.', 'app-bridge' ),
                array( 'status' => 400 )
            );
        }

        $check_user = check_password_reset_key( $reset_key, $user->user_login );

        if ( is_wp_error( $check_user ) ) {
            return new WP_Error(
                'app_bridge_invalid_reset_key',
                $check_user->get_error_message(),
                array( 'status' => 400 )
            );
        }

        reset_password( $check_user, $new_password );

        return rest_ensure_response(
            array(
                'code'    => 200,
                'message' => __( 'Password has been reset successfully.', 'app-bridge' ),
            )
        );
    }

	/**
	 * Update the context to appear in the REST API response for user fields for "edit" context.
	 *
	 * @return array Item schema data.
	 */
	public function get_item_schema() {
		$schema = parent::get_item_schema();

        $schema['properties']['first_name']['context'][] = 'view';
        $schema['properties']['last_name']['context'][] = 'view';
        $schema['properties']['username']['context'][] = 'view';
        $schema['properties']['email']['context'][] = 'view';
        $schema['properties']['locale']['context'][] = 'view';
        $schema['properties']['nickname']['context'][] = 'view';
        $schema['properties']['registered_date']['context'][] = 'view';
        $schema['properties']['roles']['context'][] = 'view';
        $schema['properties']['capabilities']['context'][] = 'view';

        return $schema;
    }
}
