<?php
/**
 * Dashboard Message Webhook — REST API Endpoint
 *
 * Exposes two REST routes so the external Laravel server can read and push
 * the Dashboard Message widget content without requiring a logged-in session.
 *
 * Authentication uses HMAC-SHA256 signed headers:
 *   X-APWP-Timestamp  — unix timestamp of the request
 *   X-APWP-Signature  — sha256=HMAC-SHA256(canonical, pk)
 *
 * For GET:  canonical = X-APWP-Timestamp value (string)
 * For POST: canonical = raw request body (JSON bytes as sent)
 *
 * `pk` is the per-license signing key embedded in the stored apwp-ao JWT.
 * It is derived server-side as HMAC-SHA256(ao_plugin_key, PLUGIN_JWT_SECRET)
 * and never transmitted directly.
 *
 * @package Agency_Pulse_Admin_Menu
 * @since   1.0.0
 */

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

class Agency_Pulse_Admin_Menu_Dashboard_Webhook {

	// -------------------------------------------------------------------------
	// Initialisation
	// -------------------------------------------------------------------------

	/**
	 * Register the rest_api_init hook.
	 *
	 * Called from Agency_Pulse_Admin_Menu::setup_hooks() during plugins_loaded.
	 *
	 * @since 1.0.0
	 */
	public static function init(): void {
		add_action( 'rest_api_init', array( __CLASS__, 'register_routes' ) );
	}

	// -------------------------------------------------------------------------
	// Route registration
	// -------------------------------------------------------------------------

