<?php
/**
 * MCP server. Implements MCP wire protocol over JSON-RPC 2.0 + HTTP.
 *
 * In v0.3+ the public Internet client (Claude / ChatGPT / Cursor) talks to the
 * PressConduit relay, which forwards already-authenticated JSON-RPC requests
 * to this endpoint with the plugin's bearer token attached. We do not handle
 * OAuth here — the relay owns that.
 *
 * Endpoint:  POST /wp-json/pressconduit/v1/mcp
 * Auth:      Authorization: Bearer <plugin token>
 */

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

class PCD_PC_MCP {

	const PROTOCOL_VERSION = '2025-03-26';

	public static function init(): void {
		add_action( 'rest_api_init', [ __CLASS__, 'register_routes' ] );
	}

	public static function register_routes(): void {
		register_rest_route( 'pressconduit/v1', '/mcp', [
			'methods'             => [ 'POST', 'GET' ],
			'callback'            => [ __CLASS__, 'handle' ],
			'permission_callback' => '__return_true', // Auth happens inside handler.
		] );

		// Liveness probe — no auth required.
		register_rest_route( 'pressconduit/v1', '/info', [
			'methods'             => 'GET',
			'callback'            => [ __CLASS__, 'info' ],
			'permission_callback' => '__return_true',
		] );

		// Update broadcast webhook — the backend calls this to push update notifications
		// without waiting for WordPress's twice-daily cron. Auth: same bearer token as MCP.
		register_rest_route( 'pressconduit/v1', '/update-check', [
			'methods'             => 'POST',
			'callback'            => [ __CLASS__, 'handle_update_check' ],
			'permission_callback' => '__return_true',
		] );
	}

	public static function handle_update_check( WP_REST_Request $request ) {
		$verified = PCD_PC_Auth::verify( $request );
		if ( is_wp_error( $verified ) ) {
			return new WP_REST_Response( [ 'error' => 'unauthorized' ], 401 );
		}
		if ( ! class_exists( 'PCD_PC_Updater' ) ) {
			return new WP_REST_Response( [ 'error' => 'no_updater' ], 500 );
		}
		$manifest = PCD_PC_Updater::force_check();
		return [
			'checked' => true,
			'latest'  => $manifest['version'] ?? null,
		];
	}

	public static function info(): array {
		return [
			'name'    => 'PressConduit',
			'version' => PCD_PC_VERSION,
			'mcp'     => [
				'endpoint' => rest_url( 'pressconduit/v1/mcp' ),
				'protocol' => self::PROTOCOL_VERSION,
				'auth'     => 'bearer',
			],
		];
	}

	public static function handle( WP_REST_Request $request ) {
		// GET is non-compliant per Streamable HTTP unless we offer SSE.
		if ( 'GET' === $request->get_method() ) {
			$resp = new WP_REST_Response( [ 'error' => 'method_not_allowed' ], 405 );
			$resp->header( 'Allow', 'POST' );
			return $resp;
		}

		$verified = PCD_PC_Auth::verify( $request );
		if ( is_wp_error( $verified ) ) {
			return new WP_REST_Response(
				[
					'jsonrpc' => '2.0',
					'id'      => null,
					'error'   => [ 'code' => -32001, 'message' => $verified->get_error_message() ],
				],
				401
			);
		}

		$body = $request->get_json_params();
		if ( ! is_array( $body ) ) {
			return self::jsonrpc_error( null, -32700, 'Parse error.', 400 );
		}

		// Batch.
		if ( isset( $body[0] ) ) {
			$out = [];
			foreach ( $body as $item ) {
				$resp = self::dispatch( $item );
				if ( null !== $resp ) {
					$out[] = $resp;
				}
			}
			return $out;
		}

		$resp = self::dispatch( $body );
		return null === $resp ? new WP_REST_Response( null, 204 ) : $resp;
	}

	private static function dispatch( array $msg ) {
		$id     = $msg['id'] ?? null;
		$method = $msg['method'] ?? '';
		$params = $msg['params'] ?? [];

		$is_notification = ! array_key_exists( 'id', $msg );

		switch ( $method ) {
			case 'initialize':
				$client_version = $params['protocolVersion'] ?? '';
				$known          = [ '2024-11-05', '2025-03-26', '2025-06-18' ];
				$negotiated     = in_array( $client_version, $known, true ) ? $client_version : self::PROTOCOL_VERSION;

				return self::jsonrpc_ok( $id, [
					'protocolVersion' => $negotiated,
					'capabilities'    => [
						'tools' => new stdClass(),
					],
					'serverInfo' => [
						'name'    => 'pressconduit',
						'version' => PCD_PC_VERSION,
					],
				] );

			case 'notifications/initialized':
				return null;

			case 'ping':
				return self::jsonrpc_ok( $id, new stdClass() );

			case 'tools/list':
				return self::jsonrpc_ok( $id, [
					'tools' => PCD_PC_Tools::list_for_mcp(),
				] );

			case 'tools/call':
				$name = $params['name'] ?? '';
				$args = (array) ( $params['arguments'] ?? [] );

				if ( ! $name ) {
					return self::jsonrpc_error( $id, -32602, 'Missing tool name.' );
				}

				$result = PCD_PC_Tools::call( $name, $args );

				if ( is_wp_error( $result ) ) {
					return self::jsonrpc_ok( $id, [
						'isError' => true,
						'content' => [ [
							'type' => 'text',
							'text' => $result->get_error_message(),
						] ],
					] );
				}

				return self::jsonrpc_ok( $id, [
					'content' => [ [
						'type' => 'text',
						'text' => is_string( $result ) ? $result : wp_json_encode( $result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ),
					] ],
				] );
		}

		if ( $is_notification ) {
			return null;
		}

		return self::jsonrpc_error( $id, -32601, "Method not found: {$method}" );
	}

	private static function jsonrpc_ok( $id, $result ): array {
		return [
			'jsonrpc' => '2.0',
			'id'      => $id,
			'result'  => $result,
		];
	}

	private static function jsonrpc_error( $id, int $code, string $message, int $http = 200 ) {
		$payload = [
			'jsonrpc' => '2.0',
			'id'      => $id,
			'error'   => [
				'code'    => $code,
				'message' => $message,
			],
		];
		return 200 === $http ? $payload : new WP_REST_Response( $payload, $http );
	}
}
