<?php

/**
 * Admin Access Control
 *
 * Handles hiding the admin bar, restricting wp-admin access,
 * and hiding the WordPress login page for non-admin users.
 *
 * @package ArraySubs\Features\MembersAccess\Services
 * @since 1.5.0
 */

namespace ArraySubs\Features\MembersAccess\Services;

defined('ABSPATH') || exit;

/**
 * Admin Access Control class
 */
class AdminAccessControl
{
    /**
     * Enable auto-loading
     *
     * @var bool
     */
    public static $loadable = true;

    /**
     * Cached toolkit settings.
     *
     * @var array|null
     */
    private ?array $settings = null;

    /**
     * Constructor
     */
    public function __construct()
    {
        // Hide admin bar — early priority so caching plugins see the filter.
        add_filter('show_admin_bar', [$this, 'maybeHideAdminBar']);

        // Replace WordPress auth URLs with the WooCommerce account URL when
        // the login page is hidden for customer-facing flows.
        add_filter('login_url', [$this, 'filterLoginUrl'], 10, 3);
        add_filter('register_url', [$this, 'filterRegisterUrl'], 10, 1);

        // Restrict wp-admin access.
        add_action('admin_init', [$this, 'maybeRestrictWpAdmin'], 1);

        // Hide WP login page — runs on login_init for login page requests.
        add_action('login_init', [$this, 'maybeHideWpLogin'], 1);
    }

    /**
     * Get cached toolkit settings.
     *
     * @return array
     */
    private function getSettings(): array
    {
        if (null === $this->settings) {
            $this->settings = arraysubs_get_setting('toolkit', []);
        }
        return $this->settings;
    }

    // ------------------------------------------------------------------
    // Admin Bar
    // ------------------------------------------------------------------

    /**
     * Conditionally hide the admin bar for non-allowed users.
     *
     * @param bool $show_admin_bar Current visibility.
     * @return bool
     */
    public function maybeHideAdminBar(bool $show_admin_bar): bool
    {
        $settings = $this->getSettings();

        if (empty($settings['hide_admin_bar'])) {
            return $show_admin_bar;
        }

        // Never hide for admins.
        if ($this->isAllowedUser($settings)) {
            return $show_admin_bar;
        }

        return false;
    }

    /**
     * Filter login URLs so third-party plugins and themes use the
     * WooCommerce My Account page when the WordPress login page is hidden.
     *
     * @param string      $login_url    Generated login URL.
     * @param string      $redirect     Redirect destination after login.
     * @param bool|string $force_reauth Whether to force re-authentication.
     * @return string
     */
    public function filterLoginUrl(string $login_url, string $redirect, $force_reauth): string
    {
        unset($force_reauth);

        if (!$this->shouldReplaceAuthUrls()) {
            return $login_url;
        }

        if ($this->isAdminRedirectTarget($redirect)) {
            return $login_url;
        }

        $args = [];

        if (!empty($redirect)) {
            $args['redirect'] = esc_url_raw($redirect);
        }

        return $this->buildAccountUrl($args);
    }

    /**
     * Filter register URLs so third-party plugins and themes use the
     * WooCommerce My Account page when the WordPress login page is hidden.
     *
     * @param string $register_url Generated registration URL.
     * @return string
     */
    public function filterRegisterUrl(string $register_url): string
    {
        if (!$this->shouldReplaceAuthUrls()) {
            return $register_url;
        }

        return $this->buildAccountUrl(['register' => 'true']);
    }

    // ------------------------------------------------------------------
    // WP-Admin Access
    // ------------------------------------------------------------------

    /**
     * Restrict wp-admin access for non-allowed roles.
     *
     * @return void
     */
    public function maybeRestrictWpAdmin(): void
    {
        $settings = $this->getSettings();

        if (empty($settings['restrict_wp_admin'])) {
            return;
        }

        // Logged-out visitors should still be able to reach the normal
        // WordPress authentication flow via /wp-admin.
        if (!is_user_logged_in()) {
            return;
        }

        // Always let allowed users through.
        if ($this->isAllowedUser($settings)) {
            return;
        }

        // Never block infrastructure requests.
        if ($this->isInfrastructureRequest()) {
            return;
        }

        $this->redirectUser($settings['wp_admin_redirect'] ?? 'my_account');
    }

