<?php

namespace App\Exceptions;

use Throwable;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use Illuminate\Database\QueryException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that are not reported.
     *
     * @var array<int, class-string<Throwable>>
     */
    protected $dontReport = [
        //
    ];

    /**
     * A list of the inputs that are never flashed for validation exceptions.
     *
     * @var array<int, string>
     */
    protected $dontFlash = [
        'current_password',
        'password',
        'password_confirmation',
    ];

    /**
     * Register the exception handling callbacks for the application.
     *
     * @return void
     */
    public function register()
    {
        $this->reportable(function (Throwable $e) {
            //
        });

        $this->renderable(function (Throwable $e, $request) {
            if ($request->is('api/*')) {
                return $this->handleApiException($e);
            }
        });
    }

    public function handleApiException(Throwable $exception)
    { 
        // Route Not Found 404
        if ($exception instanceof RouteNotFoundException) {
            return $this->apiExceptionResponse($exception, Response::HTTP_NOT_FOUND, config('response-message.router_failed'));
        }

        // Not Found 404
        if ($exception instanceof NotFoundHttpException) {
            $message = $exception->getMessage() ?: config('response-message.not_found');
            return $this->apiExceptionResponse($exception, Response::HTTP_NOT_FOUND, $message);
        }

        // Model Not Found 500
        if ($exception instanceof ModelNotFoundException) {
            $exception = new NotFoundHttpException($exception->getMessage(), $exception);
        }

        // Authentication 401
        if ($exception instanceof AuthenticationException) {
            return $this->apiExceptionResponse($exception, Response::HTTP_UNAUTHORIZED, config('response-message.authenticate_failed'));
        }

        // Unauthorized
        if ($exception instanceof UnauthorizedHttpException) {
            return $this->apiExceptionResponse($exception, $exception->getStatusCode(), config('response-message.authorize_failed'));
        }

        // Http
        if ($exception instanceof HttpException) {
            return $this->apiExceptionResponse($exception, $exception->getStatusCode(), $exception->getMessage());
        }

        // Query 500
        if ($exception instanceof QueryException) {
            $this->apiExceptionResponse($exception, Response::HTTP_INTERNAL_SERVER_ERROR, config('response-message.sql_error'));
        }

        // Validation 422
        if ($exception instanceof ValidationException) {

            $validationData = [];
            foreach ($exception->errors() as $col => $errorMsg) {
                $validationData['errors']['validation'][$col] = $errorMsg[0];
            }
            return $this->apiExceptionResponse($exception, Response::HTTP_UNPROCESSABLE_ENTITY, config('response-message.validation_failed'), $validationData);
        }

        // Others: 500 error
        return $this->apiExceptionResponse($exception, Response::HTTP_INTERNAL_SERVER_ERROR, 'server error');
    }

    public function apiExceptionResponse(Throwable $exception, int $code, string $message, array $mergeData = [])
    {
        if ($code === Response::HTTP_INTERNAL_SERVER_ERROR) {
            Log::info($exception->getMessage());
        }

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

        if (!app()->environment('PRO')&&!app()->environment('pro')) {
            $data['errors'] = [
                'message' => $exception->getMessage(),
                'trace'   => $exception->getTrace(),
            ];
        }

        if (!empty($mergeData)) {
            $data = array_merge($data, $mergeData);
        }

        return response()->json($data, $code);
    }
}
