<?php
/**
 * "Sign in with Google" for staff/students — mirrors the Laravel live site's
 * Google login (E7-4). The standard WordPress username/password form is kept
 * intact for admins; this only ADDS a Google button to wp-login.php.
 *
 * Security model (hardened vs. the Laravel version, which trusted the `hd`
 * request hint alone):
 *   - OAuth 2.0 Authorization-Code flow with a `state` CSRF token (cookie).
 *   - The ID token returned by Google is validated server-side via Google's
 *     tokeninfo endpoint: we check `aud` == our client, `iss` == Google,
 *     `email_verified`, expiry, AND a HARD email-domain allowlist
 *     (brentwood.ca). A non-allowed account is rejected and NO WP user is
 *     created.
 *   - Newly provisioned Google users get the least-privilege `bw_staff` role
 *     (read-only; can edit only their own /my-profile via the page's owner
 *     check). Existing users (e.g. admins) keep whatever role they have.
 *
 * Credentials are NOT in this file. They come from the project .env
 * (BW_GOOGLE_CLIENT_ID / BW_GOOGLE_CLIENT_SECRET), passed into the container by
 * docker-compose and read here via getenv(). .env is 660 rian:brentwooddev-dev
 * (not world-readable) — the same store as the DB password.
 *
 * @package Kadence-Child
 */

defined( 'ABSPATH' ) || exit;

/** Allowed Google Workspace email domains (hard server-side allowlist). */
function bw_goog_allowed_domains() {
	return apply_filters( 'bw_google_allowed_domains', array( 'brentwood.ca' ) );
}

/**
 * OAuth config. Source order:
 *   1. Environment (getenv) — if the host ever wires BW_GOOGLE_* into the
 *      container. NB: the gateway runs the WP container via `docker run`, so
 *      custom env is not guaranteed to arrive — hence the option fallback.
 *   2. The `bw_google_oauth` option, set by an admin via Settings → Google
 *      Login (entered in-browser, so the secret never lands in chat, shell
 *      history, or a world-readable file).
 * The DB is not world-readable on disk; the value is only reachable by project
 * members (same exposure as the .env DB password).
 */
function bw_goog_cfg() {
	$opt = get_option( 'bw_google_oauth', array() );
	$opt = is_array( $opt ) ? $opt : array();
	return array(
		'client_id'     => (string) ( getenv( 'BW_GOOGLE_CLIENT_ID' ) ?: ( $opt['client_id'] ?? '' ) ),
		'client_secret' => (string) ( getenv( 'BW_GOOGLE_CLIENT_SECRET' ) ?: ( $opt['client_secret'] ?? '' ) ),
	);
}

/** Is Google login configured (both id + secret present, non-placeholder)? */
function bw_goog_is_configured() {
	$c = bw_goog_cfg();
	if ( '' === $c['client_id'] || '' === $c['client_secret'] ) {
		return false;
	}
	// Guard against the staged placeholders being left in place.
	if ( false !== strpos( $c['client_id'], 'REPLACE_WITH' ) || false !== strpos( $c['client_secret'], 'REPLACE_WITH' ) ) {
		return false;
	}
	return true;
}

/** The exact redirect URI — must be registered in the Google Cloud OAuth client. */
function bw_goog_redirect_uri() {
	return admin_url( 'admin-ajax.php' ) . '?action=bw_google_callback';
}

/* =========================================================================
 * Least-privilege role for Google-provisioned users
 * ====================================================================== */
add_action( 'after_switch_theme', 'bw_goog_register_role' );
add_action( 'init', 'bw_goog_register_role' );
function bw_goog_register_role() {
	if ( ! get_role( 'bw_staff' ) ) {
		// 'read' only: can be logged in and use the front-end /my-profile editor
		// (which authorizes by profile ownership, not WP caps). No wp-admin
		// content access, no upload/edit_others — minimal blast radius.
		add_role( 'bw_staff', 'Staff', array( 'read' => true ) );
	}
}

/* =========================================================================
 * Step 1 — start: redirect the browser to Google
 * (admin-ajax so it works regardless of theme/login screen)
 * ====================================================================== */
