<?php

namespace App\Exceptions;

use App\Helpers\ClientHelper;
use App\Models\Menu;
use App\Models\Product;
use App\Models\Setting;
use App\Models\Template;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use GuzzleHttp\Client;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\View;
use Inertia\Inertia;
use Throwable;
use ParseError;

class Handler extends ExceptionHandler
{
    use ClientHelper;

    /**
     * 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',
    ];

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

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

    /**
     * override report method
     */
    public function report(Throwable $e)
    {
        $ip = $this->getUserIp();
        //$agent = request()->userAgent();

        if ($ip == 'UNKNOWN') {
            return;
        }

        parent::report($e);
    }

    /**
     * Report an exception only once.
     */
    protected function reportOnce(Throwable $e): void
    {
        $ip = $this->getUserIp();
        //$agent = request()->userAgent();

        if ($ip == 'UNKNOWN') {
            return;
        }

        $errorKey = md5($e->getMessage() . $e->getFile() . $e->getLine());

        if (!Cache::has($errorKey)) {
            if (app()->environment('production') && !$this->isErrorFromRemoteSending($e)) {
                $this->sendErrorToRemoteServer($e);
            }

            Cache::put($errorKey, true, now()->addMinutes(60));
        }
    }

    /**
     * Prepare exception for rendering.
     *
     * @param  \Throwable  $e
     * @return \Throwable
     */
    public function render($request, Throwable $e)
    {
        if ($request->is('api/*')) {
            if ($e instanceof ThrottleRequestsException) {
                return response()->json([
                    'success' => false,
                    'message' => __('messages.sentence.error_429_message'),
                    'code' => 429,
                ], 429);
            }

            if ($e instanceof \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException) {
                return response()->json([
                    'success' => false,
                    'message' => 'متد درخواست اشتباه است',
                    'code' => 405,
                ], 405);
            }

            if ($e instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException) {
                return response()->json([
                    'success' => false,
                    'message' => 'نتیجه یافت نشد',
                    'code' => 404,
                ], 404);
            }

            if ($e instanceof \Illuminate\Auth\AuthenticationException) {
                return response()->json([
                    'success' => false,
                    'message' => 'عدم اجازه دسترسی',
                    'code' => 401,
                ], 401);
            }

            return response()->json([
                'success' => false,
                'message' => ($e->getMessage() ? ($e->getMessage() . ' - line: ' . $e->getLine()) : 'Server Error'),
                'code' => method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500,
            ], method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500);
        }


        /** @var RUNTIME_CLASS $response */
        $response = parent::render($request, $e);

        // 429
        if ($response->status() === 429) {
            return redirect()->back()->withErrors(['error' => __('messages.sentence.error_429_message')]);
        }

        // 419
        if ($response->getStatusCode() === 419) {
            return back()->withErrors([__('messages.sentence.error_419_messages')]);
        }

        $errorMessages = [
            503 => trans("messages.sentence.error_503"),
            500 => trans("messages.sentence.error_500"),
            404 => trans("messages.sentence.error_404"),
            403 => trans("messages.sentence.error_403"),
        ];

        $version = '1';
        if (file_exists(storage_path('version'))) {
            $version = file_get_contents(storage_path('version'));
        }

        if (!app()->environment(['local', 'testing']) && in_array($response->status(), [500, 503, 404, 403])) {
            $previousUrl = str_replace($request->root(), '', url()->previous());

            // handle versioning
            if (str_starts_with($previousUrl, '/admin')) {
                // init
                Inertia::setRootView('admin');
                Inertia::version('admin-' . $version);

                return Inertia::render('Admin/Errors/Error', ['status' => $response->status()])
                    ->title($response->status() . ' - ' . (array_key_exists($response->status(), $errorMessages) ? $errorMessages[$response->status()] : __('messages.unknown_error')))
                    ->toResponse($request)
                    ->setStatusCode($response->status());
            } else if (str_starts_with($previousUrl, '/seller')) {
                // init
                Inertia::setRootView('app');
                Inertia::version('seller-' . $version);

                return Inertia::render('Store/Errors/Error', ['status' => $response->status()])
                    ->title($response->status() . ' - ' . (array_key_exists($response->status(), $errorMessages) ? $errorMessages[$response->status()] : __('messages.unknown_error')))
                    ->toResponse($request)
                    ->setStatusCode($response->status());
            } else {
                // init
                Inertia::setRootView('app');
                Inertia::version('main-' . $version);

                return Inertia::render('Errors/Error', ['status' => $response->status(), 'data' => $response->status() == 404 ? $this->getData() : null])
                    ->title($response->status() . ' - ' . (array_key_exists($response->status(), $errorMessages) ? $errorMessages[$response->status()] : __('messages.unknown_error')))
                    ->toResponse($request)
                    ->setStatusCode($response->status());
            }
        }

        return $response;
    }