    // ------------------------------------------------------------------
    // WP Login Page
    // ------------------------------------------------------------------

    /**
     * Hide the default WordPress login page for non-admin users.
     *
     * Allows password reset and other safe actions through.
     *
     * @return void
     */
    public function maybeHideWpLogin(): void
    {
        $settings = $this->getSettings();

        if (empty($settings['hide_wp_login'])) {
            return;
        }

        // If the user is already logged in and is an allowed user, let them through
        // so admin-capable users can still reach the login form.
        if (is_user_logged_in() && $this->isAllowedUser($settings)) {
            return;
        }

        // Allow safe login actions that should never be blocked.
        if ($this->isSafeLoginAction()) {
            return;
        }

        // Allow admin-targeted login requests so administrators and allowed
        // staff can still sign in through the normal WordPress flow.
        if ($this->isAdminLoginIntent()) {
            return;
        }

        $this->redirectUser($settings['wp_login_redirect'] ?? 'my_account');
    }

    // ------------------------------------------------------------------
    // Helpers
    // ------------------------------------------------------------------

    /**
     * Check if the current user is in the allowed list.
     *
     * Administrators (manage_options) are always allowed.
     *
     * @param array $settings Toolkit settings.
     * @return bool
     */
    private function isAllowedUser(array $settings): bool
    {
        // Logged-out users are never "allowed" in the admin sense.
        if (!is_user_logged_in()) {
            return false;
        }

        // Administrators are always allowed — never lock out site admins.
        if (current_user_can('manage_options')) {
            return true;
        }

        // Multisite super admins are always allowed.
        if (is_multisite() && is_super_admin()) {
            return true;
        }

        $allowed_roles = (array) ($settings['allowed_roles'] ?? []);

        if (empty($allowed_roles)) {
            return false;
        }

        $user = wp_get_current_user();
        $user_roles = (array) $user->roles;

        // Allow if any of the user's roles is in the allowed list.
        return !empty(array_intersect($user_roles, $allowed_roles));
    }

    /**
     * Check if the current request is an infrastructure request
     * that should never be blocked.
     *
     * @return bool
     */
    private function isInfrastructureRequest(): bool
    {
        // AJAX requests.
        if (wp_doing_ajax()) {
            return true;
        }

        // REST API requests.
        if (defined('REST_REQUEST') && REST_REQUEST) {
            return true;
        }

        // WP-Cron requests.
        if (wp_doing_cron()) {
            return true;
        }

        // WP-CLI requests.
        if (defined('WP_CLI') && WP_CLI) {
            return true;
        }

        // Customizer preview.
        if (is_customize_preview()) {
            return true;
        }

        // admin-post.php requests (used by some forms and plugins).
        if ($this->isAdminPostRequest()) {
            return true;
        }

        return false;
    }

    /**
     * Check if the current request targets admin-post.php.
     *
     * @return bool
     */
    private function isAdminPostRequest(): bool
    {
        $script = isset($_SERVER['SCRIPT_FILENAME']) ? basename(sanitize_file_name(wp_unslash($_SERVER['SCRIPT_FILENAME']))) : '';
        return 'admin-post.php' === $script;
    }

    /**
     * Determine whether WordPress auth URLs should be replaced.
     *
     * @return bool
     */
    private function shouldReplaceAuthUrls(): bool
    {
        $settings = $this->getSettings();

        if (empty($settings['hide_wp_login'])) {
            return false;
        }

        if (is_admin() && !wp_doing_ajax()) {
            return false;
        }

        return true;
    }