add_action( 'wp_ajax_nopriv_bw_google_login', 'bw_goog_start' );
add_action( 'wp_ajax_bw_google_login', 'bw_goog_start' );
function bw_goog_start() {
	if ( ! bw_goog_is_configured() ) {
		bw_goog_fail( 'unconfigured' );
	}
	$c = bw_goog_cfg();

	$state = wp_generate_password( 40, false );
	// CSRF: state echoed back by Google must match this cookie.
	setcookie( 'bw_google_state', $state, array(
		'expires'  => time() + 600,
		'path'     => '/',
		'secure'   => is_ssl(),
		'httponly' => true,
		'samesite' => 'Lax',
	) );
	// Stash where to send the user afterwards (default: admin).
	$redirect_to = isset( $_GET['redirect_to'] ) ? esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) : admin_url();
	set_transient( 'bw_goog_rt_' . md5( $state ), $redirect_to, 600 );

	$params = array(
		'client_id'     => $c['client_id'],
		'redirect_uri'  => bw_goog_redirect_uri(),
		'response_type' => 'code',
		'scope'         => 'openid email profile',
		'state'         => $state,
		'prompt'        => 'select_account',
		// `hd` is only a hint; the real enforcement is the server-side domain
		// check in the callback. We still send it for a better UX.
		'hd'            => implode( ' ', bw_goog_allowed_domains() ),
		'access_type'   => 'online',
	);
	wp_redirect( 'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query( $params ) );
	exit;
}

/* =========================================================================
 * Step 2 — callback: validate, then log in (or reject)
 * ====================================================================== */
add_action( 'wp_ajax_nopriv_bw_google_callback', 'bw_goog_callback' );
add_action( 'wp_ajax_bw_google_callback', 'bw_goog_callback' );
function bw_goog_callback() {
	if ( ! bw_goog_is_configured() ) {
		bw_goog_fail( 'unconfigured' );
	}

	// Google returns ?error=access_denied if the user cancels.
	if ( isset( $_GET['error'] ) ) {
		bw_goog_fail( 'cancelled' );
	}

	$code  = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : '';
	$state = isset( $_GET['state'] ) ? sanitize_text_field( wp_unslash( $_GET['state'] ) ) : '';
	$cookie_state = isset( $_COOKIE['bw_google_state'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['bw_google_state'] ) ) : '';

	// CSRF: state must be present and match the cookie.
	if ( '' === $code || '' === $state || '' === $cookie_state || ! hash_equals( $cookie_state, $state ) ) {
		bw_goog_fail( 'badstate' );
	}
	// One-time use: clear the state cookie immediately.
	setcookie( 'bw_google_state', '', array( 'expires' => time() - 3600, 'path' => '/', 'secure' => is_ssl(), 'httponly' => true, 'samesite' => 'Lax' ) );

	$c = bw_goog_cfg();

	// --- exchange the code for tokens (server-to-server) ---
	$resp = wp_remote_post( 'https://oauth2.googleapis.com/token', array(
		'timeout' => 15,
		'body'    => array(
			'code'          => $code,
			'client_id'     => $c['client_id'],
			'client_secret' => $c['client_secret'],
			'redirect_uri'  => bw_goog_redirect_uri(),
			'grant_type'    => 'authorization_code',
		),
	) );
	if ( is_wp_error( $resp ) || 200 !== (int) wp_remote_retrieve_response_code( $resp ) ) {
		bw_goog_fail( 'token', $resp );
	}
	$tok = json_decode( wp_remote_retrieve_body( $resp ), true );
	if ( empty( $tok['id_token'] ) ) {
		bw_goog_fail( 'noidtoken' );
	}

	// --- validate the ID token via Google's tokeninfo (verifies signature) ---
	$vi = wp_remote_get( 'https://oauth2.googleapis.com/tokeninfo?id_token=' . rawurlencode( $tok['id_token'] ), array( 'timeout' => 15 ) );
	if ( is_wp_error( $vi ) || 200 !== (int) wp_remote_retrieve_response_code( $vi ) ) {
		bw_goog_fail( 'verify', $vi );
	}
	$claims = json_decode( wp_remote_retrieve_body( $vi ), true );
	if ( ! is_array( $claims ) ) {
		bw_goog_fail( 'claims' );
	}

	// aud must be our client; iss must be Google; token must not be expired.
	$iss_ok = in_array( $claims['iss'] ?? '', array( 'accounts.google.com', 'https://accounts.google.com' ), true );
	$aud_ok = ( ( $claims['aud'] ?? '' ) === $c['client_id'] );
	$exp_ok = ( (int) ( $claims['exp'] ?? 0 ) > time() );
	$ver_ok = in_array( (string) ( $claims['email_verified'] ?? '' ), array( '1', 'true' ), true );
	if ( ! $iss_ok || ! $aud_ok || ! $exp_ok || ! $ver_ok ) {
		bw_goog_fail( 'invalidclaims' );
	}

	$email = strtolower( sanitize_email( $claims['email'] ?? '' ) );
	if ( '' === $email || ! is_email( $email ) ) {
		bw_goog_fail( 'noemail' );
	}

	// HARD domain allowlist — the real gate (independent of the `hd` hint).
	$domain  = substr( strrchr( $email, '@' ), 1 );
	$allowed = bw_goog_allowed_domains();
	$hd_ok   = ! isset( $claims['hd'] ) || in_array( strtolower( $claims['hd'] ), $allowed, true );
	if ( ! in_array( $domain, $allowed, true ) || ! $hd_ok ) {
		bw_goog_fail( 'domain' );
	}

	// --- find-or-create the WP user (by email) ---
	$user = get_user_by( 'email', $email );
	if ( ! $user instanceof WP_User ) {
		$uid = wp_insert_user( array(
			'user_login'   => $email,
			'user_email'   => $email,
			'user_pass'    => wp_generate_password( 40, true, true ),
			'display_name' => sanitize_text_field( $claims['name'] ?? $email ),
			'first_name'   => sanitize_text_field( $claims['given_name'] ?? '' ),
			'last_name'    => sanitize_text_field( $claims['family_name'] ?? '' ),
			'role'         => 'bw_staff', // least privilege
		) );
		if ( is_wp_error( $uid ) ) {
			bw_goog_fail( 'createuser', $uid );
		}
		$user = get_user_by( 'id', $uid );
	}
	// Record the Google subject id (do NOT touch the role of existing users).
	update_user_meta( $user->ID, 'bw_google_sub', sanitize_text_field( $claims['sub'] ?? '' ) );

	// --- log in ---
	$redirect_to = get_transient( 'bw_goog_rt_' . md5( $state ) );
	delete_transient( 'bw_goog_rt_' . md5( $state ) );
	$redirect_to = $redirect_to ?: admin_url();

	wp_set_current_user( $user->ID );
	wp_set_auth_cookie( $user->ID, true );
	do_action( 'wp_login', $user->user_login, $user );

	wp_safe_redirect( $redirect_to );
	exit;
}

