<?php

namespace Common\Core\Exceptions;

use Common\Billing\Models\Product;
use ErrorException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Foundation\Exceptions\Handler;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Sentry\Laravel\Integration;
use Sentry\State\Scope;
use Spatie\Ignition\Ignition;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;
use function Sentry\configureScope;

class BaseExceptionHandler extends Handler
{
    public function render($request, Throwable $e)
    {
        $isAuthException =
            $e instanceof AuthorizationException ||
            ($e instanceof HttpException && $e->getStatusCode() === 403);

        if (
            $isAuthException &&
            (requestIsFromFrontend() &&
                !$request->expectsJson() &&
                !Auth::check())
        ) {
            return redirect('/login');
        }

        if (
            $e instanceof AuthorizationException &&
            $e->response() instanceof AccessResponseWithPermission &&
            $e->response()->permission &&
            Auth::check() &&
            settings('billing.enable')
        ) {
            $permissionExistsInSubscriptionPlan = Product::with(['permissions'])
                ->get()
                ->some(function ($product) use ($e) {
                    // check if there's a plan that has this permission and if user is not already on this plan
                    return $product->permissions->contains(
                        'name',
                        $e->response()->permission,
                    ) &&
                        Auth::user()->subscriptions->first()?->product_id !==
                            $product->id;
                });
            if ($permissionExistsInSubscriptionPlan) {
                return Auth::user()->subscribed()
                    ? redirect('/billing')
                    : redirect('/pricing');
            }
        }

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

    public function register()
    {
        if (config('app.env') !== 'production') {
            return;
        }

        $this->renderable(function (ErrorException $e) {
            if (
                Str::contains($e->getMessage(), [
                    'failed to open stream: Permission denied',
                    'mkdir(): Permission denied',
                ])
            ) {
                return $this->filePermissionResponse($e);
            }
        });

        configureScope(function (Scope $scope): void {
            $scope->setContext('app_name', ['value' => config('app.name')]);
        });

        $this->reportable(function (Throwable $e) {
            Integration::captureUnhandledException($e);
        });
    }

    protected function convertExceptionToArray(Throwable $e): array
    {
        $previous = $e->getPrevious();
        $isValidationException =
            $e instanceof HttpException && $e->getStatusCode() === 422;
        $isExceptionWithAction =
            $previous &&
            method_exists($previous, 'response') &&
            $previous->response() &&
            property_exists($previous->response(), 'action');

        if (
            config('app.debug') &&
            !config('common.site.demo') &&
            !$isValidationException
        ) {
            $array = $this->ignitionReportFromThrowable($e);
        } else {
            $array = parent::convertExceptionToArray($e);
        }

        if ($isExceptionWithAction) {
            $array['action'] = $e->getPrevious()->response()->action;
        }

        if ($array['message'] === 'Server Error') {
            $array['message'] = __(
                'There was an issue. Please try again later.',
            );
        }

        if ($array['message'] === 'This action is unauthorized.') {
            $array['message'] = __(
                "You don't have required permissions for this action.",
            );
        }

        return $array;
    }

    protected function filePermissionResponse(ErrorException $e)
    {
        if (request()->expectsJson()) {
            return response()->json(['message' => 'test']);
        } else {
            preg_match('/\((.+?)\):/', $e->getMessage(), $matches);
            $path = $matches[1] ?? null;
            // should not return a view here, in case laravel views folder is not readable as well
            return response(
                "<div style='text-align:center'><h1>Could not access a file or folder</h1> <br> Location: <b>$path</b><br>" .
                    '<p>See the article here for possible solutions: <a target="_blank" href="https://support.vebto.com/hc/articles/21/25/207/changing-file-permissions">https://support.vebto.com/hc/articles/207/changing-file-permissions</a></p></div>',
            );
        }
    }

    protected function ignitionReportFromThrowable(Throwable $e): array
    {
        $report = app(Ignition::class)
            ->shouldDisplayException(false)
            ->handleException($e)
            ->toArray();

        $trace = array_map(function ($item) {
            $path = Str::of($item['class'] ?? $item['file'])
                ->replace([base_path(), 'vendor/laravel/framework/src/'], '')
                ->replace('\\', '/')
                ->trim('/')
                ->explode('/');
            return [
                'applicationFrame' => $item['application_frame'],
                'codeSnippet' => $item['code_snippet'],
                'path' => $path,
                'lineNumber' => $item['line_number'],
                'method' => $item['method'],
            ];
        }, $report['stacktrace']);

        $flatIndex = 0;
        $totalVendorGroups = 0;
        $groupedTrace = array_reduce(
            $trace,
            function ($carry, $item) use (&$flatIndex, &$totalVendorGroups) {
                $item['flatIndex'] = $flatIndex;
                if ($item['applicationFrame']) {
                    $carry[] = $item;
                } else {
                    if (Arr::get(Arr::last($carry), 'vendorGroup')) {
                        $carry[count($carry) - 1]['items'][] = $item;
                    } else {
                        $totalVendorGroups++;
                        $carry[] = [
                            'vendorGroup' => true,
                            'items' => [$item],
                        ];
                    }
                }
                $flatIndex++;
                return $carry;
            },
            [],
        );

        return [
            'ignitionTrace' => true,
            'message' => $report['message'],
            'exception' => $report['exception_class'],
            'file' => $report['stacktrace'][0]['file'],
            'line' => $report['stacktrace'][0]['line_number'],
            'trace' => $groupedTrace,
            'totalVendorGroups' => $totalVendorGroups,
            'phpVersion' => $report['language_version'],
            'appVersion' => config('common.site.version'),
        ];
    }
}
