<?php
/**
 * Global Exception Handler
 *
 * @package Licenzo\Framework\Exceptions
 */

namespace Licenzo\Framework\Exceptions;

use Throwable;
use Licenzo\Framework\Foundation\Application;
use Licenzo\Framework\Http\Response;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Global exception handler for the application
 */
class Handler
{
    /**
     * Application instance
     *
     * @var Application
     */
    protected $app;

    /**
     * Exception types that should not be logged
     *
     * @var array
     */
    protected $dontLog = [
        AuthenticationException::class,
        AuthorizationException::class,
        ValidationException::class,
        HttpException::class,
    ];

    /**
     * Constructor
     *
     * @param Application $app
     */
    public function __construct(Application $app)
    {
        $this->app = $app;

        // Initialize WordPress exception handling
        $this->initializeWordPressHandling();
    }

    /**
     * Report or log an exception
     *
     * @param Throwable $exception
     * @return void
     * @throws \Exception
     */
    public function report(Throwable $exception)
    {
        if ($this->shouldntReport($exception)) {
            return;
        }

        // Run custom reportable callbacks
        $this->runReportableCallbacks($exception);

        // Log the exception
        $this->logException($exception);

        // Report to external services if configured
        $this->reportToExternalServices($exception);
    }

    /**
     * Run custom reportable callbacks
     *
     * @param Throwable $exception
     * @return void
     */
    protected function runReportableCallbacks(Throwable $exception): void
    {
        $callbacks = $this->app->config('exceptions.reportable_callbacks', []);

        foreach ($callbacks as $callback) {
            if (is_callable($callback)) {
                try {
                    $callback($exception);
                } catch (Throwable $e) {
                    // Don't let callback errors break the reporting

                }
            }
        }
    }

    /**
     * Run custom renderable callbacks
     *
     * @param Throwable $exception
     * @return mixed|null
     */
    protected function runRenderableCallbacks(Throwable $exception)
    {
        $callbacks = $this->app->config('exceptions.renderable_callbacks', []);

        foreach ($callbacks as $callback) {
            if (is_callable($callback)) {
                try {
                    $result = $callback($exception);
                    if ($result !== null) {
                        return $result;
                    }
                } catch (Throwable $e) {
                    // Don't let callback errors break the rendering

                }
            }
        }

        return null;
    }

    /**
     * Render an exception into an HTTP response
     *
     * @param Throwable $exception
     * @return Response|\WP_REST_Response|array
     */
    public function render(Throwable $exception)
    {
        // Try custom renderable callbacks first
        $customResponse = $this->runRenderableCallbacks($exception);
        if ($customResponse !== null) {
            return $customResponse;
        }
        // Handle specific exception types
        if ($exception instanceof HttpException) {
            return $this->renderHttpException($exception);
        }

        if ($exception instanceof ValidationException) {
            return $this->renderValidationException($exception);
        }

        if ($exception instanceof AuthenticationException) {
            return $this->renderAuthenticationException($exception);
        }

        if ($exception instanceof AuthorizationException) {
            return $this->renderAuthorizationException($exception);
        }

        // Handle generic exceptions
        return $this->renderGenericException($exception);
    }

    /**
     * Determine if the exception should be reported
     *
     * @param Throwable $exception
     * @return bool
     * @throws \Exception
     */
    protected function shouldntReport(Throwable $exception)
    {
        // Check built-in dontLog array
        foreach ($this->dontLog as $type) {
            if ($exception instanceof $type) {
                return true;
            }
        }

        // Check configured dont_report exceptions
        $dontReport = $this->app->config('exceptions.dont_report', []);
        foreach ($dontReport as $type) {
            if ($exception instanceof $type) {
                return true;
            }
        }

        return false;
    }

    /**
     * Log the exception
     *
     * @param Throwable $exception
     * @return void
     */
    protected function logException(Throwable $exception)
    {
        $message = sprintf(
            '[%s] %s in %s:%d',
            get_class($exception),
            $exception->getMessage(),
            $exception->getFile(),
            $exception->getLine()
        );

        // Add stack trace in debug mode
        if (defined('WP_DEBUG') && WP_DEBUG) {
            $message .= "\nStack trace:\n" . $exception->getTraceAsString();
        }
    }

    /**
     * Report exception to external services
     *
     * @param Throwable $exception
     * @return void
     */
    protected function reportToExternalServices(Throwable $exception)
    {
        // Hook for external error reporting services
        do_action('LICENZO_exception_reported', $exception);
    }

    /**
     * Render HTTP exception
     *
     * @param HttpException $exception
     * @return Response|\WP_REST_Response|array
     */
    protected function renderHttpException(HttpException $exception)
    {
        $data = [
            'success' => false,
            'message' => $exception->getMessage(),
            'code' => $exception->getStatusCode(),
        ];

        // Add additional data if available
        if ($exception->hasData()) {
            $data['data'] = $exception->getData();
        }

        return $this->formatResponse($data, $exception->getStatusCode());
    }

    /**
     * Render validation exception
     *
     * @param ValidationException $exception
     * @return Response|\WP_REST_Response|array
     */
    protected function renderValidationException(ValidationException $exception)
    {
        $data = [
            'success' => false,
            'message' => $exception->getMessage() ?: 'Validation failed',
            'errors' => $exception->getErrors(),
            'code' => 422,
        ];

        return $this->formatResponse($data, 422);
    }

