<?php
/**
 * 2FA section on the user-profile screen — both `your profile` (self) and
 * the per-user edit screen (admin editing another user).
 *
 * This file is split out from class-shieldforge-twofactor.php so the core
 * authentication code stays focused on the login flow, while all "ten
 * lines of HTML for a checkbox" logic lives here.
 *
 * @package Brainwerk Security Suite
 */

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

class Shieldforge_TwoFactor_Profile {

    const NONCE_SETUP   = 'shieldforge_2fa_setup';
    const NONCE_DISABLE = 'shieldforge_2fa_disable';
    const NONCE_REGEN   = 'shieldforge_2fa_regen';

    public static function register( Shieldforge_Loader $loader ) {
        $loader->add_action( 'show_user_profile', __CLASS__, 'render_section' );
        $loader->add_action( 'edit_user_profile', __CLASS__, 'render_section' );
        $loader->add_action( 'admin_enqueue_scripts', __CLASS__, 'enqueue_assets' );

        // Action handlers run on admin-post.php so we can redirect cleanly
        // (the user-profile form posts to user-edit.php which expects a
        // different submit format — easier to use a sibling endpoint).
        $loader->add_action( 'admin_post_shieldforge_2fa_setup',   __CLASS__, 'handle_setup' );
        $loader->add_action( 'admin_post_shieldforge_2fa_disable', __CLASS__, 'handle_disable' );
        $loader->add_action( 'admin_post_shieldforge_2fa_regen',   __CLASS__, 'handle_regen' );

        // Surface freshly-generated recovery codes once via a transient
        // round-trip (so a page refresh doesn't re-print them).
        $loader->add_action( 'admin_notices', __CLASS__, 'maybe_show_recovery_notice' );
    }

    /**
     * Enqueue the profile-screen 2FA helper script. Only on the two profile
     * screens (own profile + edit-user) and only when the module is on.
     */
    public static function enqueue_assets( $hook ) {
        if ( 'profile.php' !== $hook && 'user-edit.php' !== $hook ) {
            return;
        }
        if ( ! Shieldforge_TwoFactor::is_module_enabled() ) {
            return;
        }
        wp_enqueue_script(
            'shieldforge-twofactor-profile',
            SHIELDFORGE_URL . 'assets/js/twofactor-profile.js',
            array(),
            SHIELDFORGE_VERSION,
            true
        );
    }

    /* ==================================================================
     *  Rendering
     * ================================================================== */

