<?php

/**
 * MCP Service
 * 
 * Handles communication with MCP_2025_SERVICE microservice
 * Provides centralized HTTP operations with retry logic and error handling using Guzzle
 */
class McpService
{
    private $client;
    private $defaultMaxRetries;
    private $logger;

    public function __construct($logger = null, $maxRetries = 10)
    {
        $baseUrl = getenv('MCP_2025_SERVICE_URL');
        $this->defaultMaxRetries = $maxRetries;
        $this->logger = $logger;

        if (empty($baseUrl)) {
            throw new CustomErrorException(BaseResponse::DATA_NOT_AVAILABLE, 'MCP_2025_SERVICE_URL environment variable is not set');
        }

        $this->client = new \GuzzleHttp\Client(['base_uri' => $baseUrl]);
    }

    /**
     * Execute GET request to MCP microservice
     * 
     * @param string $endpoint API endpoint (e.g., '/api/hierarchy')
     * @param array $params Query parameters
     * @param int|null $maxRetries Maximum retry attempts (uses default if null)
     * @param bool $withLogger Whether to use logger
     * @return object Response object with success, data, and message properties
     * @throws Exception
     */
    public function get($endpoint, $params = [], $maxRetries = null, $withLogger = false)
    {
        $options = [];
        if (!empty($params)) {
            $options['query'] = $params;
        }

        return $this->executeRequest('GET', $endpoint, $options, $maxRetries, $withLogger);
    }

    /**
     * Execute POST request to MCP microservice
     * 
     * @param string $endpoint API endpoint
     * @param array $data Request body data
     * @param array $params Query parameters
     * @param int|null $maxRetries Maximum retry attempts (defaults to 1 for POST to avoid duplicate operations)
     * @param bool $withLogger Whether to use logger
     * @return object Response object with success, data, and message properties
     * @throws Exception
     */
    public function post($endpoint, $data = [], $params = [], $maxRetries = null, $withLogger = false)
    {
        $options = [];
        if (!empty($params)) {
            $options['query'] = $params;
        }
        if (!empty($data)) {
            $options['json'] = $data;
        }

        // Default to 1 retry for POST requests to avoid duplicate operations
        $postMaxRetries = $maxRetries ?? 1;

        return $this->executeRequest('POST', $endpoint, $options, $postMaxRetries, $withLogger);
    }

    /**
     * Execute HTTP request with retry logic using Guzzle
     * 
     * @param string $method HTTP method (GET, POST)
     * @param string $endpoint API endpoint
     * @param array $options Guzzle request options
     * @param int|null $maxRetries Maximum retry attempts
     * @param bool $withLogger Whether to use logger
     * @return object Response object with success, data, and message properties
     * @throws Exception
     */
    private function executeRequest($method, $endpoint, $options = [], $maxRetries = null, $withLogger = false)
    {
        $maxRetries = $maxRetries ?? $this->defaultMaxRetries;
        $tries = 0;
        $lastException = null;

        while ($tries < $maxRetries) {
            try {
                $response = $this->client->request($method, $endpoint, $options);
                $statusCode = $response->getStatusCode();
                $body = $response->getBody()->getContents();
                $decodedResponse = json_decode($body);

                if ($withLogger && $this->logger) {
                    $this->logger->info("MCP Service {$method} request to {$endpoint} successful", [
                        'status_code' => $statusCode,
                        'response' => $decodedResponse
                    ]);
                }

                return $this->formatResponse($decodedResponse, $statusCode);
            } catch (\GuzzleHttp\Exception\RequestException $e) {
                $lastException = $e;
                $tries++;

                // For HTTP errors (4xx, 5xx), extract response and return it instead of retrying
                if ($e->hasResponse()) {
                    $response = $e->getResponse();
                    $statusCode = $response->getStatusCode();
                    $responseBody = $response->getBody()->getContents();
                    $decodedResponse = json_decode($responseBody);

                    if ($withLogger && $this->logger) {
                        $this->logger->warning("MCP Service {$method} request to {$endpoint} returned HTTP error {$statusCode}", [
                            'status_code' => $statusCode,
                            'response' => $decodedResponse,
                            'endpoint' => $endpoint
                        ]);
                    }

                    // Return the error response with status code instead of throwing exception
                    return $this->formatResponse($decodedResponse, $statusCode);
                }

                if ($withLogger && $this->logger) {
                    $this->logger->warning("MCP Service {$method} request to {$endpoint} failed (attempt {$tries})", [
                        'error' => $e->getMessage(),
                        'endpoint' => $endpoint,
                        'options' => $options
                    ]);
                }

                // If we've reached max retries, throw the last exception
                if ($tries >= $maxRetries) {
                    // Extract just the response body if available, without exposing the URL
                    $errorMessage = "MCP Service request failed after {$maxRetries} attempts";
                    if ($e->hasResponse()) {
                        $responseBody = $e->getResponse()->getBody()->getContents();
                        if (!empty($responseBody)) {
                            $errorMessage .= ": " . $responseBody;
                        }
                    }
                    throw new CustomErrorException(BaseResponse::INVALID_REQUEST, $errorMessage);
                }

                // Add delay between retries
                if ($tries > 1) {
                    usleep(100000); // 100ms delay
                }
            } catch (Exception $e) {
                $lastException = $e;
                $tries++;

                if ($withLogger && $this->logger) {
                    $this->logger->warning("MCP Service {$method} request to {$endpoint} failed (attempt {$tries})", [
                        'error' => $e->getMessage()
                    ]);
                }

                // If we've reached max retries, throw the last exception
                if ($tries >= $maxRetries) {
                    throw $e;
                }

                // Add delay between retries
                if ($tries > 1) {
                    usleep(100000); // 100ms delay
                }
            }
        }

        // This should not be reached due to the exception throwing above
        $errorMessage = $lastException ? $lastException->getMessage() : 'Failed to get response from MCP service after ' . $maxRetries . ' attempts';
        throw new CustomErrorException(BaseResponse::INVALID_REQUEST, $errorMessage);
    }