	/**
	 * Register GET and POST routes under the apwp-ao/v1 namespace.
	 *
	 * @since 1.0.0
	 */
	public static function register_routes(): void {
		register_rest_route(
			Agency_Pulse_Admin_Menu_Constants::REST_NAMESPACE,
			Agency_Pulse_Admin_Menu_Constants::REST_DASHBOARD_MSG,
			array(
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( __CLASS__, 'get_message' ),
					'permission_callback' => array( __CLASS__, 'verify_request' ),
				),
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( __CLASS__, 'set_message' ),
					'permission_callback' => array( __CLASS__, 'verify_request' ),
				),
			)
		);

		register_rest_route(
			Agency_Pulse_Admin_Menu_Constants::REST_NAMESPACE,
			Agency_Pulse_Admin_Menu_Constants::REST_APPLY_TEMPLATE,
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( __CLASS__, 'apply_template' ),
				'permission_callback' => array( __CLASS__, 'verify_request' ),
			)
		);

		register_rest_route(
			Agency_Pulse_Admin_Menu_Constants::REST_NAMESPACE,
			Agency_Pulse_Admin_Menu_Constants::REST_RESET_CUSTOMIZATION,
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( __CLASS__, 'reset_customization' ),
				'permission_callback' => array( __CLASS__, 'verify_request' ),
			)
		);
	}

	// -------------------------------------------------------------------------
	// Authentication
	// -------------------------------------------------------------------------

	/**
	 * Verify the HMAC-signed request from the Laravel server.
	 *
	 * Used as permission_callback for both GET and POST routes.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request
	 * @return true|WP_Error
	 */
	public static function verify_request( WP_REST_Request $request ) {
		// 1. Timestamp header — must be present and a positive integer.
		$timestamp_str = $request->get_header( 'X-APWP-Timestamp' );
		if ( null === $timestamp_str || '' === $timestamp_str ) {
			return new WP_Error(
				'missing_timestamp',
				'Missing X-APWP-Timestamp header.',
				array( 'status' => 400 )
			);
		}
		$timestamp = (int) $timestamp_str;
		if ( $timestamp <= 0 ) {
			return new WP_Error(
				'missing_timestamp',
				'Invalid X-APWP-Timestamp value.',
				array( 'status' => 400 )
			);
		}

		// 2. Replay window — ±5 minutes.
		if ( abs( time() - $timestamp ) > 300 ) {
			return new WP_Error(
				'timestamp_out_of_range',
				'Request timestamp is outside the allowed window.',
				array( 'status' => 400 )
			);
		}

		// 3. Extract pk from the stored apwp-ao JWT payload.
		$state = Agency_Pulse_Admin_Menu_License_Manager::get_state();
		$jwt   = $state['jwt'] ?? '';
		if ( empty( $jwt ) ) {
			return new WP_Error(
				'not_connected',
				'Plugin is not connected to the service.',
				array( 'status' => 401 )
			);
		}
		$payload = Agency_Pulse_Admin_Menu_License_Manager::decode_jwt_payload( $jwt );
		$pk      = $payload['pk'] ?? '';
		if ( empty( $pk ) ) {
			return new WP_Error(
				'not_connected',
				'Plugin key not available. Reconnect the plugin.',
				array( 'status' => 401 )
			);
		}

		// 4. Signature header — must be present.
		$received = $request->get_header( 'X-APWP-Signature' );
		if ( null === $received || '' === $received ) {
			return new WP_Error(
				'missing_signature',
				'Missing X-APWP-Signature header.',
				array( 'status' => 400 )
			);
		}

		// 5. Canonical value: timestamp string for GET, raw body for POST.
		$method    = strtoupper( $request->get_method() );
		$canonical = ( 'GET' === $method ) ? $timestamp_str : $request->get_body();

		// 6. Compute expected signature and compare — timing-safe.
		$expected = 'sha256=' . hash_hmac( 'sha256', $canonical, $pk );
		if ( ! hash_equals( $expected, $received ) ) {
			return new WP_Error(
				'bad_signature',
				'Signature verification failed.',
				array( 'status' => 403 )
			);
		}

		return true;
	}

	// -------------------------------------------------------------------------
	// Handlers
	// -------------------------------------------------------------------------

	/**
	 * GET handler — return the current dashboard message option.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function get_message( WP_REST_Request $request ): WP_REST_Response {
		$defaults = array(
			'enabled'    => false,
			'show_first' => false,
			'title'      => '',
			'content'    => '',
		);
		$data = wp_parse_args(
			get_option( Agency_Pulse_Admin_Menu_Constants::OPTION_DASHBOARD_MESSAGE, array() ),
			$defaults
		);
		return new WP_REST_Response( $data, 200 );
	}

	/**
	 * POST handler — update title and content, preserving enabled/show_first.
	 *
	 * Only `title` and `content` are writable from the remote endpoint.
	 * The `enabled` and `show_first` flags remain under local admin control.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response|WP_Error
	 */
	public static function set_message( WP_REST_Request $request ) {
		$body = $request->get_json_params();
		if ( ! is_array( $body ) ) {
			return new WP_Error(
				'bad_request',
				'Invalid or missing JSON body.',
				array( 'status' => 400 )
			);
		}

		$title   = sanitize_text_field( wp_unslash( $body['title']   ?? '' ) );
		$content = wp_kses_post( wp_unslash( $body['content'] ?? '' ) );

		// Preserve existing flags — only overwrite the message fields.
		$current = wp_parse_args(
			get_option( Agency_Pulse_Admin_Menu_Constants::OPTION_DASHBOARD_MESSAGE, array() ),
			array( 'enabled' => false, 'show_first' => false, 'title' => '', 'content' => '' )
		);

		update_option(
			Agency_Pulse_Admin_Menu_Constants::OPTION_DASHBOARD_MESSAGE,
			array_merge( $current, array( 'title' => $title, 'content' => $content ) )
		);

		return new WP_REST_Response( array( 'message' => 'Dashboard message updated.' ), 200 );
	}

	// -------------------------------------------------------------------------
	// apply-template handler
	// -------------------------------------------------------------------------

	/**
	 * POST handler — receive a template payload from the Laravel server and
	 * apply it to this site using the existing import pipeline.
	 *
	 * Authentication is handled by verify_request() (permission_callback).
	 * The raw body signature has already been validated before this runs.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function apply_template( WP_REST_Request $request ): WP_REST_Response {
		$body    = $request->get_json_params();
		$payload = $body['payload'] ?? null;

		error_log( '[apwp-ao] apply_template: received request' );
		error_log( '[apwp-ao] apply_template: raw body length = ' . strlen( $request->get_body() ) );
		error_log( '[apwp-ao] apply_template: payload is_array = ' . ( is_array( $payload ) ? 'yes' : 'no' ) );

		if ( ! is_array( $payload ) ) {
			error_log( '[apwp-ao] apply_template: ERROR — payload missing or not an array' );
			return new WP_REST_Response(
				array( 'success' => false, 'message' => 'Missing payload.' ),
				400
			);
		}

		$top_keys         = implode( ', ', array_keys( $payload ) );
		$configs_count    = isset( $payload['configs'] )          ? count( $payload['configs'] )          : 'MISSING';
		$assignments_count = isset( $payload['role_assignments'] ) ? count( $payload['role_assignments'] ) : 'MISSING';
		$version          = $payload['version'] ?? 'MISSING';

		error_log( '[apwp-ao] apply_template: payload top-level keys = ' . $top_keys );
		error_log( '[apwp-ao] apply_template: payload version = ' . $version );
		error_log( '[apwp-ao] apply_template: configs count = ' . $configs_count );
		error_log( '[apwp-ao] apply_template: role_assignments count = ' . $assignments_count );

		// Re-encode to a JSON string — import_from_json() expects a string.
		$payload_raw = wp_json_encode( $payload );

		error_log( '[apwp-ao] apply_template: calling import_from_json, json length = ' . strlen( $payload_raw ) );

		$result = Agency_Pulse_Admin_Menu::get_instance()
			->get_settings_page()
			->import_from_json( $payload_raw );

		error_log( '[apwp-ao] apply_template: import result type = ' . ( $result['type'] ?? 'unknown' ) );
		error_log( '[apwp-ao] apply_template: import result message = ' . ( $result['message'] ?? '(none)' ) );

		if ( 'error' === $result['type'] ) {
			error_log( '[apwp-ao] apply_template: IMPORT FAILED — ' . $result['message'] );
			return new WP_REST_Response(
				array( 'success' => false, 'message' => $result['message'] ),
				422
			);
		}

		error_log( '[apwp-ao] apply_template: SUCCESS — ' . $result['message'] );

		return new WP_REST_Response(
			array( 'success' => true, 'message' => 'Template applied.' ),
			200
		);
	}

	// -------------------------------------------------------------------------
	// reset-customization handler
	// -------------------------------------------------------------------------

	/**
	 * POST handler — reset all admin customization settings to factory defaults.
	 *
	 * Authentication is handled by verify_request() (permission_callback).
	 * Accepts an optional `reset_menu` boolean in the JSON body.
	 *
	 * @since 1.0.0
	 * @param WP_REST_Request $request
	 * @return WP_REST_Response
	 */
	public static function reset_customization( WP_REST_Request $request ): WP_REST_Response {
		$body         = $request->get_json_params();
		$reset_menu   = ! empty( $body['reset_menu'] );
		$reset_labels = ! empty( $body['reset_labels'] );
		$nuke_menus   = ! empty( $body['nuke_menus'] );

		$reset = Agency_Pulse_Admin_Menu::get_instance()
			->get_settings_page()
			->perform_reset_customization( $reset_menu, $reset_labels, $nuke_menus );

		return new WP_REST_Response(
			array( 'success' => true, 'reset' => $reset ),
			200
		);
	}
}