    public static function render_section( $user ) {
        if ( ! Shieldforge_TwoFactor::is_module_enabled() ) {
            return;
        }
        if ( ! $user || ! current_user_can( 'edit_user', $user->ID ) ) {
            return;
        }

        $is_self      = ( get_current_user_id() === (int) $user->ID );
        $is_enabled   = Shieldforge_TwoFactor::is_enabled_for_user( $user->ID );
        $is_required  = Shieldforge_TwoFactor::is_required_for_user( $user );
        $remaining    = Shieldforge_TwoFactor::recovery_codes_remaining( $user->ID );

        // Defensive: profile.php wraps everything in a single <form> and uses
        // its own .form-table layout. Some WP plugins (page builders, security
        // plugins) emit malformed HTML in show_user_profile hooks that runs
        // before us — close any unterminated tags before we start, and use a
        // self-contained <div> layout instead of nested form-tables.
        echo '</td></tr></tbody></table>';
        echo '<div class="shieldforge-2fa-section" style="background:#fff; border:1px solid #c3c4c7; border-radius:4px; padding:18px 22px; margin:24px 0;">';
        ?>
        <h2 id="shieldforge-2fa" style="margin-top:0;"><?php esc_html_e( 'Two-factor authentication (Brainwerk Security Suite)', 'brainwerk-security-suite' ); ?></h2>

        <?php if ( $is_required && ! $is_enabled ) : ?>
            <div class="notice notice-warning inline" style="margin:0 0 12px;">
                <p><?php esc_html_e( 'Site policy requires two-factor authentication for your role. Set it up below.', 'brainwerk-security-suite' ); ?></p>
            </div>
        <?php endif; ?>

        <?php if ( $is_enabled ) : ?>
            <p>
                <strong style="color:#2d6a2d"><?php esc_html_e( '2FA is active.', 'brainwerk-security-suite' ); ?></strong>
                <?php
                printf(
                    /* translators: %d: remaining recovery codes */
                    ' &mdash; ' . esc_html( _n( '%d recovery code left.', '%d recovery codes left.', $remaining, 'brainwerk-security-suite' ) ),
                    (int) $remaining
                );
                ?>
            </p>
            <?php if ( $remaining < 3 ) : ?>
                <p style="color:#b32d2e"><?php esc_html_e( 'You are running low on recovery codes. Generate a fresh batch to avoid being locked out.', 'brainwerk-security-suite' ); ?></p>
            <?php endif; ?>
            <?php
            // Same nested-form workaround as the setup wizard: render plain
            // buttons that build a one-shot form and submit via JS.
            $regen_nonce   = wp_create_nonce( self::NONCE_REGEN );
            $disable_nonce = wp_create_nonce( self::NONCE_DISABLE );
            $admin_post    = admin_url( 'admin-post.php' );
            $regen_confirm   = __( 'Generate fresh recovery codes? Existing codes will stop working immediately.', 'brainwerk-security-suite' );
            $disable_confirm = __( 'Really disable 2FA for this account? You will lose phishing-resistant login.', 'brainwerk-security-suite' );
            ?>
            <p style="margin-top:18px;">
                <button type="button"
                        class="button shieldforge-2fa-action"
                        data-action="<?php echo esc_attr( $admin_post ); ?>"
                        data-nonce="<?php echo esc_attr( $regen_nonce ); ?>"
                        data-user="<?php echo (int) $user->ID; ?>"
                        data-handler="shieldforge_2fa_regen"
                        data-confirm="<?php echo esc_attr( $regen_confirm ); ?>"
                        style="margin-right:8px;">
                    <?php esc_html_e( 'Regenerate recovery codes', 'brainwerk-security-suite' ); ?>
                </button>
                <button type="button"
                        class="button button-link-delete shieldforge-2fa-action"
                        data-action="<?php echo esc_attr( $admin_post ); ?>"
                        data-nonce="<?php echo esc_attr( $disable_nonce ); ?>"
                        data-user="<?php echo (int) $user->ID; ?>"
                        data-handler="shieldforge_2fa_disable"
                        data-confirm="<?php echo esc_attr( $disable_confirm ); ?>">
                    <?php esc_html_e( 'Disable two-factor', 'brainwerk-security-suite' ); ?>
                </button>
            </p>
        <?php else : ?>
            <?php
            if ( ! $is_self ) {
                echo '<p>' . esc_html__( 'This user has not set up 2FA yet. Only the user themselves can enable it.', 'brainwerk-security-suite' ) . '</p>';
                echo '</div>';
                return;
            }
            $secret      = Shieldforge_TwoFactor::get_or_create_pending_secret( $user->ID );
            $issuer      = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
            $account     = $user->user_login . '@' . wp_parse_url( home_url(), PHP_URL_HOST );
            $otpauth_uri = Shieldforge_TOTP::provisioning_uri( $secret, $account, $issuer );
            $secret_grouped = trim( chunk_split( strtoupper( $secret ), 4, ' ' ) );
            ?>
            <p><strong><?php esc_html_e( 'Step 1.', 'brainwerk-security-suite' ); ?></strong> <?php esc_html_e( 'Open your authenticator app (Google Authenticator, Microsoft Authenticator, Authy, 2FAS, Aegis, FreeOTP) and add a new account using the secret below.', 'brainwerk-security-suite' ); ?></p>

            <p style="margin:14px 0 6px;"><strong><?php esc_html_e( 'Setup secret (case-insensitive, spaces ignored):', 'brainwerk-security-suite' ); ?></strong></p>
            <p style="margin:0 0 14px;">
                <code style="display:inline-block; padding:8px 14px; background:#f6f7f7; border:1px solid #dcdcde; font-size:16px; letter-spacing:2px; user-select:all;"><?php echo esc_html( $secret_grouped ); ?></code>
            </p>
            <p style="margin:0 0 14px; font-size:12px; color:#50575e;">
                <?php esc_html_e( 'Algorithm: SHA-1 · Digits: 6 · Period: 30s', 'brainwerk-security-suite' ); ?>
                &nbsp;·&nbsp;
                <a href="<?php echo esc_attr( $otpauth_uri ); ?>"><?php esc_html_e( 'Open in app (mobile only)', 'brainwerk-security-suite' ); ?></a>
            </p>

            <?php
            // profile.php wraps everything in a single outer <form>, and HTML
            // forbids nested forms. Browsers silently drop the inner one and
            // the submit button does nothing. Workaround: render plain inputs
            // and submit via JS that builds a one-shot form on document.body.
            $setup_nonce  = wp_create_nonce( self::NONCE_SETUP );
            $admin_post   = admin_url( 'admin-post.php' );
            ?>
            <p><strong><?php esc_html_e( 'Step 2.', 'brainwerk-security-suite' ); ?></strong> <?php esc_html_e( 'Enter the 6-digit code your app shows right now.', 'brainwerk-security-suite' ); ?></p>
            <p style="margin:8px 0 0;">
                <input type="text"
                       id="shieldforge_2fa_code"
                       inputmode="numeric"
                       autocomplete="one-time-code"
                       pattern="[0-9 ]{6,8}"
                       placeholder="000000"
                       required
                       style="font-size:18px; letter-spacing:6px; padding:8px 12px; width:160px; vertical-align:middle;">
                <button type="button"
                        class="button button-primary"
                        id="shieldforge_2fa_confirm_btn"
                        style="vertical-align:middle;"
                        data-action="<?php echo esc_attr( $admin_post ); ?>"
                        data-nonce="<?php echo esc_attr( $setup_nonce ); ?>"
                        data-user="<?php echo (int) $user->ID; ?>"><?php esc_html_e( 'Confirm and enable 2FA', 'brainwerk-security-suite' ); ?></button>
            </p>
        <?php endif; ?>
        </div>
        <?php
        // Re-open a fresh table so any subsequent show_user_profile hooks
        // (and the bottom of profile.php) keep their layout intact.
        echo '<table class="form-table" role="presentation"><tbody><tr><td>';
    }