    /**
     * Check whether a redirect target points to an admin area.
     *
     * @param string $redirect Redirect URL.
     * @return bool
     */
    private function isAdminRedirectTarget(string $redirect): bool
    {
        if (empty($redirect)) {
            return false;
        }

        $admin_urls = array_filter([
            admin_url(),
            user_admin_url(),
            is_multisite() ? network_admin_url() : '',
        ]);

        foreach ($admin_urls as $admin_url) {
            if (0 === strpos($redirect, $admin_url)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if the current login request is a safe action that must be allowed.
     *
     * Password resets, email confirmations, post-password forms, and similar
     * actions must pass through even when the login page is hidden.
     *
     * @return bool
     */
    private function isSafeLoginAction(): bool
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
        $action = isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : '';

        $safe_actions = [
            'lostpassword',
            'retrievepassword',
            'resetpass',
            'rp',
            'confirmaction',
            'confirm_admin_email',
            'postpass',
            'logout',
            'confirm_sso_action',
        ];

        /**
         * Filters the list of safe login actions that bypass the login page redirect.
         *
         * @since 1.5.0
         *
         * @param string[] $safe_actions Action slugs that are allowed through.
         */
        $safe_actions = apply_filters('arraysubs_toolkit_safe_login_actions', $safe_actions);

        if (in_array($action, $safe_actions, true)) {
            return true;
        }

        // Also allow POST requests to lostpassword/resetpass —
        // these submit the password reset form.
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
        $request_action = isset($_REQUEST['action']) ? sanitize_text_field(wp_unslash($_REQUEST['action'])) : '';

        $safe_post_actions = ['lostpassword', 'resetpass', 'rp'];

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- using REQUEST_METHOD constant.
        if (
            isset($_SERVER['REQUEST_METHOD']) && 'POST' === $_SERVER['REQUEST_METHOD']
            && in_array($request_action, $safe_post_actions, true)
        ) {
            return true;
        }

        // Allow the key parameter for password reset links.
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
        if (!empty($_GET['key']) && !empty($_GET['login'])) {
            return true;
        }

        // Allow checkemail action (shows "check your email" notice after password reset request).
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check, no state change.
        if (isset($_GET['checkemail'])) {
            return true;
        }

        return false;
    }

    /**
     * Check whether the current login request is clearly intended for admin access.
     *
     * This keeps the regular WordPress admin login flow available for admins and
     * allowed staff while still hiding the generic login page from member-facing use.
     *
     * @return bool
     */
    private function isAdminLoginIntent(): bool
    {
        $redirect_to = filter_input(INPUT_GET, 'redirect_to', FILTER_SANITIZE_URL);
        $redirect_to = is_string($redirect_to) ? esc_url_raw($redirect_to) : '';

        if ($this->isAdminRedirectTarget($redirect_to)) {
            return true;
        }

        $interim_login = filter_input(INPUT_GET, 'interim-login', FILTER_DEFAULT);
        $reauth = filter_input(INPUT_GET, 'reauth', FILTER_DEFAULT);

        if (null !== $interim_login || null !== $reauth) {
            return true;
        }

        return false;
    }

    /**
     * Redirect the user based on the configured redirect target.
     *
     * @param string $redirect_target Redirect target: 'my_account' or 'not_found'.
     * @return void
     */
    private function redirectUser(string $redirect_target): void
    {
        if ('not_found' === $redirect_target) {
            // Return a proper 404.
            status_header(404);
            nocache_headers();

            // Try to load the theme's 404 template.
            $template = get_404_template();

            if ($template) {
                include $template;
            } else {
                wp_die(
                    esc_html__('Page not found.', 'arraysubs'),
                    esc_html__('Not Found', 'arraysubs'),
                    ['response' => 404]
                );
            }
            exit;
        }

        // Default: redirect to WooCommerce My Account page.
        $myaccount_url = $this->getMyAccountUrl();

        wp_safe_redirect($myaccount_url);
        exit;
    }

    /**
     * Get the WooCommerce My Account page URL.
     *
     * Falls back to home_url() if WooCommerce is not available.
     *
     * @return string
     */
    private function getMyAccountUrl(): string
    {
        if (function_exists('wc_get_page_permalink')) {
            $url = wc_get_page_permalink('myaccount');

            if ($url) {
                return $url;
            }
        }

        return home_url();
    }

    /**
     * Build a WooCommerce account URL with optional query arguments.
     *
     * @param array<string, string> $args Query arguments.
     * @return string
     */
    private function buildAccountUrl(array $args = []): string
    {
        $url = $this->getMyAccountUrl();

        if (empty($args)) {
            return $url;
        }

        return add_query_arg($args, $url);
    }
}