    /**
     * get error page data
     */
    public function getData()
    {
        // template
        $template = Template::where('status', 'active')->first();
        $data['template'] = $template;

        // common
        $data['appUrl'] = rtrim(asset(''), '/');
        $data['appFullName'] = Setting::where('key', 'shop_full_title')->first() != null ? Setting::where('key', 'shop_full_title')->first()->value : null;
        $data['appName'] = Setting::where('key', 'shop_title')->first() != null ? Setting::where('key', 'shop_title')->first()->value : null;
        $data['appDescription'] = Setting::where('key', 'shop_description')->first() != null ? Setting::where('key', 'shop_description')->first()->value : null;
        $data['appFavicon'] = Setting::where('key', 'favicon')->first() != null ? Setting::where('key', 'favicon')->first()->get_image : null;
        $data['appLogo'] = Setting::where('key', 'logo')->first() != null ? Setting::where('key', 'logo')->first()->get_image : null;
        $data['appLogoDark'] = Setting::where('key', 'logo_dark')->first() != null ? Setting::where('key', 'logo_dark')->first()->get_image : null;

        // header menu
        $data['header_menu'] = Menu::where('place', 'header_main')->first();

        // get random products
        $data['products'] = Product::where('status', 'publish')->inRandomOrder()->limit(12)->get()->map(fn($product) => [
            'id' => $product->id,
            'title' => $product->title,
            'slug' => $product->slug,
            'get_images' => $product->get_images,
            'comments_info' => $product->comments_info,
            'in_stock_status' => $product->in_stock_status,
            'inventory' => $product->inventory,
            'best_normal_price' => $product->best_normal_price,
            'unique_color' => $product->unique_color,
            'best_price' => $product->best_price,
            'is_vip' => $product->is_vip,
        ]);

        /** blade share */
        try {
            $bladeShares = [
                'favicon' => Cache::remember('setting_favicon', now()->addMonth(), function () {
                    return Setting::where('key', 'favicon')->first() != null ? Setting::where('key', 'favicon')->first()->value : null;
                }),
                'header_codes' => Cache::remember('setting_header_meta', now()->addMonth(), function () {
                    return Setting::where('key', 'header_meta')->first() != null ? Setting::where('key', 'header_meta')->first()->value : null;
                }),
                'footer_codes' => Cache::remember('setting_footer_meta', now()->addMonth(), function () {
                    return Setting::where('key', 'footer_meta')->first() != null ? Setting::where('key', 'footer_meta')->first()->value : null;
                }),
            ];
            $bladeShares['custom_css'] = $template != null ? $template['get_options']['style']['custom_css'] : null;
            $bladeShares['custom_js'] = $template != null ? $template['get_options']['style']['custom_js'] : null;

            View::share($bladeShares);
        } catch (Exception $e) {
        }

        return $data;
    }

    /**
     * Send error details to remote server
     *
     * @param  \Throwable  $exception
     * @return void
     */
    private function sendErrorToRemoteServer(Throwable $exception): void
    {
        try {
            $currentVersion = number_format((float) file_get_contents(storage_path('version')), 2, '.', '');

            $errorData = [
                'error' => [
                    'message' => $exception->getMessage(),
                    'file' => $exception->getFile(),
                    'line' => $exception->getLine(),
                    'url' => request()->fullUrl(),
                    'method' => request()->method(),
                    'ip' => $this->getUserIp(),
                    'agent' => request()->userAgent(),
                    'os' => $this->getDeviceMeta(request())['platform'],
                    'browser' => $this->getDeviceMeta(request())['browser'],
                    'version' => $currentVersion,
                    'source' => 'main',
                    'timestamp' => now()->toDateTimeString(),
                    'stack_trace' => $exception->getTraceAsString(),
                ],
            ];

            $client = new Client();
            $response = $client->post('https://data.vimascript.ir/api/errors/add', [
                'headers' => [
                    'Accept' => 'application/json',
                ],
                'json' => $errorData
            ]);
        } catch (Throwable $e) {
            Log::error('Failed to send error to remote server: ' . $e->getMessage());
        }
    }

    /**
     * Check if the error originated from the remote sending process
     *
     * @param  \Throwable  $exception
     * @return bool
     */
    private function isErrorFromRemoteSending(Throwable $exception): bool
    {
        $trace = $exception->getTraceAsString();
        return str_contains($trace, 'sendErrorToRemoteServer');
    }
}
