package infrastructure.client.accounts.adapter;

import application.exception.GatewayServiceException;
import com.banesco.common.infraestructure.helpers.LoggerHelper;
import com.fasterxml.jackson.databind.ObjectMapper;
import domain.dto.request.CustomerRequest;
import domain.dto.response.CustomerResponse;
import domain.model.ErrorCode;
import domain.model.ExecutionResult;
import infrastructure.client.accounts.api.CustomerAccountClient;
import infrastructure.service.StatusDescriptionResolver;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.rest.client.inject.RestClient;

@Slf4j
@ApplicationScoped
public class CustomerProductAdapter {

    private final CustomerAccountClient client;
    private final ObjectMapper objectMapper;
    private final StatusDescriptionResolver statusDescriptionResolver;

    public CustomerProductAdapter(
            @RestClient CustomerAccountClient client,
            ObjectMapper objectMapper,
            StatusDescriptionResolver statusDescriptionResolver
    ) {
        this.client = client;
        this.objectMapper = objectMapper;
        this.statusDescriptionResolver = statusDescriptionResolver;
    }

    public ExecutionResult getCustomerProduct(
            CustomerRequest request,
            String requestId,
            String appId,
            String customerReferenceFintechId,
            String customerReferenceUser
    ) {
        try {
            log.info(LoggerHelper.buildInfoPrivateRequest(requestId, "Llamando al servicio de bus-evaluate-customer-product", request));

            // Mock para cliente sin productos
            if (request.getCustomerPositionState() != null 
                    && request.getCustomerPositionState().getCustomerReference() != null
                    && "V".equals(request.getCustomerPositionState().getCustomerReference().getCustomerIdType())
                    && "8678988".equals(request.getCustomerPositionState().getCustomerReference().getCustomerId())) {
                
                domain.dto.response.StatusResponse statusResponse = domain.dto.response.StatusResponse.builder()
                        .status("ok")
                        .statusCode("200")
                        .message("Cliente sin productos")
                        .traceId(requestId)
                        .build();

                domain.dto.response.CustomerPositionStateResponse customerPositionState =
                        domain.dto.response.CustomerPositionStateResponse.builder()
                                .record(0)
                                .bankingProducts(java.util.Collections.emptyList())
                                .build();

                CustomerResponse customerResponse = new CustomerResponse(statusResponse, customerPositionState);

                return ExecutionResult.success(customerResponse);
            }

            if (request.getProductReferenceSearch() != null && !request.getProductReferenceSearch().isEmpty()) {
                // Mock para cliente con productos
                domain.dto.response.StatusResponse statusResponse = domain.dto.response.StatusResponse.builder()
                        .status("ok")
                        .statusCode("200")
                        .message("Operacion Exitosa")
                        .traceId(requestId)
                        .build();

                // Producto
                domain.model.BankingProduct product = domain.model.BankingProduct.builder()
                        .productNumber("****-****-****-8514")
                        .productType("TDB")
                        // .balance(null)
                        // .currency(null)
                        .build();

                java.util.List<domain.model.BankingProduct> products = java.util.Collections.singletonList(product);

                // CustomerPositionStateResponse
                domain.dto.response.CustomerPositionStateResponse customerPositionState =
                        domain.dto.response.CustomerPositionStateResponse.builder()
                                .record(1)
                                .bankingProducts(products)
                                .build();

                // CustomerResponse
                CustomerResponse customerResponse = new CustomerResponse(statusResponse, customerPositionState);

                return ExecutionResult.success(customerResponse);
            }

            var response = client.readCustomerProducts(
                    requestId,
                    appId,
                    customerReferenceFintechId,
                    customerReferenceUser,
                    request
            );

            if (response == null) {
                log.warn(LoggerHelper.buildError(requestId, "Respuesta invalida o nula del servicio de bus-evaluate-customer-product"));
                throw new GatewayServiceException("Respuesta invalida del servicio externo de bus-evaluate-customer-product");
            }

            return ExecutionResult.success(response);

        } catch (WebApplicationException ex) {
            return handleHttpError(ex, requestId);

        } catch (RuntimeException ex) {
            // Error de red, timeout, etc.
            throw handleNetworkError(ex, requestId);
        }
    }

