<?php

declare(strict_types=1);

namespace LolaCore\Admin;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

use LolaCore\Hub\RemoteAbilityAllowList;
use WP\MCP\Core\McpAdapter;
use WP\MCP\Transport\HttpTransport;
use WP\MCP\Infrastructure\ErrorHandling\ErrorLogMcpErrorHandler;

/**
 * Registers the LolaCore MCP server with the WordPress MCP Adapter v0.5.0+.
 *
 * Uses $adapter->create_server() directly (the public API) to register
 * LolaCore as a custom MCP server namespace at /wp-json/mcp/lolacore.
 *
 * The server exposes the three core MCP Adapter abilities:
 * - discover-abilities: Lists all publicly available WordPress abilities
 * - get-ability-info: Gets detailed information about specific abilities
 * - execute-ability: Executes WordPress abilities with provided parameters
 */
final class McpServerRegistrar
{
    /**
     * MCP server ID and route.
     */
    public const SERVER_ID = 'lolacore';

    private RemoteAbilityAllowList $remoteAllowList;

    public function __construct(?RemoteAbilityAllowList $remoteAllowList = null)
    {
        $this->remoteAllowList = $remoteAllowList ?? new RemoteAbilityAllowList();
    }

    /**
     * Hook into the MCP adapter initialization.
     */
    public function register(): void
    {
        \add_action('mcp_adapter_init', [$this, 'registerServer'], 20);
        
        // Native MCP Adapter filters to restrict exposed abilities to the LolaCore ecosystem
        \add_filter('mcp_adapter_tool_call_result', [$this, 'filterDiscoveredAbilities'], 10, 5);
        \add_filter('mcp_adapter_pre_tool_call', [$this, 'guardAbilityExecution'], 10, 4);
    }

    /**
     * Register the LolaCore MCP server with the adapter.
     *
     * @param McpAdapter $adapter The MCP Adapter singleton instance.
     */
    public function registerServer($adapter): void
    {
        if (!$adapter instanceof McpAdapter) {
            return;
        }

        // Defensive guard against MCP adapter version collisions.
        //
        // WooCommerce (and potentially other plugins) now bundle their own copy of
        // wordpress/mcp-adapter. When two copies coexist, class-resolution order can
        // leave a protocol meta-ability unregistered under the expected name at the
        // moment we create the server. Passing a non-existent ability to
        // create_server() makes the adapter call WP_Abilities_Registry::get_registered(),
        // which fires _doing_it_wrong() and logs an [ERROR] on every admin request.
        // We register only the meta-tools that are actually present, so the server still
        // works and never spams the log. The class-preload in lolacore.php is the primary
        // fix; this is defense in depth.
        $tools = array_values(array_filter(
            [
                'mcp-adapter/discover-abilities',
                'mcp-adapter/get-ability-info',
                'mcp-adapter/execute-ability',
            ],
            [$this, 'abilityIsRegistered']
        ));

        $version = defined('LOLACORE_VERSION') ? LOLACORE_VERSION : '1.5.0';

        $adapter->create_server(
            self::SERVER_ID,           // server_id
            'mcp',                     // server_route_namespace (creates /wp-json/mcp/...)
            self::SERVER_ID,           // server_route (creates .../lolacore)
            'LolaCore',                // server_name
            self::buildInstructions(), // server_description → sent as MCP 'instructions'
            $version,                  // server_version
            [HttpTransport::class],    // mcp_transports
            ErrorLogMcpErrorHandler::class, // error_handler
            null,                      // observability_handler (uses NullMcpObservabilityHandler)
            $tools                     // tools — the core MCP adapter abilities that exist
        );
    }

    /**
     * Checks whether a WordPress ability is registered without triggering the
     * _doing_it_wrong() notice that WP_Abilities_Registry::get_registered() emits
     * for unknown names. Returns false when the Abilities API is unavailable.
     *
     * @param string $name Fully-qualified ability name (e.g. 'mcp-adapter/discover-abilities').
     */
    public function abilityIsRegistered(string $name): bool
    {
        if (!class_exists('\WP_Abilities_Registry')) {
            return false;
        }

        $registry = \WP_Abilities_Registry::get_instance();

        return $registry instanceof \WP_Abilities_Registry && $registry->is_registered($name);
    }