    /**
     * Format response object with consistent structure
     * 
     * @param mixed $responseData Decoded JSON response
     * @param int $statusCode HTTP status code
     * @return object Formatted response with success, data, message, and status_code properties
     */
    private function formatResponse($responseData, $statusCode = 200)
    {
        $response = new \stdClass();
        $response->success = is_object($responseData) &&
            property_exists($responseData, "success") &&
            $responseData->success;
        $response->data = is_object($responseData) &&
            property_exists($responseData, "data") ?
            $responseData->data : null;
        $response->message = is_object($responseData) &&
            property_exists($responseData, "message") ?
            $responseData->message : '';
        $response->status_code = $statusCode;

        return $response;
    }

    /**
     * Get base URL for the MCP service
     * 
     * @return string Base URL
     */
    public function getBaseUrl()
    {
        return $this->client->getConfig('base_uri');
    }

    /**
     * Execute GET request and return SimpleResponse object
     * 
     * @param string $endpoint API endpoint (e.g., '/api/hierarchy')
     * @param array $params Query parameters
     * @param int|null $maxRetries Maximum retry attempts (uses default if null)
     * @param bool $withLogger Whether to use logger
     * @return SimpleResponse Complete response object ready to return from controller
     * @throws Exception
     */
    public function getResponse($endpoint, $params = [], $maxRetries = null, $withLogger = false)
    {
        $result = $this->get($endpoint, $params, $maxRetries, $withLogger);

        $response = $result->success ? new SimpleResponse($result->data) : new SimpleResponse(null);
        $response->setStatus($result->success);
        $response->setMessage($result->message);

        if (property_exists($result, 'status_code') && $result->status_code !== 200) {
            $response->setStatusCode($result->status_code);
        }

        return $response;
    }

    /**
     * Execute POST request and return SimpleResponse object
     * 
     * @param string $endpoint API endpoint
     * @param array $data Request body data
     * @param array $params Query parameters
     * @param int|null $maxRetries Maximum retry attempts (defaults to 1 for POST to avoid duplicate operations)
     * @param bool $withLogger Whether to use logger
     * @return SimpleResponse Complete response object ready to return from controller
     * @throws Exception
     */
    public function postResponse($endpoint, $data = [], $params = [], $maxRetries = null, $withLogger = false)
    {
        try {
            $result = $this->post($endpoint, $data, $params, $maxRetries, $withLogger);
            $response = $result->success ? new SimpleResponse($result->data) : new SimpleResponse(null);
            $response->setStatus($result->success);
            $response->setMessage($result->message);

            if (property_exists($result, 'status_code') && $result->status_code !== 200) {
                $response->setStatusCode($result->status_code);
            }

            return $response;
        } catch (Exception $e) {
            throw new CustomErrorException(BaseResponse::DATA_NOT_AVAILABLE, $e->getMessage());
        }
    }

    /**
     * Set logger instance
     * 
     * @param mixed $logger Logger instance
     * @return void
     */
    public function setLogger($logger)
    {
        $this->logger = $logger;
    }
}
