<?php

namespace SoftminalB2B\Registration;

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

use SoftminalB2B\Services\Settings;

/**
 * The B2B signup form (FEATURES C1), available as a shortcode and a block.
 *
 * The business fields are part of the free version deliberately: a wholesale
 * signup that cannot capture a company name or tax ID is the top complaint
 * against B2BKing's free tier.
 */
class RegistrationForm
{
    public const SHORTCODE = 'smb2b_registration';
    private const NONCE    = 'smb2b_register';

    /** Hidden field a human never fills in, and the time the form was built. */
    private const HONEYPOT   = 'smb2b_website_url';
    private const TIMESTAMP  = 'smb2b_form_time';
    private const MIN_SECONDS = 3;

    private static bool $registered = false;

    /** @var array<int, string> */
    private static array $errors = [];

    private static bool $submitted = false;

    public static function init(): void
    {
        if (self::$registered) {
            return;
        }

        self::$registered = true;

        // Registration is delivered via the [smb2b_registration] shortcode only.
        // No editor block: most stores build signup with a dedicated form plugin,
        // so a block with nothing to configure would just be noise.
        add_shortcode(self::SHORTCODE, [self::class, 'render']);

        // Handled on `template_redirect` so a successful signup can redirect
        // before any output is sent.
        add_action('template_redirect', [self::class, 'handleSubmission']);
    }

    public static function handleSubmission(): void
    {
        if (empty($_POST['smb2b_register_submit'])) {
            return;
        }

        self::$submitted = true;

        $nonce = isset($_POST['_wpnonce']) ? sanitize_text_field(wp_unslash($_POST['_wpnonce'])) : '';

        if (!wp_verify_nonce($nonce, self::NONCE)) {
            self::$errors[] = __('That form has expired. Please try again.', 'softminal-b2b-wholesale-for-woocommerce');
            return;
        }

        if (Settings::get('registration_enabled') !== 'yes') {
            self::$errors[] = __('Trade account applications are closed at the moment.', 'softminal-b2b-wholesale-for-woocommerce');
            return;
        }

        if (is_user_logged_in()) {
            self::$errors[] = __('You are already signed in.', 'softminal-b2b-wholesale-for-woocommerce');
            return;
        }

        if (!self::passesSpamChecks()) {
            // Deliberately vague: telling a bot which check it failed helps it.
            self::$errors[] = __('We could not process that form. Please try again.', 'softminal-b2b-wholesale-for-woocommerce');
            return;
        }

        $email = isset($_POST['smb2b_email']) ? sanitize_email(wp_unslash($_POST['smb2b_email'])) : '';
        $first = isset($_POST['smb2b_first_name']) ? sanitize_text_field(wp_unslash($_POST['smb2b_first_name'])) : '';
        $last  = isset($_POST['smb2b_last_name']) ? sanitize_text_field(wp_unslash($_POST['smb2b_last_name'])) : '';

        if (!is_email($email)) {
            self::$errors[] = __('Please enter a valid email address.', 'softminal-b2b-wholesale-for-woocommerce');
        } elseif (email_exists($email)) {
            self::$errors[] = __('An account already exists with that email address.', 'softminal-b2b-wholesale-for-woocommerce');
        }

        if ($first === '') {
            self::$errors[] = __('Please enter your first name.', 'softminal-b2b-wholesale-for-woocommerce');
        }

        $business = [];

        foreach (Applicant::fields() as $key => $field) {
            // Nonce is verified in handle() before this runs; each value is
            // sanitized on the next lines by its field type.
            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
            $raw = isset($_POST['smb2b_' . $key]) ? wp_unslash($_POST['smb2b_' . $key]) : '';

            $value = $field['type'] === 'textarea'
                ? sanitize_textarea_field($raw)
                : sanitize_text_field($raw);

            if ($field['required'] && $value === '') {
                /* translators: %s: field label */
                self::$errors[] = sprintf(__('Please enter your %s.', 'softminal-b2b-wholesale-for-woocommerce'), strtolower($field['label']));
            }

            $business[$key] = $value;
        }

        if (!empty(self::$errors)) {
            return;
        }

        $userId = wp_insert_user([
            'user_login' => self::uniqueLogin($email),
            'user_email' => $email,
            'user_pass'  => wp_generate_password(20),
            'first_name' => $first,
            'last_name'  => $last,
            'role'       => 'customer',
        ]);

        if (is_wp_error($userId)) {
            self::$errors[] = $userId->get_error_message();
            return;
        }

        Applicant::markPending((int) $userId, $business);

        if (Settings::get('registration_auto_approve') === 'yes') {
            // Merchant chose to skip manual review: approve immediately onto the
            // configured default group (retail if none). approve() fires
            // `smb2b_registration_approved`, so the applicant gets the approval
            // email with a set-password link instead of a "we'll review it" one.
            $defaultGroup = (int) Settings::get('registration_default_group');

            Applicant::approve((int) $userId, $defaultGroup > 0 ? $defaultGroup : null);
        } else {
            // The applicant has no password yet; the welcome email carries a reset
            // link so approval and password setup are one step for them.
            do_action('smb2b_registration_submitted', (int) $userId, $business);
        }

        // Redirect back to the page the form is on so its success notice shows.
        // The form self-posts, so wp_get_referer() returns false (WordPress
        // guards against a referer equal to the current request), which would
        // otherwise dump the applicant on the home page — where the shortcode,
        // and therefore the confirmation, does not exist. The queried page is
        // resolved by this point (template_redirect), and covers both the
        // shortcode and the block.
        $pageId = get_queried_object_id();
        $base   = $pageId ? get_permalink($pageId) : '';

        if (!$base) {
            $base = wp_get_referer() ?: home_url('/');
        }

        $redirect = add_query_arg('smb2b_registered', '1', $base);

        wp_safe_redirect($redirect);
        exit;
    }

