<?php
/**
 * MCP (Model Context Protocol) Controller
 * Handles JSON-RPC 2.0 requests for pricing information via HTTP endpoint
 * 
 * @package PortalPeople\mcp\controllers
 * @author Portal People Development Team
 */

namespace PortalPeople\mcp\controllers;

use Yii;
use yii\web\Controller;
use yii\web\Response;
use PortalPeople\mcp\components\JsonRpc2Validator;
use PortalPeople\mcp\components\PricingService;

/**
 * McpController provides Model Context Protocol endpoints
 * Exposes pricing tools and resources via JSON-RPC 2.0
 * 
 * Endpoints:
 * - POST /mcp/rpc - Main JSON-RPC 2.0 endpoint
 */
class McpController extends Controller
{
    /**
     * Disable CSRF validation for RPC endpoint
     * @var bool
     */
    public $enableCsrfValidation = false;
    
    /**
     * @var PricingService
     */
    private $pricingService;

    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [
            [
                'class' => 'yii\filters\ContentNegotiator',
                'formats' => [
                    'application/json' => Response::FORMAT_JSON,
                ],
            ],
            [
                'class' => 'yii\filters\Cors',
                'cors' => [
                    'Origin' => ['*'],
                    'Access-Control-Request-Method' => ['POST', 'OPTIONS'],
                    'Access-Control-Request-Headers' => ['*'],
                    'Access-Control-Allow-Credentials' => false,
                    'Access-Control-Max-Age' => 3600,
                ],
            ],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function init()
    {
        parent::init();
        $this->pricingService = new PricingService();
    }

    /**
     * Main JSON-RPC 2.0 endpoint
     * Handles all MCP protocol requests
     * 
     * @return array JSON-RPC 2.0 response
     */
    public function actionRpc()
    {
        Yii::$app->response->format = Response::FORMAT_JSON;
        
        $rawBody = Yii::$app->request->getRawBody();
        
        // Log incoming request for debugging
        Yii::info('MCP Request: ' . $rawBody, __METHOD__);
        
        // Validate incoming request
        $validation = JsonRpc2Validator::validateRequest($rawBody);
        
        if (!$validation['valid']) {
            $response = JsonRpc2Validator::createErrorResponse(
                JsonRpc2Validator::ERROR_INVALID_REQUEST,
                'Invalid JSON-RPC 2.0 Request',
                $validation['errors'],
                null
            );
            Yii::warning('Invalid request: ' . json_encode($validation['errors']), __METHOD__);
            return $response;
        }
        
        $request = json_decode($rawBody, true);
        $response = $this->handleRequest($request);
        
        // Validate outgoing response
        $responseValidation = JsonRpc2Validator::validateAndFormat($response, false);
        if ($responseValidation === false) {
            // If our response is invalid, return internal error
            return JsonRpc2Validator::createErrorResponse(
                JsonRpc2Validator::ERROR_INTERNAL,
                'Internal server error - invalid response format',
                null,
                $request['id'] ?? null
            );
        }
        
        // Log successful response
        Yii::info('MCP Response: ' . json_encode($response), __METHOD__);
        
        return $response;
    }

    /**
     * Handle JSON-RPC request and route to appropriate method
     * 
     * @param array $request The JSON-RPC request
     * @return array The JSON-RPC response
     */
    protected function handleRequest(array $request): array
    {
        $method = $request['method'] ?? '';
        $params = $request['params'] ?? [];
        $id = $request['id'] ?? null;
        
        try {
            switch ($method) {
                case 'initialize':
                    return $this->handleInitialize($params, $id);
                    
                case 'tools/list':
                    return $this->listTools($id);
                    
                case 'tools/call':
                    return $this->callTool($params, $id);
                    
                case 'resources/list':
                    return $this->listResources($id);
                    
                case 'resources/read':
                    return $this->readResource($params, $id);
                    
                case 'prompts/list':
                    return $this->listPrompts($id);
                    
                default:
                    return JsonRpc2Validator::createErrorResponse(
                        JsonRpc2Validator::ERROR_METHOD_NOT_FOUND,
                        "Method '{$method}' not found",
                        null,
                        $id
                    );
            }
        } catch (\Exception $e) {
            Yii::error($e->getMessage() . "\n" . $e->getTraceAsString(), __METHOD__);
            return JsonRpc2Validator::createErrorResponse(
                JsonRpc2Validator::ERROR_INTERNAL,
                'Internal error',
                YII_DEBUG ? $e->getMessage() : null,
                $id
            );
        }
    }

    /**
     * Handle MCP initialization
     * 
     * @param array $params
     * @param mixed $id
     * @return array
     */
    protected function handleInitialize(array $params, $id): array
    {
        return JsonRpc2Validator::createSuccessResponse([
            'protocolVersion' => '2024-11-05',
            'capabilities' => [
                'tools' => ['listChanged' => true],
                'resources' => ['subscribe' => true, 'listChanged' => true],
                'prompts' => ['listChanged' => false]
            ],
            'serverInfo' => [
                'name' => 'pricing-server',
                'version' => '1.0.0',
                'description' => 'Portal People Pricing MCP Server'
            ]
        ], $id);
    }

    /**
     * List available tools
     * 
     * @param mixed $id
     * @return array
     */
    protected function listTools($id): array
    {
        $tools = [
            [
                'name' => 'get_price',
                'description' => 'Get pricing information for a specific item/product',
                'inputSchema' => [
                    'type' => 'object',
                    'properties' => [
                        'item_id' => [
                            'type' => 'string',
                            'description' => 'The item/product ID to get pricing for'
                        ],
                        'user_id' => [
                            'type' => 'string',
                            'description' => 'Optional user ID to get user-specific pricing'
                        ],
                        'account_code' => [
                            'type' => 'string',
                            'description' => 'Optional account code to get account-specific pricing'
                        ]
                    ],
                    'required' => ['item_id']
                ]
            ],
            [
                'name' => 'get_prices_bulk',
                'description' => 'Get pricing information for multiple items at once',
                'inputSchema' => [
                    'type' => 'object',
                    'properties' => [
                        'item_ids' => [
                            'type' => 'array',
                            'items' => ['type' => 'string'],
                            'description' => 'Array of item/product IDs'
                        ],
                        'user_id' => [
                            'type' => 'string',
                            'description' => 'Optional user ID for pricing'
                        ]
                    ],
                    'required' => ['item_ids']
                ]
            ],
            [
                'name' => 'search_prices',
                'description' => 'Search for products with pricing based on criteria',
                'inputSchema' => [
                    'type' => 'object',
                    'properties' => [
                        'category' => [
                            'type' => 'string',
                            'description' => 'Filter by product category'
                        ],
                        'min_price' => [
                            'type' => 'number',
                            'description' => 'Minimum price filter'
                        ],
                        'max_price' => [
                            'type' => 'number',
                            'description' => 'Maximum price filter'
                        ],
                        'user_id' => [
                            'type' => 'string',
                            'description' => 'User ID for pricing context'
                        ],
                        'limit' => [
                            'type' => 'integer',
                            'description' => 'Maximum number of results (default: 50)',
                            'default' => 50
                        ]
                    ]
                ]
            ],
            [
                'name' => 'get_account_info',
                'description' => 'Get account information including price band for a user',
                'inputSchema' => [
                    'type' => 'object',
                    'properties' => [
                        'user_id' => [
                            'type' => 'string',
                            'description' => 'User ID to get account info for'
                        ]
                    ],
                    'required' => ['user_id']
                ]
            ],
            [
                'name' => 'composer_update',
                'description' => 'Run composer update for a specific package via ngauge-packagemanager',
                'inputSchema' => [
                    'type' => 'object',
                    'properties' => [
                        'package' => [
                            'type' => 'string',
                            'description' => 'Composer package name in vendor/package format'
                        ],
                        'dry_run' => [
                            'type' => 'boolean',
                            'description' => 'When true, runs composer update in dry-run mode',
                            'default' => false
                        ]
                    ],
                    'required' => ['package']
                ]
            ],
            [
                'name' => 'composer_update_all',
                'description' => 'Run composer update for all packages via ngauge-packagemanager',
                'inputSchema' => [
                    'type' => 'object',
                    'properties' => [
                        'dry_run' => [
                            'type' => 'boolean',
                            'description' => 'When true, runs composer update in dry-run mode',
                            'default' => false
                        ]
                    ]
                ]
            ]
        ];
        
        return JsonRpc2Validator::createSuccessResponse(['tools' => $tools], $id);
    }

    /**
     * Execute a tool
     * 
     * @param array $params
     * @param mixed $id
     * @return array
     */
    protected function callTool(array $params, $id): array
    {
        $toolName = $params['name'] ?? '';
        $args = $params['arguments'] ?? [];
        
        switch ($toolName) {
            case 'get_price':
                if (empty($args['item_id'])) {
                    return JsonRpc2Validator::createErrorResponse(
                        JsonRpc2Validator::ERROR_INVALID_PARAMS,
                        'Missing required parameter: item_id',
                        null,
                        $id
                    );
                }
                
                $result = $this->pricingService->getPrice(
                    $args['item_id'],
                    $args['user_id'] ?? null,
                    $args['account_code'] ?? null
                );
                
                return JsonRpc2Validator::createSuccessResponse([
                    'content' => [
                        [
                            'type' => 'text',
                            'text' => json_encode($result, JSON_PRETTY_PRINT)
                        ]
                    ]
                ], $id);
                
            case 'get_prices_bulk':
                if (empty($args['item_ids']) || !is_array($args['item_ids'])) {
                    return JsonRpc2Validator::createErrorResponse(
                        JsonRpc2Validator::ERROR_INVALID_PARAMS,
                        'Missing or invalid parameter: item_ids (must be array)',
                        null,
                        $id
                    );
                }
                
                $result = $this->pricingService->getPricesBulk(
                    $args['item_ids'],
                    $args['user_id'] ?? null
                );
                
                return JsonRpc2Validator::createSuccessResponse([
                    'content' => [
                        [
                            'type' => 'text',
                            'text' => json_encode($result, JSON_PRETTY_PRINT)
                        ]
                    ]
                ], $id);
                
            case 'search_prices':
                $result = $this->pricingService->searchPrices(
                    $args,
                    $args['user_id'] ?? null,
                    $args['limit'] ?? 50
                );
                
                return JsonRpc2Validator::createSuccessResponse([
                    'content' => [
                        [
                            'type' => 'text',
                            'text' => json_encode($result, JSON_PRETTY_PRINT)
                        ]
                    ]
                ], $id);
                
            case 'get_account_info':
                if (empty($args['user_id'])) {
                    return JsonRpc2Validator::createErrorResponse(
                        JsonRpc2Validator::ERROR_INVALID_PARAMS,
                        'Missing required parameter: user_id',
                        null,
                        $id
                    );
                }
                
                $result = $this->pricingService->getAccountInfo($args['user_id']);
                
                if ($result === null) {
                    return JsonRpc2Validator::createErrorResponse(
                        JsonRpc2Validator::ERROR_INVALID_PARAMS,
                        'User not found or has no account',
                        null,
                        $id
                    );
                }
                
                return JsonRpc2Validator::createSuccessResponse([
                    'content' => [
                        [
                            'type' => 'text',
                            'text' => json_encode($result, JSON_PRETTY_PRINT)
                        ]
                    ]
                ], $id);

            case 'composer_update':
                if (empty($args['package']) || !is_string($args['package'])) {
                    return JsonRpc2Validator::createErrorResponse(
                        JsonRpc2Validator::ERROR_INVALID_PARAMS,
                        'Missing required parameter: package',
                        null,
                        $id
                    );
                }

                $dryRun = $this->parseDryRun($args['dry_run'] ?? false);

                try {
                    $result = $this->runPackageManagerUpdate($args['package'], $dryRun);
                } catch (\InvalidArgumentException $e) {
                    return JsonRpc2Validator::createErrorResponse(
                        JsonRpc2Validator::ERROR_INVALID_PARAMS,
                        $e->getMessage(),
                        null,
                        $id
                    );
                } catch (\Throwable $e) {
                    return JsonRpc2Validator::createErrorResponse(
                        JsonRpc2Validator::ERROR_INTERNAL,
                        $e->getMessage(),
                        null,
                        $id
                    );
                }

                if (!is_array($result) || ($result['success'] ?? false) !== true) {
                    return JsonRpc2Validator::createErrorResponse(
                        JsonRpc2Validator::ERROR_INTERNAL,
                        $result['message'] ?? 'Composer update failed',
                        $result,
                        $id
                    );
                }

                return JsonRpc2Validator::createSuccessResponse([
                    'content' => [
                        [
                            'type' => 'text',
                            'text' => json_encode($result, JSON_PRETTY_PRINT)
                        ]
                    ]
                ], $id);

            case 'composer_update_all':
                $dryRun = $this->parseDryRun($args['dry_run'] ?? false);

                try {
                    $result = $this->runPackageManagerUpdateAll($dryRun);
                } catch (\Throwable $e) {
                    return JsonRpc2Validator::createErrorResponse(
                        JsonRpc2Validator::ERROR_INTERNAL,
                        $e->getMessage(),
                        null,
                        $id
                    );
                }

                if (!is_array($result) || ($result['success'] ?? false) !== true) {
                    return JsonRpc2Validator::createErrorResponse(
                        JsonRpc2Validator::ERROR_INTERNAL,
                        $result['message'] ?? 'Composer update all failed',
                        $result,
                        $id
                    );
                }

                return JsonRpc2Validator::createSuccessResponse([
                    'content' => [
                        [
                            'type' => 'text',
                            'text' => json_encode($result, JSON_PRETTY_PRINT)
                        ]
                    ]
                ], $id);
                
            default:
                return JsonRpc2Validator::createErrorResponse(
                    JsonRpc2Validator::ERROR_METHOD_NOT_FOUND,
                    "Tool '{$toolName}' not found",
                    null,
                    $id
                );
        }
    }

    /**
     * List available resources
     * 
     * @param mixed $id
     * @return array
     */
    protected function listResources($id): array
    {
        $resources = [
            [
                'uri' => 'price://item/{item_id}',
                'name' => 'Item Price',
                'description' => 'Price information for a specific item',
                'mimeType' => 'application/json'
            ],
            [
                'uri' => 'price://catalog',
                'name' => 'Price Catalog',
                'description' => 'Complete price catalog',
                'mimeType' => 'application/json'
            ]
        ];
        
        return JsonRpc2Validator::createSuccessResponse(['resources' => $resources], $id);
    }

    /**
     * Read a resource
     * 
     * @param array $params
     * @param mixed $id
     * @return array
     */
    protected function readResource(array $params, $id): array
    {
        $uri = $params['uri'] ?? '';
        
        if (preg_match('#price://item/(.+)#', $uri, $matches)) {
            $itemId = $matches[1];
            $priceData = $this->pricingService->getPrice($itemId);
            
            return JsonRpc2Validator::createSuccessResponse([
                'contents' => [
                    [
                        'uri' => $uri,
                        'mimeType' => 'application/json',
                        'text' => json_encode($priceData, JSON_PRETTY_PRINT)
                    ]
                ]
            ], $id);
        }
        
        return JsonRpc2Validator::createErrorResponse(
            JsonRpc2Validator::ERROR_INVALID_PARAMS,
            "Resource not found: {$uri}",
            null,
            $id
        );
    }

    /**
     * List available prompts
     * 
     * @param mixed $id
     * @return array
     */
    protected function listPrompts($id): array
    {
        return JsonRpc2Validator::createSuccessResponse(['prompts' => []], $id);
    }

    protected function runPackageManagerUpdate(string $packageName, bool $dryRun = false): array
    {
        $packageName = trim($packageName);

        if (!preg_match('/^[a-z0-9][a-z0-9_.-]*\/[a-z0-9][a-z0-9_.-]*$/i', $packageName)) {
            throw new \InvalidArgumentException('Invalid package format. Use vendor/package.');
        }

        if (!Yii::$app->has('user') || Yii::$app->user->isGuest) {
            throw new \RuntimeException('Authentication required for composer update.');
        }

        $request = Yii::$app->request;
        $originalBodyParams = $request->getBodyParams();
        $packageArg = $dryRun ? $packageName . ' --dry-run' : $packageName;

        try {
            $request->setBodyParams(['package' => $packageArg]);
            $result = Yii::$app->runAction('packagemanager/package-manager/update');
        } finally {
            $request->setBodyParams(is_array($originalBodyParams) ? $originalBodyParams : []);
        }

        return is_array($result) ? $result : [
            'success' => false,
            'message' => 'Unexpected response from package manager update action.',
            'output' => $result,
        ];
    }

    protected function runPackageManagerUpdateAll(bool $dryRun = false): array
    {
        if (!Yii::$app->has('user') || Yii::$app->user->isGuest) {
            throw new \RuntimeException('Authentication required for composer update.');
        }

        $request = Yii::$app->request;
        $originalBodyParams = $request->getBodyParams();
        $packageArg = $dryRun ? '--dry-run' : '--with-all-dependencies';

        try {
            $request->setBodyParams(['package' => $packageArg]);
            $result = Yii::$app->runAction('packagemanager/package-manager/update');
        } finally {
            $request->setBodyParams(is_array($originalBodyParams) ? $originalBodyParams : []);
        }

        return is_array($result) ? $result : [
            'success' => false,
            'message' => 'Unexpected response from package manager update action.',
            'output' => $result,
        ];
    }

    protected function parseDryRun($value): bool
    {
        $parsed = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
        return $parsed ?? false;
    }
}
