package de.effigenix.infrastructure.shared.web.exception;

import de.effigenix.domain.inventory.InventoryCountError;
import de.effigenix.domain.inventory.StockMovementError;
import de.effigenix.domain.inventory.StorageLocationError;
import de.effigenix.domain.inventory.StockError;
import de.effigenix.domain.masterdata.article.ArticleError;
import de.effigenix.domain.masterdata.productcategory.ProductCategoryError;
import de.effigenix.domain.masterdata.customer.CustomerError;
import de.effigenix.domain.masterdata.supplier.SupplierError;
import de.effigenix.domain.production.BatchError;
import de.effigenix.domain.production.ProductionOrderError;
import de.effigenix.domain.production.RecipeError;
import de.effigenix.domain.usermanagement.UserError;
import de.effigenix.infrastructure.inventory.web.controller.InventoryCountController;
import de.effigenix.infrastructure.inventory.web.controller.StockMovementController;
import de.effigenix.infrastructure.inventory.web.controller.StorageLocationController;
import de.effigenix.infrastructure.inventory.web.controller.StockController;
import de.effigenix.infrastructure.inventory.web.exception.InventoryErrorHttpStatusMapper;
import de.effigenix.infrastructure.masterdata.web.controller.ArticleController;
import de.effigenix.infrastructure.masterdata.web.controller.CustomerController;
import de.effigenix.infrastructure.masterdata.web.controller.ProductCategoryController;
import de.effigenix.infrastructure.masterdata.web.controller.SupplierController;
import de.effigenix.infrastructure.production.web.controller.BatchController;
import de.effigenix.infrastructure.production.web.controller.ProductionOrderController;
import de.effigenix.infrastructure.production.web.controller.RecipeController;
import de.effigenix.infrastructure.production.web.exception.ProductionErrorHttpStatusMapper;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.infrastructure.masterdata.web.exception.MasterDataErrorHttpStatusMapper;
import de.effigenix.infrastructure.usermanagement.web.controller.AuthController;
import de.effigenix.infrastructure.usermanagement.web.controller.RoleController;
import de.effigenix.infrastructure.usermanagement.web.controller.UserController;
import de.effigenix.infrastructure.usermanagement.web.exception.UserErrorHttpStatusMapper;

import io.sentry.Sentry;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;
import java.util.stream.Collectors;

