<?php

declare(strict_types=1);

namespace Instinctor\Frontend;


if (!defined('ABSPATH')) exit;
use Instinctor\Core\DB;
use Instinctor\Core\Session;
use Instinctor\Core\Security;
use Instinctor\Core\UserService;
use Instinctor\Core\Config;

/**
 * PasswordResetController — frontend forgot / reset password flow.
 *
 * Works across both environments:
 *
 * Standalone:
 *   GET  /forgot-password             → email form
 *   POST /forgot-password             → send reset email
 *   GET  /reset-password?token=...    → new password form
 *   POST /reset-password              → set password, redirect to login page
 *   Routes registered in Plugin::init() via Router::add().
 *
 * WordPress:
 *   POST admin-ajax.php?action=instinctor_forgot_password  → send reset email
 *   POST admin-ajax.php?action=instinctor_reset_password   → set new password
 *   AJAX handlers registered via FrontendController.
 *
 * HTML pages rendered here match the admin login shell (ilc-* CSS classes)
 * so the experience is visually consistent. The login page URL is supplied
 * by the caller — for standalone it defaults to /login; for WP it falls back
 * to wp_login_url().
 *
 * @since 3.22.20
 */
class PasswordResetController
{
    // ── Standalone route handlers ─────────────────────────────────────────────

    public static function showForgot(array $params = []): void
    {
        $nonce = Session::token('instinctor_forgot_password');
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only flag
        $sent  = !empty($_GET['sent']);

        if ($sent) {
            self::render('Check your email', self::sentHtml());
            return;
        }

        $error = self::errorFromQuery();
        self::render('Forgot password', self::forgotFormHtml($nonce, $error));
    }

