<?php
/**
 * @package   WPEmerge
 * @author    Atanas Angelov <hi@atanas.dev>
 * @copyright 2017-2019 Atanas Angelov
 * @license   https://www.gnu.org/licenses/gpl-2.0.html GPL-2.0
 * @link      https://wpemerge.com/
 */

namespace WPEmerge\Exceptions;

use Exception as PhpException;
use Psr\Http\Message\ResponseInterface;
use Whoops\RunInterface;
use WPEmerge\Csrf\InvalidCsrfTokenException;
use WPEmerge\Requests\RequestInterface;
use WPEmerge\Responses\ResponseService;
use WPEmerge\Routing\NotFoundException;
use WPEmerge\Support\Arr;

class ErrorHandler implements ErrorHandlerInterface {
	/**
	 * Response service.
	 *
	 * @var ResponseService
	 */
	protected $response_service = null;

	/**
	 * Pretty handler.
	 *
	 * @var RunInterface|null
	 */
	protected $whoops = null;

	/**
	 * Whether debug mode is enabled.
	 *
	 * @var boolean
	 */
	protected $debug = false;

	/**
	 * Constructor.
	 *
	 * @codeCoverageIgnore
	 * @param ResponseService   $response_service
	 * @param RunInterface|null $whoops
	 * @param boolean           $debug
	 */
	public function __construct( $response_service, $whoops, $debug = false ) {
		$this->response_service = $response_service;
		$this->whoops = $whoops;
		$this->debug = $debug;
	}

	/**
	 * {@inheritDoc}
	 * @codeCoverageIgnore
	 */
	public function register() {
		if ( $this->debug && $this->whoops !== null ) {
			$this->whoops->register();
		}
	}

	/**
	 * {@inheritDoc}
	 * @codeCoverageIgnore
	 */
	public function unregister() {
		if ( $this->debug && $this->whoops !== null ) {
			$this->whoops->unregister();
		}
	}

	/**
	 * Convert an exception to a ResponseInterface instance if possible.
	 *
	 * @param  PhpException            $exception
	 * @return ResponseInterface|false
	 */
	protected function toResponse( $exception ) {
		// @codeCoverageIgnoreStart
		if ( $exception instanceof InvalidCsrfTokenException ) {
			wp_nonce_ays( '' );
		}
		// @codeCoverageIgnoreEnd

		if ( $exception instanceof NotFoundException ) {
			return $this->response_service->error( 404 );
		}

		return false;
	}

	/**
	 * Convert an exception to a debug ResponseInterface instance if possible.
	 *
	 * @throws PhpException
	 * @param  RequestInterface  $request
	 * @param  PhpException      $exception
	 * @return ResponseInterface
	 */
	protected function toDebugResponse( RequestInterface $request, PhpException $exception ) {
		if ( $request->isAjax() ) {
			return $this->response_service->json( [
				'message' => $exception->getMessage(),
				'exception' => get_class( $exception ),
				'file' => $exception->getFile(),
				'line' => $exception->getLine(),
				'trace' => array_map( function ( $trace ) {
					return Arr::except( $trace, ['args'] );
				}, $exception->getTrace() ),
			] )->withStatus( 500 );
		}

		if ( $this->whoops !== null ) {
			return $this->toPrettyErrorResponse( $exception );
		}

		throw $exception;
	}

	/**
	 * Convert an exception to a pretty error response.
	 *
	 * @codeCoverageIgnore
	 * @param  PhpException      $exception
	 * @return ResponseInterface
	 */
	protected function toPrettyErrorResponse( $exception ) {
		$method = RunInterface::EXCEPTION_HANDLER;
		ob_start();
		$this->whoops->$method( $exception );
		$response = ob_get_clean();
		return $this->response_service->output( $response )->withStatus( 500 );
	}

	/**
	 * {@inheritDoc}
	 * @throws PhpException
	 */
	public function getResponse( RequestInterface $request, PhpException $exception ) {
		$response = $this->toResponse( $exception );

		if ( $response !== false ) {
			return $response;
		}

		// @codeCoverageIgnoreStart
		if ( ! defined( 'WPEMERGE_TEST_DIR' ) ) {
			// Only log errors if we are not running the WP Emerge test suite.
			error_log( $exception );
		}
		// @codeCoverageIgnoreEnd

		if ( ! $this->debug ) {
			return $this->response_service->error( 500 );
		}

		return $this->toDebugResponse( $request, $exception );
	}
}