/**
 * Global Exception Handler for REST API.
 *
 * Provides centralized exception handling for all REST controllers.
 * Converts exceptions to consistent error responses with appropriate HTTP status codes.
 *
 * Error Handling Strategy:
 * - Domain errors (UserError) → HTTP status from ApplicationError.httpStatus()
 * - Validation errors → 400 Bad Request
 * - Authentication errors → 401 Unauthorized
 * - Authorization errors → 403 Forbidden
 * - Unexpected errors → 500 Internal Server Error
 *
 * Infrastructure Layer → Spring Web Exception Handling
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * Handles AuthenticationFailedException (wraps UserError from auth operations).
     *
     * @param ex Authentication failed exception
     * @param request HTTP request
     * @return Error response with appropriate status code
     */
    @ExceptionHandler(AuthController.AuthenticationFailedException.class)
    public ResponseEntity<ErrorResponse> handleAuthenticationFailed(
            AuthController.AuthenticationFailedException ex,
            HttpServletRequest request
    ) {
        UserError error = ex.getError();
        int status = UserErrorHttpStatusMapper.toHttpStatus(error);
        logger.warn("Authentication failed: {} - {}", error.code(), error.message());

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                error.message(),
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    /**
     * Handles DomainErrorException (wraps UserError from user management operations).
     *
     * @param ex Domain error exception
     * @param request HTTP request
     * @return Error response with appropriate status code
     */
    @ExceptionHandler(UserController.DomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleDomainError(
            UserController.DomainErrorException ex,
            HttpServletRequest request
    ) {
        UserError error = ex.getError();
        int status = UserErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("User", error.code(), error.message(), status);

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                error.message(),
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(ArticleController.ArticleDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleArticleDomainError(
            ArticleController.ArticleDomainErrorException ex,
            HttpServletRequest request
    ) {
        ArticleError error = ex.getError();
        int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("Article", error.code(), error.message(), status);

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                error.message(),
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(ProductCategoryController.ProductCategoryDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleProductCategoryDomainError(
            ProductCategoryController.ProductCategoryDomainErrorException ex,
            HttpServletRequest request
    ) {
        ProductCategoryError error = ex.getError();
        int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("ProductCategory", error.code(), error.message(), status);

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                error.message(),
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(SupplierController.SupplierDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleSupplierDomainError(
            SupplierController.SupplierDomainErrorException ex,
            HttpServletRequest request
    ) {
        SupplierError error = ex.getError();
        int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("Supplier", error.code(), error.message(), status);

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                error.message(),
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(CustomerController.CustomerDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleCustomerDomainError(
            CustomerController.CustomerDomainErrorException ex,
            HttpServletRequest request
    ) {
        CustomerError error = ex.getError();
        int status = MasterDataErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("Customer", error.code(), error.message(), status);

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                error.message(),
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(StorageLocationController.StorageLocationDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleStorageLocationDomainError(
            StorageLocationController.StorageLocationDomainErrorException ex,
            HttpServletRequest request
    ) {
        StorageLocationError error = ex.getError();
        int status = InventoryErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("StorageLocation", error.code(), error.message(), status);

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                error.message(),
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(StockMovementController.StockMovementDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleStockMovementDomainError(
            StockMovementController.StockMovementDomainErrorException ex,
            HttpServletRequest request
    ) {
        StockMovementError error = ex.getError();
        int status = InventoryErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("StockMovement", error.code(), error.message(), status);

        String clientMessage = status >= 500
                ? "An internal error occurred"
                : error.message();

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                clientMessage,
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(StockController.StockDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleStockDomainError(
            StockController.StockDomainErrorException ex,
            HttpServletRequest request
    ) {
        StockError error = ex.getError();
        int status = InventoryErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("Stock", error.code(), error.message(), status);

        String clientMessage = status >= 500
                ? "An internal error occurred"
                : error.message();

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                clientMessage,
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(InventoryCountController.InventoryCountDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleInventoryCountDomainError(
            InventoryCountController.InventoryCountDomainErrorException ex,
            HttpServletRequest request
    ) {
        InventoryCountError error = ex.getError();
        int status = InventoryErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("InventoryCount", error.code(), error.message(), status);

        String clientMessage = status >= 500
                ? "An internal error occurred"
                : error.message();

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                clientMessage,
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(RecipeController.RecipeDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleRecipeDomainError(
            RecipeController.RecipeDomainErrorException ex,
            HttpServletRequest request
    ) {
        RecipeError error = ex.getError();
        int status = ProductionErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("Recipe", error.code(), error.message(), status);

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                error.message(),
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(ProductionOrderController.ProductionOrderDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleProductionOrderDomainError(
            ProductionOrderController.ProductionOrderDomainErrorException ex,
            HttpServletRequest request
    ) {
        ProductionOrderError error = ex.getError();
        int status = ProductionErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("ProductionOrder", error.code(), error.message(), status);

        String clientMessage = status >= 500
                ? "An internal error occurred"
                : error.message();

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                clientMessage,
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(BatchController.BatchDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleBatchDomainError(
            BatchController.BatchDomainErrorException ex,
            HttpServletRequest request
    ) {
        BatchError error = ex.getError();
        int status = ProductionErrorHttpStatusMapper.toHttpStatus(error);
        logDomainError("Batch", error.code(), error.message(), status);

        String clientMessage = status >= 500
                ? "An internal error occurred"
                : error.message();

        ErrorResponse errorResponse = ErrorResponse.from(
                error.code(),
                clientMessage,
                status,
                request.getRequestURI()
        );

        return ResponseEntity.status(status).body(errorResponse);
    }

    @ExceptionHandler(RoleController.RoleDomainErrorException.class)
    public ResponseEntity<ErrorResponse> handleRoleDomainError(
            RoleController.RoleDomainErrorException ex,
            HttpServletRequest request
    ) {
        RepositoryError error = ex.getError();
        logger.error("Role repository error: {}", error.message());
        Sentry.captureException(ex);

        ErrorResponse errorResponse = ErrorResponse.from(
                "REPOSITORY_ERROR",
                error.message(),
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                request.getRequestURI()
        );

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
    }

    /**
     * Handles validation errors from @Valid annotations.
     *
     * Returns 400 Bad Request with list of validation errors.
     *
     * @param ex Validation exception
     * @param request HTTP request
     * @return Error response with validation errors
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationError(
            MethodArgumentNotValidException ex,
            HttpServletRequest request
    ) {
        logger.warn("Validation error: {}", ex.getMessage());

        List<ErrorResponse.ValidationError> validationErrors = ex.getBindingResult()
                .getAllErrors()
                .stream()
                .map(error -> {
                    String fieldName = error instanceof FieldError fieldError
                            ? fieldError.getField()
                            : error.getObjectName();
                    String message = error.getDefaultMessage();
                    return new ErrorResponse.ValidationError(fieldName, message);
                })
                .collect(Collectors.toList());

        ErrorResponse errorResponse = ErrorResponse.withValidationErrors(
                "Validation failed",
                HttpStatus.BAD_REQUEST.value(),
                request.getRequestURI(),
                validationErrors
        );

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(errorResponse);
    }

    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<ErrorResponse> handleAuthenticationError(
            AuthenticationException ex,
            HttpServletRequest request
    ) {
        logger.warn("Authentication error: {}", ex.getMessage());

        ErrorResponse errorResponse = ErrorResponse.from(
                "AUTHENTICATION_FAILED",
                "Authentication failed",
                HttpStatus.UNAUTHORIZED.value(),
                request.getRequestURI()
        );

        return ResponseEntity
                .status(HttpStatus.UNAUTHORIZED)
                .body(errorResponse);
    }

    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<ErrorResponse> handleAccessDeniedError(
            AccessDeniedException ex,
            HttpServletRequest request
    ) {
        logger.warn("Access denied: {}", ex.getMessage());

        ErrorResponse errorResponse = ErrorResponse.from(
                "ACCESS_DENIED",
                "Access denied",
                HttpStatus.FORBIDDEN.value(),
                request.getRequestURI()
        );

        return ResponseEntity
                .status(HttpStatus.FORBIDDEN)
                .body(errorResponse);
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ErrorResponse> handleIllegalArgumentError(
            IllegalArgumentException ex,
            HttpServletRequest request
    ) {
        logger.warn("Invalid argument: {}", ex.getMessage());

        ErrorResponse errorResponse = ErrorResponse.from(
                "INVALID_ARGUMENT",
                ex.getMessage(),
                HttpStatus.BAD_REQUEST.value(),
                request.getRequestURI()
        );

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(errorResponse);
    }

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<ErrorResponse> handleRuntimeError(
            RuntimeException ex,
            HttpServletRequest request
    ) {
        logger.error("Unexpected runtime error: {}", ex.getMessage(), ex);
        Sentry.captureException(ex);

        ErrorResponse errorResponse = ErrorResponse.from(
                "INTERNAL_ERROR",
                "An unexpected error occurred. Please contact support.",
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                request.getRequestURI()
        );

        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(errorResponse);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(
            Exception ex,
            HttpServletRequest request
    ) {
        logger.error("Unexpected checked exception: {}", ex.getMessage(), ex);
        Sentry.captureException(ex);

        ErrorResponse errorResponse = ErrorResponse.from(
                "INTERNAL_ERROR",
                "An unexpected error occurred. Please contact support.",
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                request.getRequestURI()
        );

        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(errorResponse);
    }

    private void logDomainError(String context, String code, String message, int status) {
        if (status >= 500) {
            logger.error("{} domain error: {} - {}", context, code, message);
        } else {
            logger.warn("{} domain error: {} - {}", context, code, message);
        }
    }
}
