<?php

namespace App\Exceptions;

use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Exceptions\PostTooLargeException;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use App\Services\Security\SecurityEventLogger;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable;

class Handler extends ExceptionHandler
{
    protected $dontReport = [
        DataQualityException::class,
        ValidationException::class,
        AuthorizationException::class,
        AuthenticationException::class,
        ModelNotFoundException::class,
        NotFoundHttpException::class,
        TokenMismatchException::class,
    ];

    protected $dontFlash = [
        'current_password',
        'password',
        'password_confirmation',
        'token',
        'api_token',
        'client_secret',
        'client_key',
        'secret',
    ];

    public function render($request, Throwable $exception)
    {
        $reference = (string) Str::uuid();

        if ($exception instanceof AuthenticationException) {
            return $this->unauthenticated($request, $exception);
        }

        if ($exception instanceof ValidationException) {
            return $this->renderValidationFailure($request, $exception);
        }

        if ($exception instanceof DataQualityException) {
            if (!$request->expectsJson() && !$request->isMethodSafe()) {
                return back()
                    ->withInput($request->except($this->dontFlash))
                    ->withErrors($exception->messages() ?: ['data_quality' => $exception->getMessage()])
                    ->with('error_message', $exception->getMessage());
            }

            return $this->renderFriendly($request, $exception->status(), 'Data Quality Check Failed', $exception->getMessage(), $reference, $exception->messages());
        }

        if ($exception instanceof AuthorizationException) {
            app(SecurityEventLogger::class)->log('authorization_exception', 'warning', ['reference' => $reference], $request);
            return $this->renderFriendly($request, 403, 'Access Restricted', 'You do not have permission to perform this action or access this resource.', $reference);
        }

        if ($exception instanceof ModelNotFoundException || $exception instanceof NotFoundHttpException) {
            return $this->renderFriendly($request, 404, 'Resource Not Found', 'The requested resource could not be found or is no longer available.', $reference);
        }

        if ($exception instanceof TokenMismatchException) {
            return $this->renderFriendly($request, 419, 'Session Expired', 'Your session has expired. Please refresh the page, sign in again if required, and retry the action.', $reference);
        }

        if ($exception instanceof PostTooLargeException) {
            return $this->renderFriendly($request, 413, 'Uploaded File Too Large', 'The uploaded file is larger than the allowed limit. Please reduce the file size and try again.', $reference);
        }

        if ($exception instanceof QueryException) {
            $message = $this->friendlyDatabaseMessage($exception);
            $this->logApplicationError($exception, $request, $reference);

            if (!$request->expectsJson() && !$request->isMethodSafe()) {
                return back()
                    ->withInput($request->except($this->dontFlash))
                    ->withErrors(['database' => $message])
                    ->with('error_message', $message);
            }

            return $this->renderFriendly($request, 500, 'Database Operation Failed', $message, $reference);
        }

        if ($exception instanceof HttpExceptionInterface) {
            $status = $exception->getStatusCode();
            return $this->renderFriendly($request, $status, $this->titleForStatus($status), $this->messageForStatus($status), $reference);
        }

        $this->logApplicationError($exception, $request, $reference);

        return $this->renderFriendly($request, 500, 'System Error', 'The system could not complete the request. Please try again. If the issue persists, contact the system administrator with the reference number below.', $reference);
    }


    protected function renderValidationFailure($request, ValidationException $exception)
    {
        if ($request->expectsJson()) {
            return response()->json([
                'status' => 'error',
                'code' => 422,
                'message' => 'Some submitted data is incomplete or invalid. Please review and try again.',
                'errors' => $exception->errors(),
            ], 422);
        }

        return back()
            ->withInput($request->except($this->dontFlash))
            ->withErrors($exception->errors())
            ->with('error_message', 'Some mandatory fields are missing or invalid. Please review the highlighted fields and try again.');
    }

    protected function unauthenticated($request, AuthenticationException $exception)
    {
        if ($request->expectsJson()) {
            return response()->json(['message' => 'Unauthenticated'], 401);
        }

        return redirect()->guest(route('login'));
    }

    protected function renderFriendly($request, int $status, string $title, string $message, string $reference, array $details = [])
    {
        if ($request->expectsJson()) {
            return response()->json([
                'status' => 'error',
                'code' => $status,
                'message' => $message,
                'details' => $status === 422 ? $details : [],
                'reference' => $reference,
            ], $status);
        }

        $view = view()->exists("errors.$status") ? "errors.$status" : 'errors.index';

        return response()->view($view, [
            'statusCode' => $status,
            'title' => $title,
            'message' => $message,
            'details' => $details,
            'reference' => $reference,
            'requestedResource' => $request->path(),
        ], $status);
    }

    protected function friendlyDatabaseMessage(QueryException $exception): string
    {
        $raw = $exception->getMessage();

        if (str_contains($raw, 'Unknown column')) {
            return 'The system tried to validate data using a database column that is not available in this installation. The action was stopped before saving incomplete or inconsistent data.';
        }

        if (str_contains($raw, 'Duplicate entry')) {
            return 'A duplicate record was detected. Please verify whether this record already exists before saving again.';
        }

        if (str_contains($raw, 'Incorrect datetime value') || str_contains($raw, '0000-00-00')) {
            return 'The request contains an invalid date value. Please review the date fields and try again.';
        }

        if (str_contains($raw, 'Cannot add or update a child row') || str_contains($raw, 'foreign key constraint')) {
            return 'The selected reference data is inconsistent. Please confirm that the selected values are valid and related.';
        }

        return 'The database could not complete the request. No dirty data was saved. Please review the form and try again.';
    }

    protected function titleForStatus(int $status): string
    {
        return [
            403 => 'Access Restricted',
            404 => 'Resource Not Found',
            419 => 'Session Expired',
            422 => 'Data Validation Failed',
            500 => 'System Error',
        ][$status] ?? 'Request Failed';
    }

    protected function messageForStatus(int $status): string
    {
        return [
            403 => 'You do not have permission to access this resource or perform this action.',
            404 => 'The requested resource could not be found or is no longer available.',
            419 => 'Your session has expired. Please refresh the page and try again.',
            422 => 'Some submitted data is incomplete or inconsistent. Please review and try again.',
            500 => 'The system could not complete the request. Please try again or contact the system administrator.',
        ][$status] ?? 'The request could not be completed.';
    }

    protected function logApplicationError(Throwable $exception, $request, string $reference): void
    {
        Log::error('NGBVD handled application error', [
            'reference' => $reference,
            'exception' => get_class($exception),
            'message' => $exception->getMessage(),
            'user_id' => optional($request->user())->id,
            'method' => $request->method(),
            'url' => $request->fullUrl(),
            'ip' => $request->ip(),
        ]);
    }

    public function register()
    {
        $this->reportable(function (Throwable $e) {
            // Logging is handled in render() with a user-facing reference number.
        });
    }
}