    /* ==================================================================
     *  Action handlers (admin-post.php endpoints)
     * ================================================================== */

    public static function handle_setup() {
        $user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0;
        check_admin_referer( self::NONCE_SETUP );
        if ( ! self::can_act_on( $user_id ) ) {
            wp_die( esc_html__( 'Insufficient permissions.', 'brainwerk-security-suite' ) );
        }
        // Setup must be self-service — no admin can complete another user's
        // TOTP setup because they don't have the device.
        if ( get_current_user_id() !== $user_id ) {
            wp_die( esc_html__( 'Only the user can complete their own 2FA setup.', 'brainwerk-security-suite' ) );
        }
        $code   = isset( $_POST['shieldforge_2fa_code'] ) ? sanitize_text_field( wp_unslash( $_POST['shieldforge_2fa_code'] ) ) : '';
        $codes  = Shieldforge_TwoFactor::confirm_setup( $user_id, $code );
        if ( false === $codes ) {
            self::redirect_with_notice( $user_id, 'invalid_code' );
        }
        // Stash recovery codes in a transient bound to this user so we can
        // show them once on the next page load. Using a transient (not a
        // query string) keeps the codes out of browser history / referrers.
        set_transient( 'shieldforge_2fa_recovery_show_' . $user_id, $codes, 5 * MINUTE_IN_SECONDS );
        self::redirect_with_notice( $user_id, 'enabled' );
    }