/** Log a reason server-side and bounce back to the login page with a generic message. */
function bw_goog_fail( $reason, $detail = null ) {
	$msg = is_wp_error( $detail ) ? $detail->get_error_message() : ( is_string( $detail ) ? $detail : '' );
	error_log( '[bw-google-login] failed: ' . $reason . ( $msg ? ' — ' . $msg : '' ) );
	$user_msg = ( 'domain' === $reason )
		? 'That Google account is not permitted. Use your @brentwood.ca account.'
		: ( 'cancelled' === $reason ? 'Google sign-in was cancelled.' : 'Google sign-in failed. Please try again or use your password.' );
	wp_safe_redirect( add_query_arg( 'bw_google_error', rawurlencode( $user_msg ), wp_login_url() ) );
	exit;
}

/* =========================================================================
 * Login screen: the button + a surfaced error message
 * ====================================================================== */
add_action( 'login_message', function ( $message ) {
	if ( ! bw_goog_is_configured() ) {
		return $message;
	}
	$start = esc_url( admin_url( 'admin-ajax.php' ) . '?action=bw_google_login' );
	$err   = isset( $_GET['bw_google_error'] ) ? sanitize_text_field( wp_unslash( $_GET['bw_google_error'] ) ) : '';

	$out = '';
	if ( '' !== $err ) {
		$out .= '<div id="login_error" class="bw-google-error">' . esc_html( $err ) . '</div>';
	}
	$out .= '<div class="bw-google-login">'
		. '<a class="bw-google-btn" href="' . $start . '">'
		. '<svg class="bw-google-btn__g" viewBox="0 0 48 48" aria-hidden="true" focusable="false">'
		. '<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/>'
		. '<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/>'
		. '<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/>'
		. '<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/>'
		. '</svg>'
		. '<span>Sign in with Google</span>'
		. '</a>'
		. '<div class="bw-google-or"><span>Staff &amp; Students — or sign in below</span></div>'
		. '</div>';
	return $message . $out;
} );

add_action( 'login_enqueue_scripts', function () {
	if ( ! bw_goog_is_configured() ) {
		return;
	}
	$css = '.bw-google-login{margin:0 0 16px;}'
		. '.bw-google-btn{display:flex;align-items:center;justify-content:center;gap:10px;'
		. 'width:100%;box-sizing:border-box;padding:11px 14px;background:#fff;color:#3c4043;'
		. 'font-weight:600;font-size:14px;text-decoration:none;border:1px solid #dadce0;'
		. 'border-radius:6px;box-shadow:0 1px 2px rgba(0,0,0,.05);}'
		. '.bw-google-btn:hover{background:#f7f8f8;border-color:#c9ccd1;}'
		. '.bw-google-btn__g{width:18px;height:18px;display:block;}'
		. '.bw-google-or{position:relative;text-align:center;margin:14px 0 4px;}'
		. '.bw-google-or span{background:#fff;padding:0 10px;color:#777;font-size:12px;position:relative;z-index:1;}'
		. '.bw-google-or:before{content:"";position:absolute;left:0;right:0;top:50%;border-top:1px solid #dcdcde;}'
		. '.bw-google-error{margin:0 0 16px;}';
	wp_register_style( 'bw-google-login', false );
	wp_enqueue_style( 'bw-google-login' );
	wp_add_inline_style( 'bw-google-login', $css );
} );

