<?php

namespace Openguru\OpenCartFramework;

use ErrorException;
use Openguru\OpenCartFramework\Contracts\ExceptionHandlerInterface;
use Openguru\OpenCartFramework\Exceptions\ActionNotFoundException;
use Openguru\OpenCartFramework\Exceptions\HttpNotFoundException;
use Openguru\OpenCartFramework\Exceptions\InvalidApiTokenException;
use Openguru\OpenCartFramework\Exceptions\NonLoggableExceptionInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Psr\Log\LoggerInterface;
use Throwable;

/**
 * @codeCoverageIgnore
 */
class ErrorHandler
{
    private LoggerInterface $logger;
    private Application $app;

    public function __construct(LoggerInterface $logger, Application $application)
    {
        $this->logger = $logger;
        $this->app = $application;
    }

    public function register(): void
    {
        set_error_handler([$this, 'handleError']);
        set_exception_handler([$this, 'handleException']);
        register_shutdown_function([$this, 'handleShutdown']);
    }

    public function handleError(int $severity, string $message, string $file, int $line): bool
    {
        if (($severity & E_DEPRECATED | E_USER_DEPRECATED)) {
            return true;
        }

        $this->logger->error('Handled PHP error: ' . implode(', ', compact('severity', 'message', 'file', 'line')));

        // Convert warnings and notices to ErrorException
        if (! (error_reporting() & $severity)) {
            return true;
        }

        throw new ErrorException($message, 0, $severity, $file, $line);
    }

    public function handleException(Throwable $exception): void
    {
        if ($this->app->has(ExceptionHandlerInterface::class)) {
            $customHandler = $this->app->get(ExceptionHandlerInterface::class);
            $response = $customHandler->respond($exception);
            if ($response !== null) {
                $response->send();

                return;
            }
        }

        if (! $exception instanceof NonLoggableExceptionInterface) {
            $this->logger->error($exception->getMessage(), []);
        }

        if ($exception instanceof ActionNotFoundException) {
            (new JsonResponse([
                'message' => sprintf('Action %s is not found.', $exception->getAction()),
            ], Response::HTTP_NOT_FOUND))->send();
            exit(1);
        }

        if ($exception instanceof HttpNotFoundException) {
            (new JsonResponse([
                'message' => $exception->getMessage(),
            ], Response::HTTP_NOT_FOUND))->send();
            return;
        }

        if ($exception instanceof InvalidApiTokenException) {
            (new JsonResponse([
                'message' => $exception->getMessage(),
            ], $exception->getCode()))->send();
            exit(1);
        }

        if (PHP_SAPI === 'cli') {
            echo $exception->getMessage() . PHP_EOL;
        } else {
            $debug = env('APP_DEBUG');

            $data = $debug ? [
                'exception' => get_class($exception),
                'message' => $exception->getMessage(),
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
                'trace' => $exception->getTrace(),
            ] : [
                'message' => 'Server Error.',
            ];

            (new JsonResponse($data, Response::HTTP_INTERNAL_SERVER_ERROR))->send();
        }

        exit(1);
    }

    public function handleShutdown(): void
    {
        $error = error_get_last();

        if ($error !== null && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
            $message = sprintf(
                "[%s] Fatal error: %s in %s:%d",
                date('Y-m-d H:i:s'),
                $error['message'],
                $error['file'],
                $error['line']
            );

            $this->logger->error($message);

            if (PHP_SAPI === 'cli') {
                echo $message . PHP_EOL;
            } else {
                $debug = getenv('APP_DEBUG');
                $data = $debug ? [
                    'message' => $message,
                    'file' => $error['file'],
                    'line' => $error['line'],
                ] : [
                    'message' => 'Server Error.',
                ];

                (new JsonResponse($data, Response::HTTP_INTERNAL_SERVER_ERROR))->send();
            }

            exit(1);
        }
    }
}