    /**
     * Orquesta el manejo de errores HTTP (WebApplicationException).
     */
    private ExecutionResult handleHttpError(WebApplicationException ex, String requestId) {
        var errorResponse = ex.getResponse();
        int status = errorResponse.getStatus();
        String errorBody = readErrorBody(errorResponse);

        // Si es un error de infraestructura (HTML), lanza excepción.
        if (isInfrastructureError(errorResponse)) {
            String htmlError = String.format("Error HTTP %s del servicio. Se recibio una respuesta HTML no esperada.", status);
            log.error(LoggerHelper.buildError(requestId, htmlError));
            throw new GatewayServiceException(htmlError);
        }

        // Si no es HTML, debe ser un error de negocio (JSON). Intentamos parsearlo.
        try {
            return parseBusinessError(errorBody, status, requestId);
        } catch (Exception parseEx) {
            // Si falla el parseo, es un error de infraestructura (contrato JSON roto).
            var rawError = String.format("Error HTTP no manejado (body no parseable). Status: %s. Body: %s",
                    status, errorBody.substring(0, Math.min(errorBody.length(), 1000)));
            log.error(LoggerHelper.buildError(requestId, rawError), parseEx);
            throw new GatewayServiceException(rawError, parseEx);
        }
    }

    /**
     * Parsea un error de negocio (JSON) y lo convierte en un ExecutionResult.
     */
    private ExecutionResult parseBusinessError(String errorBody, int httpStatus, String requestId) throws com.fasterxml.jackson.core.JsonProcessingException {
        var errorDto = objectMapper.readValue(errorBody, CustomerResponse.class);

        if (errorDto != null && errorDto.getStatusResponse() != null) {
            var statusCode = errorDto.getStatusResponse().getStatusCode();
            var message = errorDto.getStatusResponse().getMessage();

            var bizError = String.format("Error de negocio (HTTP %s) del servicio. Code: %s, Message: %s", httpStatus, statusCode, message);
            log.warn(LoggerHelper.buildError(requestId, bizError));

            var error = resolveErrorCode(statusCode, message, httpStatus, requestId);
            return ExecutionResult.failure(error);
        }

        // Si el JSON es válido, pero incompleto (ej. statusResponse: null), es un error de contrato
        log.error(LoggerHelper.buildError(requestId, "Error de contrato: El JSON de error no contiene 'statusResponse'"));
        throw new GatewayServiceException("Error de contrato: JSON de error invalido");
    }

    /**
     * Busca el ErrorCode o crea uno de fallback.
     */
    private ErrorCode resolveErrorCode(String statusCode, String message, int httpStatus, String requestId) {
        var error = statusDescriptionResolver.getCode(statusCode);
        if (error == null) {
            log.warn("Codigo de error '{}' no encontrado en StatusDescriptionResolver. Usando valores del body.", statusCode);
            error = new ErrorCode(
                    statusCode,
                    String.valueOf(httpStatus),
                    statusCode,
                    message,
                    "error"
            );
        }
        return error;
    }

    /**
     * Lee de forma segura el body de una respuesta de error.
     */
    private String readErrorBody(Response errorResponse) {
        if (!errorResponse.hasEntity()) {
            return "No body";
        }
        try {
            return errorResponse.readEntity(String.class);
        } catch (Exception e) {
            log.error("No se pudo leer el stream del body de error: {}", e.getMessage());
            return "Error al leer stream";
        }
    }

    /**
     * Comprueba si la respuesta es un error de infraestructura (ej. HTML).
     */
    private boolean isInfrastructureError(Response errorResponse) {
        var mediaType = errorResponse.getMediaType();
        return mediaType != null && mediaType.toString().contains("text/html");
    }

    /**
     * Maneja errores de red/comunicación (RuntimeException).
     */
    private GatewayServiceException handleNetworkError(RuntimeException ex, String requestId) {
        log.error(LoggerHelper.buildError(
                requestId,
                String.format("Error de comunicacion con el servicio: %s", ex.getMessage())
        ), ex);
        return new GatewayServiceException("No se pudo comunicar con el servicio externo de bus-evaluate-customer-product.", ex);
    }
}