<?php
/**
 * Handles the Custom Login URL feature for Guard Dog.
 *
 * @package GuardDog
 * @since 1.1.0
 */

if (!defined('ABSPATH')) exit; // Exit if accessed directly

class Guard_Dog_Login_Url {

    /**
     * Option key for the login slug.
     * @var string
     */
    const OPTION_NAME = 'guard_dog_login_slug';

    /**
     * The stored custom login slug.
     * @var string|false
     */
    private $login_slug = false;

    /**
     * Constructor. Adds hooks.
     *
     * @since 1.1.0
     */
    public function __construct() {
        $this->login_slug = get_option(self::OPTION_NAME);
        $this->add_hooks();
    }

    /**
     * Adds the necessary WordPress hooks.
     *
     * @since 1.1.0
     */
    public function add_hooks() {
        // Don't add hooks if slug is empty
        if (empty($this->login_slug)) {
            return;
        }

        // Use direct URL interception (like All In One Login)
        // This works for all server types without rewrite rules
        add_action('plugins_loaded', [$this, 'plugins_loaded'], 1);
        add_action('wp_loaded', [$this, 'wp_loaded']);

        // Login URL filtering
        add_filter('login_redirect', [$this, 'prevent_admin_redirect_on_login_page'], 10, 3);
        add_filter('login_url', [$this, 'filter_login_url'], 10, 3);
        add_filter('site_url', [$this, 'filter_site_url_for_login'], 10, 4);
        add_filter('network_site_url', [$this, 'filter_network_site_url_for_login'], 10, 3);
        add_filter('register_url', [$this, 'filter_register_url'], 10, 1);
        add_filter('lostpassword_url', [$this, 'filter_lostpassword_url'], 10, 2);
        
        // Fix password reset redirects to use custom login URL
        add_action('login_init', [$this, 'fix_password_reset_redirects'], 1);
    }

    /**
     * Plugins loaded hook - removes default WordPress admin redirect.
     *
     * @since 1.1.0
     */
    public function plugins_loaded() {
        // Remove the default wp_redirect_admin_locations action
        remove_action('template_redirect', 'wp_redirect_admin_locations', 1000);
    }