    /**
     * Builds the instruction block sent to MCP agents during the initialize handshake.
     *
     * This text is delivered via the MCP protocol's `instructions` field and becomes
     * part of the connecting agent's context automatically. It teaches any agent
     * (Cursor, Antigravity, Claude Desktop, etc.) how to work with LolaCore's
     * persistent memory, action history, and tool surface — without requiring
     * any client-side configuration.
     *
     * @return string The full instruction block.
     */
    private static function buildInstructions(): string
    {
        $lines = [
            'You are connected to a WordPress site managed by LolaCore, an AI-powered WordPress management system with persistent memory and 350+ abilities.',
            '',
            '## Getting Started',
            'Always call `lolacore/get-site-info` first. It returns:',
            '- Site metadata (WordPress version, theme, plugins, PHP, etc.)',
            '- `memory_context.facts`: the most relevant persistent facts about this site and its owner (preferences, business context, past decisions)',
            '- `memory_context.last_session_summary`: a summary of the last conversation session',
            '',
            'Use this context to personalize your responses and avoid asking questions the site owner has already answered.',
            '',
            '## Persistent Memory',
            'LolaCore maintains a persistent memory database across all sessions and all agents (web chat, MCP, API).',
            '',
            '### Reading Memory',
            '- `lolacore/get-memory-context`: searches memory by keyword (LIKE match). Pass a `prompt` with specific terms.',
            '- The `memory_context` block in `get-site-info` already contains the top-ranked facts — you do not need to call `get-memory-context` separately unless you need to search for something specific.',
            '',
            '### Writing Memory',
            '- `lolacore/add-memory-fact`: saves a new fact. Use `fact_type` to categorize:',
            '  - `user_preference`: owner preferences, communication style, design taste',
            '  - `business_context`: business model, target audience, revenue info',
            '  - `site_state`: technical state, configurations, known issues',
            '  - `decision_history`: decisions made and their rationale',
            '- Set `importance` (1-10) based on long-term relevance. Preferences and decisions are typically 7-9. Transient state is 3-5.',
            '- Save facts when the user expresses a preference, makes a decision, or when you discover something important about the site.',
            '',
            '## Action History',
            '- `lolacore/get-action-history`: shows past actions executed by any agent (web chat or MCP). Check this before performing destructive operations to understand recent changes.',
            '',
            '## Scan Data',
            '- `lolacore/get-scan-data`: returns the latest algorithmic scan of the site (SEO, security, performance findings). Reference this when the user asks about site health.',
            '',
            '## Behavior Guidelines',
            '- All write operations require confirmation from the user — the system handles this automatically.',
            '- When in doubt about a past decision or preference, check memory before asking the user again.',
            '- Actions executed via MCP are logged in the same history as web chat actions — the owner sees a unified timeline.',
        ];

        /**
         * Filters the MCP server instructions sent to connecting agents.
         *
         * Addons can append their own guidance paragraphs to help MCP agents
         * understand addon-specific abilities and workflows.
         *
         * @since 1.5.0
         *
         * @param string $instructions The full instruction text.
         */
        return apply_filters( 'lolacore_mcp_instructions', implode( "\n", $lines ) );
    }

    /**
     * @return array<string>
     */
    private function getAllowedAbilities(): array
    {
        return $this->remoteAllowList->getAllowedAbilities();
    }

    /**
     * Guards execution and info retrieval to prevent accessing unlicensed or third-party abilities.
     *
     * @param array $args The tool arguments.
     * @param string $tool_name The tool name being called.
     * @param mixed $mcp_tool The MCP tool instance.
     * @param mixed $server The MCP server instance.
     * @return array|\WP_Error Arguments or WP_Error to block execution.
     */
    public function guardAbilityExecution(array $args, string $tool_name, $mcp_tool, $server = null)
    {
        // Only apply LolaCore restrictions to the LolaCore MCP server
        if ($server && method_exists($server, 'get_server_id') && $server->get_server_id() !== self::SERVER_ID) {
            return $args;
        }

        // We only care about filtering the abilities being passed to execute or get-info.
        if (!in_array($tool_name, ['mcp-adapter-execute-ability', 'mcp-adapter-get-ability-info'], true)) {
            return $args;
        }

        $ability_name = $args['ability_name'] ?? '';
        if (empty($ability_name)) {
            return $args; // Let the underlying tool handle missing arguments
        }

        $allowed_abilities = $this->getAllowedAbilities();
        if (in_array($ability_name, $allowed_abilities, true)) {
            return $args;
        }

        return new \WP_Error(
            'ability_not_allowed',
            sprintf('The ability "%s" is not registered in the LolaCore ecosystem or requires a valid license.', $ability_name)
        );
    }

    /**
     * Filters the result of discover-abilities to only include LolaCore ecosystem abilities.
     *
     * @param mixed $result The raw execution result.
     * @param array $args The tool arguments used.
     * @param string $tool_name The tool name that was called.
     * @param mixed $mcp_tool The MCP tool instance.
     * @param mixed $server The MCP server instance.
     * @return mixed Filtered result.
     */
    public function filterDiscoveredAbilities($result, array $args, string $tool_name, $mcp_tool = null, $server = null)
    {
        // Only apply LolaCore restrictions to the LolaCore MCP server
        if ($server && method_exists($server, 'get_server_id') && $server->get_server_id() !== self::SERVER_ID) {
            return $result;
        }

        if ($tool_name !== 'mcp-adapter-discover-abilities' || !is_array($result) || !isset($result['abilities'])) {
            return $result;
        }

        $allowed_abilities = $this->getAllowedAbilities();
        
        $result['abilities'] = array_values(array_filter($result['abilities'], function($ability) use ($allowed_abilities) {
            $ability_name = is_array($ability) ? ($ability['name'] ?? '') : '';
            return in_array($ability_name, $allowed_abilities, true);
        }));

        return $result;
    }
}
