<?php
/**
 * Magic Login Links — passwordless authentication via single-use email link.
 *
 * UX-Workflow:
 *   1) User klickt auf wp-login.php auf „Per E-Mail einloggen" (statt Passwort)
 *   2) Gibt seinen Login/Mail ein
 *   3) Bekommt Mail mit Link: wp-login.php?action=shieldforge_magic&token=XYZ
 *   4) Klick → eingeloggt, Token verbrennt
 *
 * Security:
 *   - Token = 64-Zeichen-Zufall (random_bytes), gespeichert HMAC-SHA256-Hash
 *   - Single-use (sofort gelöscht nach Verbrauch)
 *   - 15-Minuten-Ablauf
 *   - Rate-Limit: 3 Magic-Link-Anforderungen pro Mail-Adresse in 60 Minuten
 *   - 2FA bleibt erforderlich falls aktiv (Magic Link ≠ Bypass)
 *
 * @package Brainwerk Security Suite
 */

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

class Shieldforge_Magic_Link {

    const META_KEY    = 'shieldforge_magic_token';
    const NONCE       = 'shieldforge_magic_request';
    const EXPIRY_SEC  = 900; // 15 min
    const RL_KEY      = 'shieldforge_magic_rl_';
    const RL_LIMIT    = 3;
    const RL_WINDOW   = HOUR_IN_SECONDS;

    public static function register( Shieldforge_Loader $loader ) {
        if ( ! Shieldforge_Settings::get( 'magic_link_enabled', false ) ) return;
        $loader->add_action( 'login_form', __CLASS__, 'render_button' );
        $loader->add_action( 'login_init', __CLASS__, 'route' );
        // Optional: Block 2FA-Bypass — wenn 2FA an, müsste Magic-Link-Login auch noch 2FA durchlaufen.
    }

    /** Button am Login-Form. */
    public static function render_button() {
        $url = wp_login_url();
        $form_url = add_query_arg( 'action', 'shieldforge_magic_form', $url );
        echo '<p style="margin-top:14px;text-align:center;font-size:13px;"><a href="' . esc_url( $form_url ) . '">' . esc_html__( '🔗 Stattdessen per Magic-Link einloggen (passwortlos)', 'brainwerk-security-suite' ) . '</a></p>';
    }

    /** Custom-Action-Router auf wp-login.php. */
    public static function route() {
        $action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only routing check, pre-auth (runs before wp-login.php), no state change.
        if ( 'shieldforge_magic_form' === $action ) self::handle_form();
        if ( 'shieldforge_magic_submit' === $action ) self::handle_submit();
        if ( 'shieldforge_magic' === $action ) self::handle_consume();
    }

    private static function handle_form() {
        login_header( __( 'Magic-Link anfordern', 'brainwerk-security-suite' ) );
        echo '<form name="magicform" method="post" action="' . esc_url( add_query_arg( 'action', 'shieldforge_magic_submit', wp_login_url() ) ) . '">';
        wp_nonce_field( self::NONCE );
        echo '<p><label>' . esc_html__( 'Benutzername oder E-Mail-Adresse', 'brainwerk-security-suite' ) . '<br><input type="text" name="user_login" class="input" required></label></p>';
        echo '<p class="submit"><input type="submit" class="button button-primary button-large" value="' . esc_attr__( 'Magic-Link senden', 'brainwerk-security-suite' ) . '"></p>';
        echo '<p style="font-size:12px;color:#646970;">' . esc_html__( 'Wir schicken einen einmaligen Anmelde-Link an die hinterlegte E-Mail-Adresse. Der Link ist 15 Minuten gültig.', 'brainwerk-security-suite' ) . '</p>';
        echo '<p><a href="' . esc_url( wp_login_url() ) . '">' . esc_html__( '← Zurück zum normalen Login', 'brainwerk-security-suite' ) . '</a></p>';
        echo '</form>';
        login_footer();
        exit;
    }