    /**
     * Render authentication exception
     *
     * @param AuthenticationException $exception
     * @return Response|\WP_REST_Response|array
     */
    protected function renderAuthenticationException(AuthenticationException $exception)
    {
        $data = [
            'success' => false,
            'message' => $exception->getMessage() ?: 'Authentication required',
            'code' => 401,
        ];

        return $this->formatResponse($data, 401);
    }

    /**
     * Render authorization exception
     *
     * @param AuthorizationException $exception
     * @return Response|\WP_REST_Response|array
     */
    protected function renderAuthorizationException(AuthorizationException $exception)
    {
        $data = [
            'success' => false,
            'message' => $exception->getMessage() ?: 'Access denied',
            'code' => 403,
        ];

        return $this->formatResponse($data, 403);
    }

    /**
     * Render generic exception
     *
     * @param Throwable $exception
     * @return Response|\WP_REST_Response|array
     */
    protected function renderGenericException(Throwable $exception)
    {
        $message = 'Internal server error';
        $code = 500;

        // Show detailed error in debug mode
        if (defined('WP_DEBUG') && WP_DEBUG) {
            $message = $exception->getMessage();
        }

        $data = [
            'success' => false,
            'message' => $message,
            'code' => $code,
        ];

        // Add debug information in debug mode
        if (defined('WP_DEBUG') && WP_DEBUG) {
            $data['debug'] = [
                'exception' => get_class($exception),
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
                'trace' => $exception->getTraceAsString(),
            ];
        }

        return $this->formatResponse($data, $code);
    }

    /**
     * Format response based on request type
     *
     * @param array $data
     * @param int $statusCode
     * @return Response|\WP_REST_Response|array
     */
    protected function formatResponse(array $data, int $statusCode)
    {
        $requestType = $this->app->getRequestType();

        switch ($requestType) {
            case 'rest':
                return new \WP_REST_Response($data, $statusCode);

            case 'ajax':
                // For AJAX, we'll return the data and let the caller handle wp_send_json_*
                return $data;

            default:
                // Return Response object for other cases
                return Response::error($data['message'], $data, $statusCode);
        }
    }

    /**
     * Handle uncaught exceptions
     *
     * @param Throwable $exception
     * @return void
     * @throws Throwable
     */
    public function handleUncaughtException(Throwable $exception)
    {
        try {
            $this->report($exception);
            $response = $this->render($exception);

            // Send response based on type
            if ($response instanceof \WP_REST_Response) {
                // For REST API, we can't send the response directly here
                // The response will be handled by the calling code
                return;
            }

            if (is_array($response)) {
                // For AJAX requests
                wp_send_json_error($response, $response['code'] ?? 500);
                return;
            }

        } catch (Throwable $renderException) {
            $this->logException($exception);
            throw $renderException;
        }
    }

    /**
     * Convert exception to array for logging/debugging
     *
     * @param Throwable $exception
     * @return array
     */
    public function exceptionToArray(Throwable $exception)
    {
        return [
            'class' => get_class($exception),
            'message' => $exception->getMessage(),
            'code' => $exception->getCode(),
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
            'trace' => $exception->getTraceAsString(),
        ];
    }

    /**
     * Initialize WordPress-specific exception handling
     *
     * @return void
     * @throws \Exception
     */
    protected function initializeWordPressHandling()
    {
        // Only register global exception handler if explicitly enabled
        if ($this->app->config('app.enable_global_exception_handler', false)) {
            $this->app->registerGlobalExceptionHandler();
        }

        // Register WordPress hooks for exception handling
        $this->registerWordPressHooks();
    }

    /**
     * Register WordPress-specific exception handling hooks
     *
     * @return void
     * @throws \Exception
     */
    protected function registerWordPressHooks()
    {
        // Only handle AJAX errors for our plugin - don't interfere with WordPress core error handling
        add_action('wp_ajax_nopriv_LICENZO_error', [$this, 'handleAjaxError']);
        add_action('wp_ajax_LICENZO_error', [$this, 'handleAjaxError']);

        // Handle plugin-specific fatal errors only if they occur in our plugin
        add_action('wp_fatal_error_handler', function($error) {
            if (is_array($error) && isset($error['message'], $error['file'])) {
                // Only handle fatal errors from our plugin
                if ($this->isPluginFile($error['file'])) {
                    $exception = new \ErrorException(
                        $error['message'],
                        0,
                        $error['type'] ?? E_ERROR,
                        $error['file'],
                        $error['line'] ?? 0
                    );

                    $this->report($exception);
                }
            }
        }, 10, 1);
    }

    /**
     * Check if a file belongs to our plugin
     *
     * @param string $file
     * @return bool
     */
    protected function isPluginFile(string $file): bool
    {
        $pluginPath = realpath($this->app->basePath());
        $filePath = realpath($file);

        // Check if the file is within our plugin directory
        return $filePath && $pluginPath && strpos($filePath, $pluginPath) === 0;
    }

    /**
     * Handle AJAX errors
     *
     * @return void
     */
    public function handleAjaxError()
    {
        $error = $_POST['error'] ?? '';
        $context = $_POST['context'] ?? [];

        if ($error) {
            $exception = new \Exception($error);
            $this->report($exception);
        }

        wp_send_json_success(['message' => 'Error reported']);
    }
}