    public static function handle_disable() {
        $user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0;
        check_admin_referer( self::NONCE_DISABLE );
        if ( ! self::can_act_on( $user_id ) ) {
            wp_die( esc_html__( 'Insufficient permissions.', 'brainwerk-security-suite' ) );
        }
        Shieldforge_TwoFactor::disable_for_user(
            $user_id,
            get_current_user_id() === $user_id ? 'user_disabled' : 'admin_rescue'
        );
        self::redirect_with_notice( $user_id, 'disabled' );
    }

    public static function handle_regen() {
        $user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0;
        check_admin_referer( self::NONCE_REGEN );
        if ( ! self::can_act_on( $user_id ) ) {
            wp_die( esc_html__( 'Insufficient permissions.', 'brainwerk-security-suite' ) );
        }
        if ( get_current_user_id() !== $user_id ) {
            wp_die( esc_html__( 'Only the user can regenerate their own recovery codes.', 'brainwerk-security-suite' ) );
        }
        $codes = Shieldforge_TwoFactor::regenerate_recovery_codes( $user_id );
        set_transient( 'shieldforge_2fa_recovery_show_' . $user_id, $codes, 5 * MINUTE_IN_SECONDS );
        self::redirect_with_notice( $user_id, 'regen' );
    }

    /* ==================================================================
     *  Notice rendering
     * ================================================================== */

    public static function maybe_show_recovery_notice() {
        $user_id = get_current_user_id();
        if ( ! $user_id ) {
            return;
        }
        // Only on profile pages.
        $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
        if ( ! $screen || ! in_array( $screen->id, array( 'profile', 'user-edit', 'user-edit-network' ), true ) ) {
            return;
        }
        $codes = get_transient( 'shieldforge_2fa_recovery_show_' . $user_id );
        if ( ! is_array( $codes ) || empty( $codes ) ) {
            // Generic confirmations:
            $msg = isset( $_GET['shieldforge_2fa'] ) ? sanitize_key( wp_unslash( $_GET['shieldforge_2fa'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only display flag selecting a static confirmation notice on the user's own profile page, no state change.
            switch ( $msg ) {
                case 'disabled':
                    echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Two-factor authentication disabled.', 'brainwerk-security-suite' ) . '</p></div>';
                    break;
                case 'invalid_code':
                    echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__( 'That code did not match. Check your device clock and try again.', 'brainwerk-security-suite' ) . '</p></div>';
                    break;
            }
            return;
        }
        delete_transient( 'shieldforge_2fa_recovery_show_' . $user_id );
        ?>
        <div class="notice notice-success">
            <p><strong><?php esc_html_e( 'Save these recovery codes now — they are shown only once.', 'brainwerk-security-suite' ); ?></strong></p>
            <p class="description"><?php esc_html_e( 'Each code works exactly once. Store them somewhere safe (password manager, printed copy in a drawer). Without them, losing your authenticator means losing access.', 'brainwerk-security-suite' ); ?></p>
            <pre style="background:#f6f7f7; padding:12px; border:1px solid #dcdcde; max-width:360px; font-size:14px; letter-spacing:1px;"><?php
            foreach ( $codes as $c ) {
                echo esc_html( $c ) . "\n";
            }
            ?></pre>
        </div>
        <?php
    }

    /* ==================================================================
     *  Helpers
     * ================================================================== */

    private static function can_act_on( $user_id ) {
        return $user_id && current_user_can( 'edit_user', $user_id );
    }

    private static function redirect_with_notice( $user_id, $msg ) {
        $base = ( get_current_user_id() === (int) $user_id )
            ? admin_url( 'profile.php' )
            : admin_url( 'user-edit.php?user_id=' . (int) $user_id );
        wp_safe_redirect( add_query_arg( 'shieldforge_2fa', $msg, $base ) . '#shieldforge-2fa' );
        exit;
    }
}
