<?php

namespace AIPT\Api\Chatbot\Exceptions;

/**
 * Factory for creating appropriate chatbot exceptions from AI provider errors.
 *
 * Parses error messages and HTTP status codes to determine the correct
 * exception type for proper error handling and recovery strategies.
 *
 * @package AIPT\Api\Chatbot\Exceptions
 * @since 2.5.0
 */
class ExceptionFactory {

    /**
     * Rate limit error indicators.
     *
     * @var array
     */
    private static $rate_limit_indicators = [
        'rate limit',
        'rate_limit',
        'too many requests',
        'quota exceeded',
        'quota_exceeded',
        'resource_exhausted',
        'overloaded',
    ];

    /**
     * Authentication error indicators.
     *
     * @var array
     */
    private static $auth_indicators = [
        'invalid api key',
        'invalid_api_key',
        'api key invalid',
        'authentication',
        'unauthorized',
        'permission denied',
        'access denied',
        'invalid credentials',
        'api_key_invalid',
    ];

    /**
     * Service unavailable error indicators.
     *
     * @var array
     */
    private static $service_indicators = [
        'service unavailable',
        'service_unavailable',
        'internal server error',
        'bad gateway',
        'gateway timeout',
        'server error',
        'temporarily unavailable',
        'model is overloaded',
        'server_error',
    ];

    /**
     * Content filter error indicators.
     *
     * @var array
     */
    private static $content_filter_indicators = [
        'content_filter',
        'content filter',
        'safety',
        'harmful',
        'policy',
        'moderation',
        'blocked',
        'inappropriate',
        'violates',
    ];

    /**
     * Invalid request error indicators.
     *
     * @var array
     */
    private static $invalid_request_indicators = [
        'invalid request',
        'invalid_request',
        'bad request',
        'malformed',
        'invalid parameter',
        'missing parameter',
        'context_length_exceeded',
        'context length',
        'token limit',
        'max_tokens',
    ];

    /**
     * Create an exception from an error message and optional HTTP status code.
     *
     * @param string $error_message Error message from AI provider.
     * @param int    $http_status   HTTP status code (0 if unknown).
     * @param string $provider      AI provider name.
     * @return ChatbotException Appropriate exception instance.
     */
    public static function fromError(string $error_message, int $http_status = 0, string $provider = ''): ChatbotException {
        $error_lower = strtolower($error_message);

        if ($http_status === 429) {
            $retry_after = self::parseRetryAfter($error_message);
            return new RateLimitException($error_message, $retry_after);
        }

		if ($http_status === 401 || $http_status === 403) {
			return new ProviderAuthException($error_message, $provider, $http_status);
		}

        if (in_array($http_status, [500, 502, 503, 504], true)) {
            return new ServiceUnavailableException($error_message, $provider);
        }

        if ($http_status === 400) {

            if (self::matchesIndicators($error_lower, self::$content_filter_indicators)) {
                return new ContentFilterException($error_message);
            }
            return new InvalidRequestException($error_message);
        }

        if (self::matchesIndicators($error_lower, self::$rate_limit_indicators)) {
            $retry_after = self::parseRetryAfter($error_message);
            return new RateLimitException($error_message, $retry_after);
        }

		if (self::matchesIndicators($error_lower, self::$auth_indicators)) {
			return new ProviderAuthException($error_message, $provider);
		}

        if (self::matchesIndicators($error_lower, self::$service_indicators)) {
            return new ServiceUnavailableException($error_message, $provider);
        }

        if (self::matchesIndicators($error_lower, self::$content_filter_indicators)) {
            return new ContentFilterException($error_message);
        }

        if (self::matchesIndicators($error_lower, self::$invalid_request_indicators)) {
            return new InvalidRequestException($error_message);
        }

        return new ChatbotException($error_message);
    }

    /**
     * Create an exception from a generic Exception.
     *
     * @param \Exception $exception Original exception.
     * @param string     $provider  AI provider name.
     * @return ChatbotException Appropriate exception instance.
     */
    public static function fromException(\Exception $exception, string $provider = ''): ChatbotException {

        if ($exception instanceof ChatbotException) {
            return $exception;
        }

        $message = $exception->getMessage();

        $http_status = 0;
        if (preg_match('/\b(4\d{2}|5\d{2})\b/', $message, $matches)) {
            $http_status = (int) $matches[1];
        }

        return self::fromError($message, $http_status, $provider);
    }

    /**
     * Check if error message matches any indicators.
     *
     * @param string $error_lower Lowercase error message.
     * @param array  $indicators  Array of indicator strings.
     * @return bool True if any indicator matches.
     */
    private static function matchesIndicators(string $error_lower, array $indicators): bool {
        foreach ($indicators as $indicator) {
            if (strpos($error_lower, $indicator) !== false) {
                return true;
            }
        }
        return false;
    }

    /**
     * Parse retry-after seconds from error message.
     *
     * @param string $error_message Error message.
     * @return int Seconds to wait (default 60).
     */
    private static function parseRetryAfter(string $error_message): int {

        if (preg_match('/retry.?after[:\s]+(\d+)/i', $error_message, $matches)) {
            return (int) $matches[1];
        }

        if (preg_match('/wait[:\s]+(\d+)\s*seconds?/i', $error_message, $matches)) {
            return (int) $matches[1];
        }

        if (preg_match('/in\s+(\d+)\s*seconds?/i', $error_message, $matches)) {
            return (int) $matches[1];
        }

        return 60;
    }
}