    /**
     * Honeypot plus a minimum fill time. Both are silent to real users and
     * cost nothing; a captcha is offered as an option rather than a default
     * because it taxes every genuine applicant.
     */
    private static function passesSpamChecks(): bool
    {
        // Reached only after handle() has verified the nonce; these are spam
        // signals, cast/compared directly.
        // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
        $honeypot = isset($_POST[self::HONEYPOT]) ? trim((string) wp_unslash($_POST[self::HONEYPOT])) : '';

        if ($honeypot !== '') {
            return false;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $started = isset($_POST[self::TIMESTAMP]) ? (int) $_POST[self::TIMESTAMP] : 0;

        if ($started <= 0 || (time() - $started) < self::MIN_SECONDS) {
            return false;
        }

        /**
         * Lets a site add a captcha or another check without touching this
         * class.
         *
         * @param bool $passes
         */
        return (bool) apply_filters('smb2b_registration_spam_check', true);
    }

    private static function uniqueLogin(string $email): string
    {
        $base  = sanitize_user(current(explode('@', $email)), true) ?: 'b2b';
        $login = $base;
        $i     = 2;

        while (username_exists($login)) {
            $login = $base . $i;
            $i++;
        }

        return $login;
    }

    /**
     * @param array<string, mixed> $atts
     */
    public static function render($atts = []): string
    {
        if (is_user_logged_in()) {
            $userId = get_current_user_id();

            if (Applicant::isPending($userId)) {
                return self::notice(
                    __('Your trade account application is with us and we will email you when it is reviewed.', 'softminal-b2b-wholesale-for-woocommerce'),
                    'info'
                );
            }

            return self::notice(__('You are already signed in.', 'softminal-b2b-wholesale-for-woocommerce'), 'info');
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display flag after redirect.
        if (!self::$submitted && isset($_GET['smb2b_registered'])) {
            return self::notice(
                __('Thanks — your application is in. We will email you once it has been reviewed.', 'softminal-b2b-wholesale-for-woocommerce'),
                'success'
            );
        }

        if (Settings::get('registration_enabled') !== 'yes') {
            return self::notice(__('Trade account applications are closed at the moment.', 'softminal-b2b-wholesale-for-woocommerce'), 'info');
        }

        self::enqueueStyles();

        ob_start();
        include __DIR__ . '/views/form.php';

        return (string) ob_get_clean();
    }

    /**
     * The form's minimal, theme-agnostic styling. Registered as a src-less
     * handle and attached as inline CSS, so it is enqueued the WordPress way
     * rather than echoed in a <style> tag. Only loaded when the form renders.
     */
    public static function enqueueStyles(): void
    {
        // Self-contained, theme-agnostic styling that matches the bulk order
        // table (indigo accent, rounded inputs, focus ring). Own token palette
        // scoped to .smb2b-register so it never inherits the theme's form sty
        // or button colours.
        $css = "
.smb2b-register {
  --r-pri:var(--wp--preset--color--primary, #6D5EF5); --r-ring:color-mix(in srgb, var(--r-pri) 35%, transparent); --r-fg:#18181B; --r-muted:#71717A;
  --r-border:#D9D9E0; --r-card:#fff; --r-ok:#16A34A; --r-ok-weak:#E7F6EC;
  --r-err:#B91C1C; --r-err-weak:#FDECEC; --r-info:#1D4ED8; --r-info-weak:#EAF1FE;
  max-width:620px; color:var(--r-fg);
  font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
}
.smb2b-register *{box-sizing:border-box;}
.smb2b-register__row{display:flex;gap:16px;flex-wrap:wrap;}
.smb2b-register__row .smb2b-register__field{flex:1 1 200px;}
.smb2b-register__field{display:block;margin:0 0 18px;}
.smb2b-register__field label{display:block;font-weight:600;font-size:13.5px;margin-bottom:6px;color:var(--r-fg);}
.smb2b-register__field input,
.smb2b-register__field textarea{
  width:100%;display:block;font:inherit;font-size:14px;color:var(--r-fg);background:var(--r-card);
  border:1px solid var(--r-border);border-radius:9px;padding:11px 12px;
  -webkit-appearance:none;appearance:none;box-shadow:none;transition:border-color .1s,box-shadow .1s;
}
.smb2b-register__field textarea{min-height:110px;resize:vertical;line-height:1.5;}
.smb2b-register__field input:focus,
.smb2b-register__field textarea:focus{outline:none;border-color:var(--r-pri);box-shadow:0 0 0 3px var(--r-ring);}
.smb2b-register__field input::placeholder,
.smb2b-register__field textarea::placeholder{color:var(--r-muted);}
.smb2b-register__help{display:block;font-size:12.5px;color:var(--r-muted);margin-top:6px;}
.smb2b-register__hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;}
.smb2b-register__actions{margin:24px 0 0;}
.smb2b-register button[type=\"submit\"]{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  height:48px;padding:0 24px;border:none;border-radius:11px;cursor:pointer;
  font:inherit;font-size:15px;font-weight:600;color:#fff;background:var(--r-pri);
  -webkit-appearance:none;appearance:none;box-shadow:0 6px 16px -6px var(--r-ring);
  transition:filter .1s;
}
.smb2b-register button[type=\"submit\"]:hover{filter:brightness(1.06);}
.smb2b-register button[type=\"submit\"]:disabled{opacity:.5;cursor:not-allowed;filter:none;}
.smb2b-notice{padding:12px 16px;border-radius:9px;margin-bottom:18px;font-size:13.5px;}
.smb2b-notice--error{background:var(--r-err-weak);color:var(--r-err);}
.smb2b-notice--success{background:var(--r-ok-weak);color:var(--r-ok);}
.smb2b-notice--info{background:var(--r-info-weak);color:var(--r-info);}
.smb2b-notice ul{margin:6px 0 0;padding-left:18px;}";

        // Share the storefront accent colour (set under Settings → Bulk order
        // form). Only --r-pri needs setting; the focus ring derives from it.
        // Empty = match the theme's own colour, not ours.
        $stored = (string) Settings::get('bulk_order_accent', '');
        $accent = $stored !== '' ? sanitize_hex_color($stored) : \SoftminalB2B\Support\ThemeColor::accent();
        if ($accent) {
            $css .= "\n.smb2b-register{--r-pri:{$accent};}";
        }

        wp_register_style('smb2b-registration', false, [], SMB2B_VERSION);
        wp_enqueue_style('smb2b-registration');
        wp_add_inline_style('smb2b-registration', $css);
    }

    /**
     * @return array<int, string>
     */
    public static function errors(): array
    {
        return self::$errors;
    }

    public static function nonceField(): string
    {
        return wp_nonce_field(self::NONCE, '_wpnonce', true, false);
    }

    public static function honeypotName(): string
    {
        return self::HONEYPOT;
    }

    public static function timestampName(): string
    {
        return self::TIMESTAMP;
    }

    private static function notice(string $message, string $type): string
    {
        return sprintf(
            '<div class="smb2b-notice smb2b-notice--%s">%s</div>',
            esc_attr($type),
            esc_html($message)
        );
    }
}