    /**
     * Main wp_loaded hook - handles all custom login URL logic.
     * Based on All In One Login plugin approach.
     *
     * @since 1.1.0
     */
    public function wp_loaded() {
        global $pagenow;

        // Handle admin access for non-logged-in users (redirect to 404)
        // But allow admin-post.php and admin-ajax.php as they handle both authenticated and unauthenticated requests
        if (is_admin() && !is_user_logged_in() && !defined('DOING_AJAX')) {
            // Get the current request URI to check if it's admin-post.php or admin-ajax.php
            $request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
            $is_admin_post = (strpos($request_uri, '/wp-admin/admin-post.php') !== false);
            $is_admin_ajax = (strpos($request_uri, '/wp-admin/admin-ajax.php') !== false);
            
            // Allow admin-post.php and admin-ajax.php to proceed
            if (!$is_admin_post && !$is_admin_ajax) {
                wp_safe_redirect(home_url('/404'));
                exit;
            }
        }

        // Check if this is a request for our custom login URL
        if (isset($_SERVER['REQUEST_URI'])) {
            $request_uri_raw = (string) wp_unslash($_SERVER['REQUEST_URI']);
            $request_uri = wp_parse_url($request_uri_raw);
            $request_path = $request_uri['path'] ?? '';
            
            // Normalize the path
            $request_path = trim(sanitize_text_field(rawurldecode($request_path)), '/');
            $site_path = trim(wp_parse_url(site_url(), PHP_URL_PATH) ?? '', '/');
            
            // Calculate the expected custom login path
            $expected_path = $site_path ? $site_path . '/' . $this->login_slug : $this->login_slug;
            $expected_path = trim($expected_path, '/');

            // Check if this request matches our custom login URL
            if ($request_path === $expected_path) {
                $canonical_login_url = home_url($this->login_slug);
                if (!empty($request_uri['query'])) {
                    $canonical_login_url .= '?' . $request_uri['query'];
                }

                $request_scheme = is_ssl() ? 'https' : 'http';
                $request_host = '';
                $request_port = '';
                if (isset($_SERVER['HTTP_HOST'])) {
                    $request_origin = $request_scheme . '://' . sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST']));
                    $request_host = (string) wp_parse_url($request_origin, PHP_URL_HOST);
                    $request_port = (string) (wp_parse_url($request_origin, PHP_URL_PORT) ?? '');
                }
                $canonical_scheme = (string) wp_parse_url($canonical_login_url, PHP_URL_SCHEME);
                $canonical_host = (string) wp_parse_url($canonical_login_url, PHP_URL_HOST);
                $canonical_port = (string) (wp_parse_url($canonical_login_url, PHP_URL_PORT) ?? '');

                if (
                    $request_host !== '' &&
                    (
                        strcasecmp($request_host, $canonical_host) !== 0 ||
                        $request_scheme !== $canonical_scheme ||
                        $request_port !== $canonical_port
                    )
                ) {
                    Guard_Dog()->debug->login_url('Redirecting custom login URL to canonical host', [
                        'request_host' => $request_host,
                        'canonical_host' => $canonical_host,
                        'request_port' => $request_port,
                        'canonical_port' => $canonical_port,
                        'request_scheme' => $request_scheme,
                        'canonical_scheme' => $canonical_scheme,
                        'redirect_to' => $canonical_login_url,
                    ]);
                    wp_safe_redirect($canonical_login_url);
                    exit;
                }

                // Load wp-login.php directly (like All In One Login does)
                // If checkemail parameter is present, ensure it's set in $_GET for wp-login.php to process
                Guard_Dog()->debug->login_url('Custom login URL accessed, loading wp-login.php', [
                    'request_path' => $request_path,
                    'expected_path' => $expected_path,
                    'custom_slug' => $this->login_slug,
                    'checkemail' => isset($_GET['checkemail']) ? sanitize_text_field(wp_unslash($_GET['checkemail'])) : 'none'
                ]);
                global $error, $interim_login, $action, $user_login;
                require_once ABSPATH . 'wp-login.php';
                exit;
            }
            
            // Check if this is a direct access to wp-login.php that should be blocked
            $wp_login_path = $site_path ? $site_path . '/wp-login.php' : 'wp-login.php';
            $wp_login_path = trim($wp_login_path, '/');
            
            if ($request_path === $wp_login_path) {
                $allow_wp_login_action = false;
                
                // Check for action parameter
                if (isset($_GET['action'])) {
                    $action = sanitize_text_field(wp_unslash($_GET['action']));
                    $allowed_actions = ['logout', 'postpass', 'rp', 'retrievepassword', 'resetpass', 'confirm_admin_email', 'register', 'lostpassword', 'checkemail'];
                    $allow_wp_login_action = in_array($action, $allowed_actions, true);
                }
                
                // Also allow checkemail parameter (WordPress registration success redirect)
                // Instead of loading wp-login.php directly, redirect to custom login URL with checkemail param
                if (!$allow_wp_login_action && isset($_GET['checkemail'])) {
                    $checkemail_value = sanitize_text_field(wp_unslash($_GET['checkemail']));
                    $custom_login_url = home_url($this->login_slug);
                    $custom_login_url = add_query_arg('checkemail', $checkemail_value, $custom_login_url);
                    
                    Guard_Dog()->debug->login_url('Redirecting checkemail to custom login URL', [
                        'request_path' => $request_path,
                        'checkemail' => $checkemail_value,
                        'custom_url' => $custom_login_url
                    ]);
                    
                    wp_safe_redirect($custom_login_url);
                    exit;
                }
                
                $is_post_request = (isset($_SERVER['REQUEST_METHOD']) && sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'])) === 'POST');

                if (!$allow_wp_login_action && !$is_post_request) {
                    // Block direct access to wp-login.php - redirect to 404
                    Guard_Dog()->debug->login_url('Blocking direct access to wp-login.php, redirecting to 404', [
                        'request_path' => $request_path,
                        'wp_login_path' => $wp_login_path,
                        'allow_wp_login_action' => $allow_wp_login_action,
                        'is_post_request' => $is_post_request
                    ]);
                    wp_safe_redirect(home_url('/404'));
                    exit;
                }

                Guard_Dog()->debug->login_url('Allowing wp-login.php access (allowed action or POST)', [
                    'request_path' => $request_path,
                    'wp_login_path' => $wp_login_path,
                    'allow_wp_login_action' => $allow_wp_login_action,
                    'is_post_request' => $is_post_request,
                    'action' => isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : 'none'
                ]);
                global $error, $interim_login, $action, $user_login;
                require_once ABSPATH . 'wp-login.php';
                exit;
            }
        }
    }

    /**
     * Prevents redirecting logged-in users from the login page to /wp-admin/.
     *
     * @since 1.0.1 (Moved to class 1.1.0)
     *
     * @param string   $redirect_to           The redirect destination URL.
     * @param string   $requested_redirect_to The requested redirect destination URL passed as a parameter.
     * @param WP_User|WP_Error $user          WP_User object if login was successful, WP_Error object otherwise.
     * @return string The redirect destination URL.
     */
    public function prevent_admin_redirect_on_login_page($redirect_to, $requested_redirect_to, $user) {
        // If this is the result of a login form submission (POST), always allow the intended redirect.
        if (isset($_SERVER['REQUEST_METHOD']) && sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'])) === 'POST') {
            return $redirect_to;
        }

        // Otherwise (it's a GET request), check if a logged-in user is visiting the custom login page.
        $current_slug = get_option(self::OPTION_NAME);
        if (empty($current_slug)) return $redirect_to; // Bail if no slug set

        global $pagenow;
        // Check if the current request is for our custom login slug page
        $request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
        $request_path = trim(wp_parse_url($request_uri, PHP_URL_PATH) ?: '', '/');
        $site_path = trim(wp_parse_url(site_url(), PHP_URL_PATH) ?: '', '/');
        $custom_login_path = $site_path ? $site_path . '/' . $current_slug : $current_slug;

        if ($pagenow === 'wp-login.php' && $request_path === $custom_login_path && isset($user->ID) && $user->ID > 0) {
            // Let logged-in users stay on the custom login page (e.g., to see logout link)
            return '';
        }

        // For all other cases (including GET requests not matching the above), allow the default redirect.
        return $redirect_to;
    }

    /**
     * Filters the login URL to use the custom slug.
     *
     * @since 1.0.1 (Moved to class 1.1.0)
     *
     * @param string $login_url    The login URL.
     * @param string $redirect     The path to redirect to on login, if supplied.
     * @param bool   $force_reauth Whether to force reauthorization, if supplied.
     * @return string The filtered login URL.
     */
    public function filter_login_url($login_url, $redirect, $force_reauth) {
        $current_slug = get_option(self::OPTION_NAME);
        if (empty($current_slug)) return $login_url;

        // Avoid loops if we are already building our custom url
        if (strpos($login_url, $current_slug) !== false) {
            return $login_url;
        }

        $custom_login_url = home_url($current_slug);

        if (!empty($redirect)) {
            $custom_login_url = add_query_arg('redirect_to', urlencode($redirect), $custom_login_url);
        }
        if ($force_reauth) {
            $custom_login_url = add_query_arg('reauth', '1', $custom_login_url);
        }

        $parsed_original_url = wp_parse_url($login_url);
        if (isset($parsed_original_url['query'])) {
            parse_str($parsed_original_url['query'], $query_params);
             // Preserve common action parameters including login for password reset
            $preserve_actions = ['action', 'key', 'login', 'error', 'checkemail', 'wp_lang'];
            foreach ($preserve_actions as $action_param) {
                if (isset($query_params[$action_param])) {
                    $custom_login_url = add_query_arg($action_param, $query_params[$action_param], $custom_login_url);
                }
            }
        }

        return $custom_login_url;
    }

    /**
     * Filters site_url when it's for the login page.
     *
     * @since 1.0.1 (Moved to class 1.1.0)
     *
     * @param string      $url     The complete site URL including scheme and path.
     * @param string      $path    Path relative to the site URL.
     * @param string|null $scheme  Scheme to give the site URL context.
     * @param int|null    $blog_id Blog ID, or null for the current blog.
     * @return string The filtered site URL.
     */
    public function filter_site_url_for_login($url, $path, $scheme, $blog_id) {
        if ($path === 'wp-login.php' || strpos($path, 'wp-login.php') === 0) {
            return $this->filter_login_url($url, '', false);
        }
        return $url;
    }

    /**
     * Filters network_site_url when it's for the login page.
     *
     * WordPress uses network_site_url() for password reset email links,
     * so we need to filter this as well.
     *
     * @since 1.9.0
     *
     * @param string      $url    The complete network site URL including scheme and path.
     * @param string      $path   Path relative to the network site URL.
     * @param string|null $scheme Scheme to give the site URL context.
     * @return string The filtered network site URL.
     */
    public function filter_network_site_url_for_login($url, $path, $scheme) {
        if ($path === 'wp-login.php' || strpos($path, 'wp-login.php') === 0) {
            return $this->filter_login_url($url, '', false);
        }
        return $url;
    }

    /**
     * Filters the registration URL to use the custom slug.
     *
     * @since 1.0.1 (Moved to class 1.1.0)
     *
     * @param string $register_url The registration URL.
     * @return string The filtered registration URL.
     */
    public function filter_register_url($register_url) {
        $current_slug = get_option(self::OPTION_NAME);
        if (empty($current_slug)) return $register_url;

        // Replace wp-login.php with our custom slug
        $custom_register_url = str_replace('wp-login.php', $current_slug, $register_url);
        
        return $custom_register_url;
    }

    /**
     * Filters the lost password URL to use the custom slug.
     *
     * @since 1.0.1 (Moved to class 1.1.0)
     *
     * @param string $lostpassword_url The lost password URL.
     * @param string $redirect         The path to redirect to on login.
     * @return string The filtered lost password URL.
     */
    public function filter_lostpassword_url($lostpassword_url, $redirect) {
        $current_slug = get_option(self::OPTION_NAME);
        if (empty($current_slug)) return $lostpassword_url;

        // Replace wp-login.php with our custom slug
        $custom_lostpassword_url = str_replace('wp-login.php', $current_slug, $lostpassword_url);
        
        return $custom_lostpassword_url;
    }

    /**
     * Fix password reset redirects to use custom login URL.
     *
     * WordPress core redirects to wp-login.php on password reset errors,
     * but we need to redirect to the custom login URL instead.
     *
     * @since 1.9.0
     * @return void
     */
    public function fix_password_reset_redirects() {
        // Only process if we're on a password reset action
        // phpcs:disable WordPress.Security.NonceVerification.Recommended
        // Nonce verification is not required here as we're only reading GET parameters
        // to determine which action to handle, not processing form data.
        $action = isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : '';
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
        
        if ($action !== 'rp' && $action !== 'resetpass') {
            return;
        }

        // Check if we have key and login parameters
        // phpcs:disable WordPress.Security.NonceVerification.Recommended
        // Nonce verification is not required here as we're only reading GET parameters
        // to determine if password reset parameters are present, not processing form data.
        $has_key = isset($_GET['key']);
        $has_login = isset($_GET['login']);
        // phpcs:enable WordPress.Security.NonceVerification.Recommended

        // If we have both key and login, WordPress will set a cookie and redirect
        // The redirect should go to our custom login URL, not wp-login.php
        // We'll let WordPress handle the cookie setting, but we need to ensure
        // the redirect after cookie setting goes to custom URL
        
        // If we're missing the login parameter but have a key, that's the problem
        // WordPress requires both for validation
        if ($has_key && !$has_login) {
            // Redirect to lost password page with error
            $custom_login_url = home_url($this->login_slug);
            $custom_login_url = add_query_arg([
                'action' => 'lostpassword',
                'error' => 'invalidkey'
            ], $custom_login_url);
            
            wp_safe_redirect($custom_login_url);
            exit;
        }
    }
}
