package com.yami.shop.common.handler;

import cn.hutool.core.util.CharsetUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;

/**
 * HttpHandler类是一个Spring组件，主要用于处理将服务器响应信息输出到Web端的相关操作，它能够将封装好的响应实体（ServerResponseEntity）或者特定的业务异常（YamiShopBindException）信息
 * 按照合适的格式（JSON格式）写入到HTTP响应中，以便前端能够正确接收并解析后端返回的结果，同时在处理过程中进行了一些必要的日志记录以及异常处理，保证操作的稳定性和可靠性。
 *
 * @author 菠萝凤梨
 * @date 2022/3/28 14:15
 */
@Component
// 使用@Component注解将该类标记为Spring的组件，使得Spring能够扫描并管理这个类，将其纳入到Spring容器中，方便进行依赖注入等操作。
public class HttpHandler {

    // 创建一个名为logger的静态常量，用于记录日志信息，通过LoggerFactory获取HttpHandler类对应的Logger实例，方便在类的各个方法中记录不同级别的日志，
    // 例如记录操作过程中的错误信息、调试信息等，有助于排查问题和了解程序的执行情况。
    private static final Logger logger = LoggerFactory.getLogger(HttpHandler.class);

    // 使用@Autowired注解进行依赖注入，将Spring容器中管理的ObjectMapper实例注入到当前类中，ObjectMapper主要用于将Java对象转换为JSON字符串（序列化）以及将JSON字符串转换为Java对象（反序列化），
    // 在将ServerResponseEntity对象转换为JSON格式并写入HTTP响应时会用到它。
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 将ServerResponseEntity对象转换为JSON格式并输出到Web端（即写入HTTP响应）的方法。
     * 首先会对传入的ServerResponseEntity对象进行判空处理，如果为空则记录相应的日志信息并直接返回，不进行后续操作。
     * 接着获取当前请求的相关属性（ServletRequestAttributes），如果获取失败（为null）则记录错误日志并返回，因为没有请求上下文就无法进行响应输出操作。
     * 然后获取HttpServletResponse对象，如果该对象也为null同样记录错误日志并返回。
     * 之后设置响应的字符编码为UTF-8，并指定响应的内容类型为JSON格式（通过设置ContentType为APPLICATION_JSON_VALUE），
     * 最后通过ObjectMapper将ServerResponseEntity对象转换为JSON字符串，并使用PrintWriter将其写入到HTTP响应中，如果在写入过程中出现IOException异常，
     * 则抛出YamiShopBindException异常，并将原始的IOException作为原因封装进去，以便上层调用者能够处理这个异常情况。
     *
     * @param serverResponseEntity 封装了服务器响应信息（如响应码、消息、响应数据等）的实体对象，需要将其转换为JSON格式并输出到HTTP响应中，供前端接收和解析。
     * @param <T> 泛型参数，表示ServerResponseEntity中响应数据部分的类型，其具体类型取决于实际业务逻辑中返回的数据类型。
     */
    public <T> void printServerResponseToWeb(ServerResponseEntity<T> serverResponseEntity) {
        if (serverResponseEntity == null) {
            logger.info("print obj is null");
            return;
        }

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
               .getRequestAttributes();
        if (requestAttributes == null) {
            logger.error("requestAttributes is null, can not print to web");
            return;
        }
        HttpServletResponse response = requestAttributes.getResponse();
        if (response == null) {
            logger.error("httpServletResponse is null, can not print to web");
            return;
        }
        logger.error("response error: " + serverResponseEntity.getMsg());
        response.setCharacterEncoding(CharsetUtil.UTF_8);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter printWriter = null;
        try {
            printWriter = response.getWriter();
            printWriter.write(objectMapper.writeValueAsString(serverResponseEntity));
        }
        catch (IOException e) {
            throw new YamiShopBindException("io 异常", e);
        }
    }

    /**
     * 处理将YamiShopBindException异常信息输出到Web端（写入HTTP响应）的方法。
     * 首先对传入的YamiShopBindException对象进行判空处理，如果为空则记录相应日志信息并返回，不进行后续操作。
     * 接着判断该异常对象中是否已经包含了ServerResponseEntity对象（即是否已经有了预先定义好的响应实体），如果有则直接调用printServerResponseToWeb方法将其输出到HTTP响应中并返回。
     * 如果异常对象中不包含ServerResponseEntity对象，则创建一个新的ServerResponseEntity对象，将异常的错误码（通过getCode方法获取）和错误消息（通过getMessage方法获取）设置到这个新的响应实体中，
     * 然后再调用printServerResponseToWeb方法将其输出到HTTP响应中，使得前端能够接收到关于异常的详细信息，以合适的格式展示给用户或者进行相应的错误处理。
     *
     * @param yamiShopBindException 业务异常对象，包含了业务逻辑中出现错误时的相关信息，如错误码、错误消息等，需要将这些信息以合适的格式输出到HTTP响应中，以便前端知晓出现的问题。
     * @param <T> 泛型参数，在这里主要是为了与printServerResponseToWeb(ServerResponseEntity<T>)方法的参数类型保持一致，在实际使用中具体类型取决于业务逻辑中响应数据的类型情况。
     */
    public <T> void printServerResponseToWeb(YamiShopBindException yamiShopBindException) {
        if (yamiShopBindException == null) {
            logger.info("print obj is null");
            return;
        }

        if (Objects.nonNull(yamiShopBindException.getServerResponseEntity())) {
            printServerResponseToWeb(yamiShopBindException.getServerResponseEntity());
            return;
        }

        ServerResponseEntity<T> serverResponseEntity = new ServerResponseEntity<>();
        serverResponseEntity.setCode(yamiShopBindException.getCode());
        serverResponseEntity.setMsg(yamiShopBindException.getMessage());
        printServerResponseToWeb(serverResponseEntity);
    }
}