<?php

namespace App\Exceptions;

use App\Support\ApiError;
use Illuminate\Database\Connectors\ConnectionException as DatabaseConnectionException;
use Illuminate\Database\QueryException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Client\ConnectionException as HttpClientConnectionException;
use Illuminate\Queue\InvalidQueueException;
use Illuminate\Queue\MaxAttemptsExceededException;
use Illuminate\Routing\Exceptions\InvalidSignatureException;
use Illuminate\Validation\ValidationException;
use League\Flysystem\FilesystemException;
use PDOException;
use Throwable;

class Handler extends ExceptionHandler
{
    /**
     * The list of the inputs that are never flashed to the session on validation exceptions.
     *
     * @var array<int, string>
     */
    protected $dontFlash = [
        'current_password',
        'password',
        'password_confirmation',
    ];

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

    public function render($request, Throwable $e)
    {
        if ($request->expectsJson()) {
            if ($e instanceof ValidationException) {
                return ApiError::response(
                    'validation_failed',
                    'Validation failed',
                    'The given data was invalid.',
                    422,
                    ['errors' => $e->errors()],
                );
            }

            $status = $this->isHttpException($e)
                ? $this->toHttpException($e)->getStatusCode()
                : 500;

            $code = $status >= 500 ? 'server_error' : 'request_failed';

            return ApiError::response(
                $code,
                $status >= 500 ? 'Unexpected error' : 'Request could not be completed',
                $this->buildGenericMessage($status),
                $status,
            );
        }

        if ($request->inertia()) {
            if ($e instanceof ValidationException) {
                return back()->withErrors($e->errors())->withInput($request->all());
            }
        }

        if ($e instanceof InvalidSignatureException && ! $request->expectsJson()) {
            $request->session()->flash('verification', [
                'status' => 'error',
                'title' => __('auth.verification.expired_title'),
                'message' => __('auth.verification.expired_message'),
            ]);

            return redirect()->route('verification.notice');
        }

        if (! $request->expectsJson() && ! $request->inertia()) {
            if ($hintKey = $this->resolveServerErrorHint($e)) {
                $request->attributes->set('serverErrorHint', __($hintKey, [], app()->getLocale()));
            }
        }

        return parent::render($request, $e);
    }

    private function buildGenericMessage(int $status): string
    {
        if ($status >= 500) {
            return 'Something went wrong on our side. Please try again later.';
        }

        return 'Your request could not be processed. Please verify the details and try again.';
    }

    private function resolveServerErrorHint(Throwable $throwable): ?string
    {
        $status = $this->isHttpException($throwable)
            ? $this->toHttpException($throwable)->getStatusCode()
            : 500;

        if ($status < 500) {
            return null;
        }

        if ($this->exceptionChainContains($throwable, [
            QueryException::class,
            DatabaseConnectionException::class,
            PDOException::class,
            'Doctrine\\DBAL\\Exception',
        ])) {
            return 'marketing.server_error.hints.database';
        }

        if ($this->exceptionChainContains($throwable, [
            FilesystemException::class,
            'League\\Flysystem\\UnableToWriteFile',
            'League\\Flysystem\\UnableToCreateDirectory',
            'League\\Flysystem\\UnableToCheckExistence',
        ])) {
            return 'marketing.server_error.hints.storage';
        }

        if ($this->exceptionChainContains($throwable, [
            InvalidQueueException::class,
            MaxAttemptsExceededException::class,
            'RedisException',
            'Predis\\Connection\\ConnectionException',
        ])) {
            return 'marketing.server_error.hints.queue';
        }

        if ($this->exceptionChainContains($throwable, [
            HttpClientConnectionException::class,
            'GuzzleHttp\\Exception\\ConnectException',
            'Psr\\Http\\Client\\NetworkExceptionInterface',
            'Symfony\\Component\\HttpClient\\Exception\\TransportExceptionInterface',
        ])) {
            return 'marketing.server_error.hints.network';
        }

        return 'marketing.server_error.hints.generic';
    }

    private function exceptionChainContains(Throwable $throwable, array $classNames): bool
    {
        do {
            foreach ($classNames as $className) {
                if ($this->throwableIsInstanceOf($throwable, $className)) {
                    return true;
                }
            }

            $throwable = $throwable->getPrevious();
        } while ($throwable);

        return false;
    }

    private function throwableIsInstanceOf(Throwable $throwable, string $className): bool
    {
        if ($className === '') {
            return false;
        }

        if (! class_exists($className) && ! interface_exists($className)) {
            return false;
        }

        return $throwable instanceof $className;
    }
}
