package com.gnx.telemedicine.exception;

import com.gnx.telemedicine.dto.error.ErrorResponseDto;
import com.gnx.telemedicine.util.CorrelationIdUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * Build standardized error response.
     */
    private ErrorResponseDto buildErrorResponse(
            String error,
            String message,
            HttpStatus status,
            HttpServletRequest request,
            Map<String, String> errors,
            Map<String, Object> details) {
        // Get correlation ID from MDC or generate a new one
        String correlationId = CorrelationIdUtil.getCorrelationId();
        if (correlationId == null || correlationId.isEmpty()) {
            correlationId = CorrelationIdUtil.generateCorrelationId();
            CorrelationIdUtil.setCorrelationId(correlationId);
        }
        String path = request != null ? request.getRequestURI() : null;

        ErrorResponseDto.ErrorResponseDtoBuilder builder = ErrorResponseDto.builder()
                .error(error)
                .message(message)
                .status(status.value())
                .timestamp(LocalDateTime.now())
                .correlationId(correlationId)
                .path(path);

        if (errors != null && !errors.isEmpty()) {
            builder.errors(errors);
        }

        if (details != null && !details.isEmpty()) {
            builder.details(details);
        }

        return builder.build();
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponseDto> handleValidation(
            MethodArgumentNotValidException ex,
            HttpServletRequest request) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });

        ErrorResponseDto errorResponse = buildErrorResponse(
                "Validation failed",
                "Validation failed. Please check the input fields.",
                HttpStatus.BAD_REQUEST,
                request,
                errors,
                null
        );

        log.warn("Validation error: {} | Path: {}", errors, request.getRequestURI());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }

    @ExceptionHandler(TwoFactorAuthenticationRequiredException.class)
    public ResponseEntity<ErrorResponseDto> handleTwoFactorRequired(
            TwoFactorAuthenticationRequiredException ex,
            HttpServletRequest request) {
        String errorMessage = ex.getMessage() != null ? ex.getMessage() : "2FA code required";
        Map<String, Object> details = new HashMap<>();
        details.put("requires2FA", true);

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.BAD_REQUEST,
                request,
                null,
                details
        );

        log.debug("2FA code required for user | Path: {}", request.getRequestURI());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ErrorResponseDto> handleIllegalArgument(
            IllegalArgumentException ex,
            HttpServletRequest request) {
        String errorMessage = ex.getMessage() != null ? ex.getMessage() : "Bad request";

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.BAD_REQUEST,
                request,
                null,
                null
        );

        log.warn("Illegal argument: {} | Path: {}", errorMessage, request.getRequestURI());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }

    @ExceptionHandler(BadCredentialsException.class)
    public ResponseEntity<ErrorResponseDto> handleBadCredentials(
            BadCredentialsException ex,
            HttpServletRequest request) {
        String errorMessage = ex.getMessage() != null ? ex.getMessage() : "Invalid credentials";

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.UNAUTHORIZED,
                request,
                null,
                null
        );

        log.warn("Bad credentials attempt | Path: {}", request.getRequestURI());
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
    }

    @ExceptionHandler(DisabledException.class)
    public ResponseEntity<ErrorResponseDto> handleDisabled(
            DisabledException ex,
            HttpServletRequest request) {
        String errorMessage = ex.getMessage() != null ? ex.getMessage() : "Account is disabled";

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.FORBIDDEN,
                request,
                null,
                null
        );

        log.warn("Disabled account access attempt | Path: {}", request.getRequestURI());
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<ErrorResponseDto> handleHttpMessageNotReadable(
            HttpMessageNotReadableException ex,
            HttpServletRequest request) {
        String message = ex.getMessage();
        String errorMessage;
        if (message != null && (message.contains("UUID") || message.contains("patientId"))) {
            errorMessage = "Invalid UUID format for patient ID. Please select a valid patient.";
        } else if (message != null && message.contains("status")) {
            errorMessage = "Invalid status value. Must be one of: PENDING, NORMAL, ABNORMAL, CRITICAL.";
        } else {
            errorMessage = "Invalid request format. " + (message != null ? message : "Please check all required fields.");
        }

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.BAD_REQUEST,
                request,
                null,
                null
        );

        log.warn("Request parsing error: {} | Path: {}", errorMessage, request.getRequestURI());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<ErrorResponseDto> handleTypeMismatch(
            MethodArgumentTypeMismatchException ex,
            HttpServletRequest request) {
        String errorMessage = String.format("Invalid value '%s' for parameter '%s'. Expected type: %s",
                ex.getValue(), ex.getName(), ex.getRequiredType() != null ? ex.getRequiredType().getSimpleName() : "unknown");

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.BAD_REQUEST,
                request,
                null,
                null
        );

        log.warn("Type mismatch error: {} | Path: {}", errorMessage, request.getRequestURI());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }

    @ExceptionHandler({DuplicateUserException.class, AvailabilityException.class, PasswordMismatchException.class})
    public ResponseEntity<ErrorResponseDto> handleBusinessExceptions(
            RuntimeException ex,
            HttpServletRequest request) {
        String errorMessage = ex.getMessage() != null ? ex.getMessage() : "Business rule violation";

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.BAD_REQUEST,
                request,
                null,
                null
        );

        log.warn("Business exception: {} | Path: {}", errorMessage, request.getRequestURI());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponseDto> handleResourceNotFound(
            ResourceNotFoundException ex,
            HttpServletRequest request) {
        String errorMessage = ex.getMessage() != null ? ex.getMessage() : "Resource not found";

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.NOT_FOUND,
                request,
                null,
                null
        );

        log.warn("Resource not found: {} | Path: {}", errorMessage, request.getRequestURI());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
    }

    @ExceptionHandler(UnauthorizedException.class)
    public ResponseEntity<ErrorResponseDto> handleUnauthorized(
            UnauthorizedException ex,
            HttpServletRequest request) {
        String errorMessage = ex.getMessage() != null ? ex.getMessage() : "Unauthorized access";

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.UNAUTHORIZED,
                request,
                null,
                null
        );

        log.warn("Unauthorized access attempt | Path: {}", request.getRequestURI());
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
    }

    @ExceptionHandler(ForbiddenException.class)
    public ResponseEntity<ErrorResponseDto> handleForbidden(
            ForbiddenException ex,
            HttpServletRequest request) {
        String errorMessage = ex.getMessage() != null ? ex.getMessage() : "Access forbidden";

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.FORBIDDEN,
                request,
                null,
                null
        );

        log.warn("Forbidden access attempt | Path: {}", request.getRequestURI());
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse);
    }

    @ExceptionHandler(ConflictException.class)
    public ResponseEntity<ErrorResponseDto> handleConflict(
            ConflictException ex,
            HttpServletRequest request) {
        String errorMessage = ex.getMessage() != null ? ex.getMessage() : "Resource conflict";

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.CONFLICT,
                request,
                null,
                null
        );

        log.warn("Conflict: {} | Path: {}", errorMessage, request.getRequestURI());
        return ResponseEntity.status(HttpStatus.CONFLICT).body(errorResponse);
    }

    @ExceptionHandler(EmptyResultDataAccessException.class)
    public ResponseEntity<ErrorResponseDto> handleEmptyResult(
            EmptyResultDataAccessException ex,
            HttpServletRequest request) {
        String errorMessage = "Requested resource not found";

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.NOT_FOUND,
                request,
                null,
                null
        );

        log.warn("Empty result data access | Path: {}", request.getRequestURI());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
    }

    @ExceptionHandler(DataIntegrityViolationException.class)
    public ResponseEntity<ErrorResponseDto> handleDataIntegrityViolation(
            DataIntegrityViolationException ex,
            HttpServletRequest request) {
        String errorMessage = "Data integrity violation. The operation conflicts with existing data.";
        String rootCause = ex.getRootCause() != null ? ex.getRootCause().getMessage() : null;

        Map<String, Object> details = new HashMap<>();
        if (rootCause != null) {
            details.put("rootCause", rootCause);
        }

        ErrorResponseDto errorResponse = buildErrorResponse(
                "Data integrity violation",
                errorMessage,
                HttpStatus.CONFLICT,
                request,
                null,
                details
        );

        log.warn("Data integrity violation | Path: {} | Root cause: {}", request.getRequestURI(), rootCause);
        return ResponseEntity.status(HttpStatus.CONFLICT).body(errorResponse);
    }

    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<ErrorResponseDto> handleAccessDenied(
            AccessDeniedException ex,
            HttpServletRequest request) {
        String errorMessage = "Access denied. You do not have permission to perform this action.";

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.FORBIDDEN,
                request,
                null,
                null
        );

        log.warn("Access denied | Path: {}", request.getRequestURI());
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse);
    }

    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<ErrorResponseDto> handleAuthentication(
            AuthenticationException ex,
            HttpServletRequest request) {
        String errorMessage = ex.getMessage() != null ? ex.getMessage() : "Authentication failed";

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.UNAUTHORIZED,
                request,
                null,
                null
        );

        log.warn("Authentication failed | Path: {}", request.getRequestURI());
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResponseEntity<ErrorResponseDto> handleMissingParameter(
            MissingServletRequestParameterException ex,
            HttpServletRequest request) {
        String errorMessage = String.format("Missing required parameter: %s", ex.getParameterName());

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.BAD_REQUEST,
                request,
                null,
                null
        );

        log.warn("Missing request parameter: {} | Path: {}", ex.getParameterName(), request.getRequestURI());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseEntity<ErrorResponseDto> handleMethodNotSupported(
            HttpRequestMethodNotSupportedException ex,
            HttpServletRequest request) {
        String errorMessage = String.format("HTTP method '%s' is not supported for this endpoint. Supported methods: %s",
                ex.getMethod(), ex.getSupportedHttpMethods());

        ErrorResponseDto errorResponse = buildErrorResponse(
                errorMessage,
                errorMessage,
                HttpStatus.METHOD_NOT_ALLOWED,
                request,
                null,
                null
        );

        log.warn("Method not supported: {} | Path: {}", ex.getMethod(), request.getRequestURI());
        return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(errorResponse);
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<ErrorResponseDto> handleNoHandlerFound(
            NoHandlerFoundException ex,
            HttpServletRequest request) {
        String errorMessage = String.format("No handler found for %s %s", ex.getHttpMethod(), ex.getRequestURL());

        ErrorResponseDto errorResponse = buildErrorResponse(
                "Endpoint not found",
                errorMessage,
                HttpStatus.NOT_FOUND,
                request,
                null,
                null
        );

        log.warn("No handler found: {} {} | Path: {}", ex.getHttpMethod(), ex.getRequestURL(), request.getRequestURI());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
    }

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<ErrorResponseDto> handleRuntimeException(
            RuntimeException ex,
            HttpServletRequest request) {
        String correlationId = CorrelationIdUtil.getCorrelationId();
        ErrorResponseDto errorResponse = buildErrorResponse(
                "An unexpected error occurred",
                "An unexpected error occurred. Please try again later.",
                HttpStatus.INTERNAL_SERVER_ERROR,
                request,
                null,
                null
        );

        log.error("Unexpected runtime exception | Correlation ID: {} | Path: {}", correlationId, request.getRequestURI(), ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponseDto> handleGenericException(
            Exception ex,
            HttpServletRequest request) {
        String correlationId = CorrelationIdUtil.getCorrelationId();
        ErrorResponseDto errorResponse = buildErrorResponse(
                "An internal server error occurred",
                "An internal server error occurred. Please try again later.",
                HttpStatus.INTERNAL_SERVER_ERROR,
                request,
                null,
                null
        );

        log.error("Unexpected exception | Correlation ID: {} | Path: {}", correlationId, request.getRequestURI(), ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
    }
}