/* =========================================================================
 * Admin settings page (Settings → Google Login) — admin-only credential entry
 * ====================================================================== */
add_action( 'admin_menu', function () {
	add_options_page(
		'Google Login',
		'Google Login',
		'manage_options',
		'bw-google-login',
		'bw_goog_settings_page'
	);
} );

function bw_goog_settings_page() {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}

	// Save.
	if ( isset( $_POST['bw_goog_save'] ) && check_admin_referer( 'bw_goog_settings' ) ) {
		$opt = get_option( 'bw_google_oauth', array() );
		$opt = is_array( $opt ) ? $opt : array();

		$opt['client_id'] = sanitize_text_field( wp_unslash( $_POST['bw_goog_client_id'] ?? '' ) );

		// Only overwrite the secret if a new (non-masked) value was entered, so
		// re-saving the form doesn't blank an existing secret.
		$secret_in = trim( (string) wp_unslash( $_POST['bw_goog_client_secret'] ?? '' ) );
		if ( '' !== $secret_in && false === strpos( $secret_in, '•' ) ) {
			$opt['client_secret'] = $secret_in;
		}

		update_option( 'bw_google_oauth', $opt, false ); // autoload=false
		echo '<div class="notice notice-success is-dismissible"><p>Google Login settings saved.</p></div>';
	}

	$cfg          = bw_goog_cfg();
	$has_secret   = '' !== $cfg['client_secret'];
	$secret_field = $has_secret ? '••••••••••••••••' : '';
	$configured   = bw_goog_is_configured();
	$redirect_uri = bw_goog_redirect_uri();
	?>
	<div class="wrap">
		<h1>Google Login</h1>
		<p>
			Status:
			<?php if ( $configured ) : ?>
				<strong style="color:#1b5e35;">Configured &amp; active</strong> — the "Sign in with Google" button is shown on the login page.
			<?php else : ?>
				<strong style="color:#a02622;">Not configured</strong> — the button is hidden until a Client ID and Secret are saved.
			<?php endif; ?>
		</p>

		<h2 class="title">Step 1 — register this redirect URI in Google Cloud Console</h2>
		<p>In the OAuth 2.0 Client (the same client the Laravel site uses), add this <strong>Authorized redirect URI</strong> exactly:</p>
		<p><code style="user-select:all;display:inline-block;padding:6px 10px;background:#f0f0f1;border:1px solid #c3c4c7;border-radius:4px;"><?php echo esc_html( $redirect_uri ); ?></code></p>
		<p class="description">When this site is pushed to <code>dev.brentwood.ca</code>, also add that domain's equivalent URI.</p>

		<h2 class="title">Step 2 — enter the credentials</h2>
		<form method="post">
			<?php wp_nonce_field( 'bw_goog_settings' ); ?>
			<table class="form-table" role="presentation">
				<tr>
					<th scope="row"><label for="bw_goog_client_id">Client ID</label></th>
					<td><input name="bw_goog_client_id" id="bw_goog_client_id" type="text" class="regular-text code"
						value="<?php echo esc_attr( $cfg['client_id'] ); ?>" autocomplete="off"></td>
				</tr>
				<tr>
					<th scope="row"><label for="bw_goog_client_secret">Client Secret</label></th>
					<td>
						<input name="bw_goog_client_secret" id="bw_goog_client_secret" type="password" class="regular-text code"
							value="<?php echo esc_attr( $secret_field ); ?>" autocomplete="off">
						<p class="description"><?php echo $has_secret ? 'A secret is saved. Leave the dots to keep it; type a new value to replace it.' : 'Paste the OAuth client secret.'; ?></p>
					</td>
				</tr>
				<tr>
					<th scope="row">Allowed domains</th>
					<td><code><?php echo esc_html( implode( ', ', bw_goog_allowed_domains() ) ); ?></code>
						<p class="description">Only Google accounts on these domains may sign in (hard server-side check).</p></td>
				</tr>
			</table>
			<?php submit_button( 'Save settings', 'primary', 'bw_goog_save' ); ?>
		</form>
	</div>
	<?php
}