    private static function handle_submit() {
        if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), self::NONCE ) ) wp_die( 'bad nonce', 403 );
        $login = isset( $_POST['user_login'] ) ? sanitize_text_field( wp_unslash( $_POST['user_login'] ) ) : '';
        $user  = is_email( $login ) ? get_user_by( 'email', $login ) : get_user_by( 'login', $login );

        // Rate-Limit pro Login (auch wenn User nicht existiert — verhindert User-Enumeration)
        $rl_key = self::RL_KEY . md5( strtolower( $login ) );
        $hits = (int) get_transient( $rl_key );
        if ( $hits >= self::RL_LIMIT ) {
            self::generic_sent_screen();
            exit;
        }
        set_transient( $rl_key, $hits + 1, self::RL_WINDOW );

        if ( $user instanceof WP_User ) {
            $token = bin2hex( random_bytes( 32 ) );
            $hash  = hash_hmac( 'sha256', $token, wp_salt( 'auth' ) );
            update_user_meta( $user->ID, self::META_KEY, array( 'hash' => $hash, 'expires' => time() + self::EXPIRY_SEC ) );
            $link = add_query_arg( array( 'action' => 'shieldforge_magic', 'token' => $token, 'uid' => $user->ID ), wp_login_url() );
            $subject = sprintf( __( '[%s] Ihr Magic-Login-Link', 'brainwerk-security-suite' ), get_bloginfo( 'name' ) );
            $body  = '<p>' . sprintf( __( 'Hallo %s,', 'brainwerk-security-suite' ), esc_html( $user->display_name ) ) . '</p>';
            $body .= '<p>' . __( 'Klicken Sie innerhalb der nächsten 15 Minuten auf folgenden Link, um sich einzuloggen — kein Passwort nötig:', 'brainwerk-security-suite' ) . '</p>';
            $body .= '<p><a href="' . esc_url( $link ) . '" style="display:inline-block;padding:10px 20px;background:#2271b1;color:#fff;text-decoration:none;border-radius:4px;">' . esc_html__( 'Jetzt einloggen', 'brainwerk-security-suite' ) . '</a></p>';
            $body .= '<p style="font-size:12px;color:#646970;">' . esc_html__( 'Sie haben diesen Link nicht angefordert? Dann ignorieren Sie diese Mail — niemand kann mit Ihrem Account einloggen ohne diesen Link UND Ihren ggf. konfigurierten 2FA-Code.', 'brainwerk-security-suite' ) . '</p>';
            wp_mail( $user->user_email, $subject, $body, array( 'Content-Type: text/html; charset=UTF-8' ) );
        }
        // Generic „sent"-Screen — auch wenn User nicht existiert (User-Enumeration-Schutz)
        self::generic_sent_screen();
        exit;
    }

    private static function generic_sent_screen() {
        login_header( __( 'Magic-Link versandt', 'brainwerk-security-suite' ) );
        echo '<p style="text-align:center;padding:20px;background:#e6f5ea;border:1px solid #2e8b3e;border-radius:6px;">';
        echo '<strong>' . esc_html__( '✓ Falls dieser Account existiert, ist eine Mail mit Login-Link unterwegs.', 'brainwerk-security-suite' ) . '</strong><br>';
        echo '<span style="font-size:13px;color:#3c4147;">' . esc_html__( 'Prüfen Sie Ihren Posteingang (auch Spam-Ordner). Der Link ist 15 Minuten gültig.', 'brainwerk-security-suite' ) . '</span>';
        echo '</p>';
        echo '<p style="text-align:center;"><a href="' . esc_url( wp_login_url() ) . '">' . esc_html__( '← Zurück zum Login', 'brainwerk-security-suite' ) . '</a></p>';
        login_footer();
    }

    private static function handle_consume() {
        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- the magic-link token itself is the single-use credential, verified via hash_equals() against its stored HMAC below; a WP nonce is not applicable to this cross-request, email-delivered flow.
        $uid   = isset( $_GET['uid'] )   ? (int) $_GET['uid'] : 0;
        $token = isset( $_GET['token'] ) ? sanitize_text_field( wp_unslash( $_GET['token'] ) ) : '';
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
        if ( ! $uid || ! $token ) wp_die( esc_html__('Ungültiger Magic-Link.', 'brainwerk-security-suite' ), 400 );

        $stored = get_user_meta( $uid, self::META_KEY, true );
        if ( ! is_array( $stored ) || empty( $stored['hash'] ) || empty( $stored['expires'] ) ) {
            wp_die( esc_html__('Link bereits verwendet oder abgelaufen.', 'brainwerk-security-suite' ), 400 );
        }
        if ( time() > (int) $stored['expires'] ) {
            delete_user_meta( $uid, self::META_KEY );
            wp_die( esc_html__('Magic-Link abgelaufen. Bitte erneut anfordern.', 'brainwerk-security-suite' ), 400 );
        }
        $candidate = hash_hmac( 'sha256', $token, wp_salt( 'auth' ) );
        if ( ! hash_equals( $stored['hash'], $candidate ) ) {
            wp_die( esc_html__('Ungültiger Token.', 'brainwerk-security-suite' ), 400 );
        }
        // Verbrauch — Token sofort löschen (single-use)
        delete_user_meta( $uid, self::META_KEY );
        $user = get_user_by( 'id', $uid );
        if ( ! $user instanceof WP_User ) wp_die( esc_html__('User nicht gefunden.', 'brainwerk-security-suite' ), 400 );

        // 2FA-Gate: Der Magic-Link beweist nur Mail-Besitz (= 1. Faktor-Ersatz).
        // Hat der User 2FA aktiv, MUSS er trotzdem den zweiten Faktor liefern —
        // sonst wäre der Link ein 2FA-Bypass. Wir leiten in dasselbe Interstitial
        // wie der Passwort-Login (needs_2fa() ist für 2FA-befreite Accounts, inkl.
        // Break-Glass-Admin, false → die loggen wie bisher direkt durch).
        if ( class_exists( 'Shieldforge_TwoFactor' ) && Shieldforge_TwoFactor::needs_2fa( $user ) ) {
            Shieldforge_TwoFactor::stash_pending_and_redirect( $user, false, admin_url() ); // exits
        }

        // Einloggen
        wp_set_current_user( $user->ID );
        wp_set_auth_cookie( $user->ID, false );
        do_action( 'wp_login', $user->user_login, $user );
        wp_safe_redirect( admin_url() );
        exit;
    }
}