    public static function handleForgot(array $params = []): void
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified via wp_verify_nonce()
        if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_nonce'] ?? '')), 'instinctor_forgot_password')) {
            self::redirect('/forgot-password?error=token');
        }

        $ip = Security::clientIp();
        if (!Security::rateLimit('forgot_password', $ip)) {
            self::redirect('/forgot-password?error=rate');
        }

        $email = trim(sanitize_email(wp_unslash($_POST['email'] ?? '')));

        // requestPasswordReset() is always ok — no email enumeration
        UserService::requestPasswordReset($email, 'frontend');

        self::redirect('/forgot-password?sent=1');
    }

    public static function showReset(array $params = []): void
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- token is the credential
        $token = trim(sanitize_text_field(wp_unslash($_GET['token'] ?? '')));
        $nonce = Session::token('instinctor_reset_password');
        $error = self::errorFromQuery();

        self::render('Reset password', self::resetFormHtml($token, $nonce, $error));
    }

    public static function handleReset(array $params = []): void
    {
        $token    = trim(sanitize_text_field(wp_unslash($_POST['token'] ?? '')));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- password must not be altered by sanitization; it is hashed.
        $password = wp_unslash($_POST['password'] ?? '');
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- password confirmation must not be altered by sanitization.
        $confirm  = wp_unslash($_POST['password_confirm'] ?? '');

        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified via wp_verify_nonce()
        if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_nonce'] ?? '')), 'instinctor_reset_password')) {
            self::redirect('/reset-password?token=' . urlencode($token) . '&error=token');
        }

        if ($password !== $confirm) {
            self::redirect('/reset-password?token=' . urlencode($token) . '&error=mismatch');
        }

        $result = UserService::resetPassword($token, $password);

        if (!$result['ok']) {
            $msg = urlencode($result['error'] ?? 'An error occurred.');
            self::redirect('/reset-password?token=' . urlencode($token) . '&error=msg&msg=' . $msg);
        }

        // Redirect to login page with success notice
        $loginUrl = self::loginUrl() . '?notice=password_reset';
        self::redirect($loginUrl);
    }

    // ── WordPress AJAX handlers ───────────────────────────────────────────────

    /**
     * AJAX: request password reset email.
     * Registered as wp_ajax_nopriv_instinctor_forgot_password (and standalone AjaxDispatcher).
     */
    public static function ajaxForgot(): void
    {
        if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'] ?? '')), 'instinctor_frontend')) {
            wp_send_json_error(['message' => 'Security check failed. Please refresh and try again.']);
        }

        $ip = Security::clientIp();
        if (!Security::rateLimit('forgot_password', $ip)) {
            wp_send_json_error(['message' => 'Too many requests. Please wait a moment.']);
        }

        $email = sanitize_email(wp_unslash($_POST['email'] ?? ''));
        UserService::requestPasswordReset($email, 'frontend');

        wp_send_json_success(['message' => 'If that address is on file, a reset link is on its way.']);
    }

    /**
     * AJAX: complete password reset.
     * WordPress storefront: NOT used — WP customers follow the native wp-login.php reset flow.
     * Standalone: validates token against instinctor_users and sets new password.
     */
    public static function ajaxReset(): void
    {
        if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'] ?? '')), 'instinctor_frontend')) {
            wp_send_json_error(['message' => 'Security check failed. Please refresh and try again.']);
        }

        // On WordPress, storefront password resets go through wp-login.php (native WP flow).
        // This AJAX handler is only reachable on standalone.
        $isWP = !defined('INSTINCTOR_ENV') || INSTINCTOR_ENV === 'wordpress';
        if ($isWP) {
            wp_send_json_error(['message' => 'Please use the link in your reset email.']);
        }

        $token    = trim(sanitize_text_field(wp_unslash($_POST['token'] ?? '')));
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- password must not be altered by sanitization; it is hashed.
        $password = wp_unslash($_POST['password'] ?? '');
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- password confirmation must not be altered by sanitization.
        $confirm  = wp_unslash($_POST['password_confirm'] ?? '');

        if ($password !== $confirm) {
            wp_send_json_error(['message' => 'Passwords do not match.']);
        }

        $result = UserService::resetPassword($token, $password);

        if (!$result['ok']) {
            wp_send_json_error(['message' => $result['error'] ?? 'An error occurred.']);
        }

        wp_send_json_success(['message' => 'Password updated. You can now log in.']);
    }

    // ── HTML fragments ────────────────────────────────────────────────────────

    private static function forgotFormHtml(string $nonce, string $error): string
    {
        $loginUrl = htmlspecialchars(self::loginUrl(), ENT_QUOTES);
        $e = htmlspecialchars($nonce, ENT_QUOTES);
        return "
            <h1 class=\"ilc-heading\">Forgot password</h1>
            <p class=\"ilc-sub\">Enter your email and we'll send you a reset link.</p>
            {$error}
            <form method=\"post\" action=\"/forgot-password\">
                <input type=\"hidden\" name=\"_nonce\" value=\"{$e}\">
                <div class=\"ilc-field\"><label>Email *</label>
                    <input type=\"email\" name=\"email\" required autocomplete=\"email\" autofocus></div>
                <button type=\"submit\" class=\"ilc-btn\">Send reset link</button>
            </form>
            <a href=\"{$loginUrl}\" class=\"ilc-link\">← Back to sign in</a>";
    }

    private static function sentHtml(): string
    {
        $loginUrl = htmlspecialchars(self::loginUrl(), ENT_QUOTES);
        return "
            <h1 class=\"ilc-heading\">Check your email</h1>
            <p class=\"ilc-sub\">If that address is on file, you'll receive a reset link shortly. Check your spam folder if it doesn't arrive within a few minutes.</p>
            <a href=\"{$loginUrl}\" class=\"ilc-link\">← Back to sign in</a>";
    }

    private static function resetFormHtml(string $token, string $nonce, string $error): string
    {
        $t = htmlspecialchars($token, ENT_QUOTES);
        $n = htmlspecialchars($nonce, ENT_QUOTES);
        return "
            <h1 class=\"ilc-heading\">Reset password</h1>
            <p class=\"ilc-sub\">Choose a new password for your account.</p>
            {$error}
            <form method=\"post\" action=\"/reset-password\">
                <input type=\"hidden\" name=\"_nonce\" value=\"{$n}\">
                <input type=\"hidden\" name=\"token\" value=\"{$t}\">
                <div class=\"ilc-field\"><label>New password *</label>
                    <input type=\"password\" name=\"password\" required autocomplete=\"new-password\"
                           minlength=\"8\" autofocus placeholder=\"At least 8 characters\"></div>
                <div class=\"ilc-field\"><label>Confirm password *</label>
                    <input type=\"password\" name=\"password_confirm\" required
                           autocomplete=\"new-password\"></div>
                <button type=\"submit\" class=\"ilc-btn\">Set new password</button>
            </form>";
    }

    private static function errorFromQuery(): string
    {
        // phpcs:ignore WordPress.Security.NonceVerification -- display-only error label read from the redirect query string.
        $error = sanitize_text_field(wp_unslash($_GET['error'] ?? ''));
        if (!$error) return '';

        $map = [
            'token'    => 'Security token expired, please try again.',
            'mismatch' => 'Passwords do not match.',
            'rate'     => 'Too many requests. Please wait a moment.',
            // phpcs:ignore WordPress.Security.NonceVerification -- display-only message read from the redirect query string, escaped on output.
            'msg'      => htmlspecialchars(sanitize_text_field(wp_unslash($_GET['msg'] ?? 'An error occurred.')), ENT_QUOTES, 'UTF-8'),
        ];

        $text = $map[$error] ?? 'An error occurred.';
        return "<div class=\"ilc-error\">{$text}</div>";
    }

    // ── Rendering ─────────────────────────────────────────────────────────────

    private static function render(string $title, string $body): never
    {
        $store = Config::get('instinctor_store_name', 'Instinctor');
        // Title and store name are real-escaped with esc_html() at output.
        echo '<!DOCTYPE html><html lang="en"><head>'
            . '<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">'
            . '<title>' . esc_html($title . ', ' . $store) . '</title>'
            // phpcs:disable WordPress.Security.EscapeOutput -- self::css() is a hardcoded static stylesheet (no dynamic data); esc_html would corrupt its quotes/selectors
            . '<style>' . self::css() . '</style>'
            // phpcs:enable WordPress.Security.EscapeOutput
            . '</head><body class="ilc-page">'
            // phpcs:disable WordPress.Security.EscapeOutput -- self::logo() is a hardcoded inline SVG (static, no dynamic data)
            . '<div class="ilc-wrap"><div class="ilc-logo">' . self::logo()
            // phpcs:enable WordPress.Security.EscapeOutput
            . '<span>' . esc_html($store) . '</span></div>'
            // phpcs:disable WordPress.Security.EscapeOutput -- $body is caller-composed markup whose dynamic values are already escaped by the *FormHtml() builders; wp_kses_post would strip its <form>/<input>
            . $body
            // phpcs:enable WordPress.Security.EscapeOutput
            . '</div></body></html>';
        exit;
    }

    private static function logo(): string
    {
        return '<svg viewBox="0 0 24 24" width="32" height="32" xmlns="http://www.w3.org/2000/svg" color="#1d2327"><g transform="scale(2.288)" fill="currentColor" stroke="none"><path d="M3.02 0.02l2.23 0c1.44,0 2.75,0.59 3.7,1.54 0.95,0.95 1.54,2.26 1.54,3.7 0,1.44 -0.59,2.75 -1.54,3.7 -0.95,0.95 -2.26,1.54 -3.7,1.54 -1.44,0 -2.75,-0.59 -3.7,-1.54 -0.95,-0.95 -1.54,-2.26 -1.54,-3.7l0 -2.23 2.31 0 0 2.23c0,0.8 0.33,1.53 0.86,2.06 0.53,0.53 1.26,0.86 2.06,0.86 0.8,0 1.53,-0.33 2.06,-0.86 0.53,-0.53 0.86,-1.26 0.86,-2.06 0,-0.8 -0.33,-1.53 -0.86,-2.06 -0.53,-0.53 -1.26,-0.86 -2.06,-0.86l-2.23 0 0 -2.31z"/><rect x="0.02" y="0.02" width="2.31" height="2.31"/></g></svg>';
    }

    private static function css(): string
    {
        return '
*{box-sizing:border-box;margin:0;padding:0}
body.ilc-page{min-height:100vh;background:#f5f5f5;display:flex;align-items:center;
    justify-content:center;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
    font-size:13px;color:#1d2327}
.ilc-wrap{width:360px;background:#fff;border:1px solid #e0e0e0;border-radius:4px;padding:32px}
.ilc-logo{display:flex;align-items:center;gap:10px;margin-bottom:24px;font-size:17px;
    font-weight:600;color:#1d2327}
.ilc-heading{font-size:16px;font-weight:600;margin-bottom:6px}
.ilc-sub{font-size:12px;color:#888;margin-bottom:20px;line-height:1.5}
.ilc-error{padding:8px 12px;background:#fff5f5;border:1px solid #f5c0c0;border-radius:3px;
    color:#c0392b;font-size:12px;margin-bottom:16px}
.ilc-field{margin-bottom:14px}
.ilc-field label{display:block;font-size:11px;color:#999;text-transform:uppercase;
    letter-spacing:.04em;margin-bottom:5px}
.ilc-field input{width:100%;padding:7px 10px;font-size:12px;border:1px solid #e0e0e0;
    border-radius:3px;background:#fff;color:#1d2327;outline:none;transition:border-color .15s}
.ilc-field input:focus{border-color:#555}
.ilc-btn{width:100%;padding:8px;background:#1d2327;color:#fff;border:none;border-radius:3px;
    font-size:12px;font-weight:500;cursor:pointer;transition:background .15s;margin-top:4px}
.ilc-btn:hover{background:#555}
.ilc-link{display:inline-block;margin-top:16px;font-size:12px;color:#888;text-decoration:none}
.ilc-link:hover{color:#1d2327}
        ';
    }

    private static function loginUrl(): string
    {
        $isWP = !defined('INSTINCTOR_ENV') || INSTINCTOR_ENV === 'wordpress';
        if ($isWP && function_exists('wp_login_url')) {
            return wp_login_url();
        }
        $base = defined('INSTINCTOR_STANDALONE_URL') ? rtrim(INSTINCTOR_STANDALONE_URL, '/') : '';
        return $base . '/login';
    }

    private static function redirect(string $url): never
    {
        header('Location: ' . $url, true, 302);
        exit;
    }

    // ── WordPress template_redirect intercept ─────────────────────────────────

    /**
     * Hooked to template_redirect on WordPress.
     * Previously intercepted ?instinctor_action=reset_password — now a no-op on WP
     * because storefront password resets route through WP native wp-login.php.
     * Kept as a hook point for future use (e.g. custom login page redirection).
     */
    public static function maybeHandleWPReset(): void
    {
        // Storefront resets on WP use the native wp-login.php?action=rp flow.
        // Nothing to intercept here.
        $action = sanitize_text_field(wp_unslash($_GET['instinctor_action'] ?? ''));
        if ($action !== 'reset_password') {
            return;
        }
        // Guard: only handle on standalone (should not be reachable on WP any more)
        $isWP = !defined('INSTINCTOR_ENV') || INSTINCTOR_ENV === 'wordpress';
        if ($isWP) {
            return;
        }

        $token  = trim(sanitize_text_field(wp_unslash($_GET['token'] ?? '')));
        $method = strtoupper(sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'] ?? 'GET')));

        if ($method === 'POST') {
            $postedToken = trim(sanitize_text_field(wp_unslash($_POST['token'] ?? '')));
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- password must not be altered by sanitization; it is hashed.
            $password    = wp_unslash($_POST['password'] ?? '');
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- password confirmation must not be altered by sanitization.
            $confirm     = wp_unslash($_POST['password_confirm'] ?? '');
            $nonce       = sanitize_text_field(wp_unslash($_POST['_nonce'] ?? ''));

            if (!wp_verify_nonce($nonce, 'instinctor_reset_password')) {
                $url = add_query_arg(['instinctor_action' => 'reset_password', 'token' => $postedToken, 'error' => 'token'], home_url('/'));
                wp_safe_redirect($url); exit;
            }

            if ($password !== $confirm) {
                $url = add_query_arg(['instinctor_action' => 'reset_password', 'token' => $postedToken, 'error' => 'mismatch'], home_url('/'));
                wp_safe_redirect($url); exit;
            }

            $result = UserService::resetPassword($postedToken, $password);

            if (!$result['ok']) {
                $url = add_query_arg(['instinctor_action' => 'reset_password', 'token' => $postedToken, 'error' => 'msg', 'msg' => urlencode($result['error'] ?? '')], home_url('/'));
                wp_safe_redirect($url); exit;
            }

            // Success — redirect to login with notice
            $loginUrl = function_exists('wp_login_url') ? wp_login_url() : home_url('/login');
            wp_safe_redirect(add_query_arg('notice', 'password_reset', $loginUrl)); exit;
        }

        // GET — show the reset form
        $formAction = add_query_arg('instinctor_action', 'reset_password', home_url('/'));
        $nonce      = Session::token('instinctor_reset_password');
        $error      = self::errorFromQuery();

        self::render('Reset password', self::resetFormHtmlWP($token, $nonce, $error, $formAction));
    }

    private static function resetFormHtmlWP(string $token, string $nonce, string $error, string $action): string
    {
        $t = htmlspecialchars($token, ENT_QUOTES);
        $n = htmlspecialchars($nonce, ENT_QUOTES);
        $a = htmlspecialchars($action, ENT_QUOTES);
        return "
            <h1 class=\"ilc-heading\">Reset password</h1>
            <p class=\"ilc-sub\">Choose a new password for your account.</p>
            {$error}
            <form method=\"post\" action=\"{$a}\">
                <input type=\"hidden\" name=\"_nonce\" value=\"{$n}\">
                <input type=\"hidden\" name=\"token\" value=\"{$t}\">
                <div class=\"ilc-field\"><label>New password *</label>
                    <input type=\"password\" name=\"password\" required autocomplete=\"new-password\"
                           minlength=\"8\" autofocus placeholder=\"At least 8 characters\"></div>
                <div class=\"ilc-field\"><label>Confirm password *</label>
                    <input type=\"password\" name=\"password_confirm\" required
                           autocomplete=\"new-password\"></div>
                <button type=\"submit\" class=\"ilc-btn\">Set new password</button>
            </form>";
    }


}
