package com.intrasoft.ermis.transit.management.core.service.report;

import static com.intrasoft.ermis.transit.management.core.service.report.ReportServiceImpl.VALID_NP_REPORT_MESSAGE_TYPES;
import static com.intrasoft.ermis.transit.management.core.service.report.ReportServiceImpl.VALID_TAD_REPORT_MESSAGE_TYPES;

import com.intrasoft.ermis.common.xml.parsing.XmlConverter;
import com.intrasoft.ermis.ddntav5152.messages.CC013CType;
import com.intrasoft.ermis.ddntav5152.messages.CC015CType;
import com.intrasoft.ermis.ddntav5152.messages.CC029CType;
import com.intrasoft.ermis.ddntav5152.messages.CD001CType;
import com.intrasoft.ermis.ddntav5152.messages.CD003CType;
import com.intrasoft.ermis.ddntav5152.messages.CD038CType;
import com.intrasoft.ermis.ddntav5152.messages.CD050CType;
import com.intrasoft.ermis.ddntav5152.messages.CD115CType;
import com.intrasoft.ermis.ddntav5152.messages.CD160CType;
import com.intrasoft.ermis.ddntav5152.messages.CD165CType;
import com.intrasoft.ermis.ddntav5152.messages.MessageTypes;
import com.intrasoft.ermis.transit.common.exceptions.BaseException;
import com.intrasoft.ermis.transit.common.mappers.MessageTypeMapper;
import com.intrasoft.ermis.transit.management.domain.Message;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;

/**
 * <pre>
 * Generates a CC029C type message from a compatible DDNTA message type of one of the following types
 * CC015C
 * If the input message is already a CC029C type, then no conversion takes place
 * The generated CC029C message will be fed to the existing TAD report generation implementation
 */
@Service
@RequiredArgsConstructor
public class ReportMessageTranslatorImpl implements ReportMessageTranslatorService{

    private final MessageTypeMapper messageTypeMapper;

    public Message generateCC029CTypeMessageFromOriginalMessage(Message originalMessage) {
        MessageTypes originalMessageType = findMessageType(originalMessage);

        if (originalMessageType == MessageTypes.CC_029_C) {
            return originalMessage;
        } else if (VALID_TAD_REPORT_MESSAGE_TYPES.contains(originalMessageType)) {
            return mapToCC029C(originalMessage, originalMessageType);
        } else {
            throw new BaseException(String.format("IE message of type: '%s' is not valid for TAD generation", originalMessageType.value()));
        }
    }

    public Message generateCC015CTypeMessageFromOriginalMessage(Message originalMessage) {
        MessageTypes originalMessageType = findMessageType(originalMessage);

        if (originalMessageType == MessageTypes.CC_015_C) {
            return originalMessage;
        } else if (VALID_NP_REPORT_MESSAGE_TYPES.contains(originalMessageType)) {
            return mapToCC015C(originalMessage, originalMessageType);
        } else {
            throw new BaseException(String.format("IE message of type: '%s' is not valid for TAD generation", originalMessageType.value()));
        }
    }

    public static MessageTypes findMessageType(Message message) {
        String messageId = message.getId();
        String messageType = message.getMessageType();
        try {
            return MessageTypes.fromValue(messageType);
        } catch (IllegalArgumentException ex) {
            throw new IllegalArgumentException(String.format("Unknown DDNTA message type: '%s' for IE message with id '%s'.", messageType, messageId), ex);
        }
    }

    protected Message mapToCC029C(Message message, MessageTypes messageType) {
        return generateCC029CMessage(message.getPayload(), generateDDNTAClassFromMessageType(messageType));
    }

    protected Message mapToCC015C(Message message, MessageTypes messageType) {
        return generateCC015CMessage(message.getPayload(), generateDDNTAClassFromMessageType(messageType));
    }

    @SneakyThrows
    protected <T> Message generateCC029CMessage(String xmlPayload, Class<T> clazz) {
        T t = XmlConverter.buildMessageFromXML(xmlPayload, clazz);
        return Message.builder().payload(
                              XmlConverter.marshal(
                                      enhanceGeneratedCC029C(convertToCC029C(t))))
                      .messageType(MessageTypes.CC_029_C.value())
                      .build();
    }

    @SneakyThrows
    protected <T> Message generateCC015CMessage(String xmlPayload, Class<T> clazz) {
        T t = XmlConverter.buildMessageFromXML(xmlPayload, clazz);
        return Message.builder().payload(
                              XmlConverter.marshal(
                                      enhanceGeneratedCC015C(convertToCC015C(t))))
                      .messageType(MessageTypes.CC_015_C.value())
                      .build();
    }

    protected <T> CC029CType convertToCC029C(T t) {
        CC029CType result;
        var messageType = MessagTypesConvertibleToCC029CEnum.fromValue(t.getClass().getSimpleName());
        switch (Objects.requireNonNull(messageType)) {
            case CC015C:
                result = messageTypeMapper.fromCC015CtoCC029C((CC015CType) t);
                break;
            case CD001C:
                result = messageTypeMapper.fromCD001CToCC029C((CD001CType) t);
                break;
            case CD003C:
                result = messageTypeMapper.fromCD003CToCC029C((CD003CType) t);
                break;
            case CD160C:
                result = messageTypeMapper.fromCD160CToCC029C((CD160CType) t);
                break;
            case CD165C:
                result = messageTypeMapper.fromCD165CToCC029C((CD165CType) t);
                break;
            case CD050C:
                result = messageTypeMapper.fromCD050CToCC029C((CD050CType) t);
                break;
            case CD115C:
                result = messageTypeMapper.fromCD115CToCC029C((CD115CType) t);
                break;
            case CD038C:
                result = messageTypeMapper.fromCD038CToCC029C((CD038CType) t);
                break;
            default:
                throw new BaseException("Invalid message Type Class: " + t.getClass().getSimpleName());
        }
        result.setMessageType(MessageTypes.CC_029_C);
        return result;
    }

    protected <T> CC015CType convertToCC015C(T t) {
        CC015CType result;
        var messageType = MessageTypesConvertibleToCC013CEnum.fromValue(t.getClass().getSimpleName());
        switch (Objects.requireNonNull(messageType)) {
            case CC013C:
                result = messageTypeMapper.fromCC013toCC015CType((CC013CType) t);
                break;
            default:
                throw new BaseException("Invalid message Type Class: " + t.getClass().getSimpleName());
        }
        result.setMessageType(MessageTypes.CC_015_C);
        return result;
    }

    /**
     * Potentially enrich the generated CC029C with possible extra elements that are missing in the Original Documents and are needed in the TAD document.
     * Nothing found till now though e.g. <combinedNomenclatureCode></combinedNomenclatureCode>
     */
    private static CC029CType enhanceGeneratedCC029C(CC029CType cc029CType) {
        return cc029CType;
    }

    private static CC015CType enhanceGeneratedCC015C(CC015CType cc015Type) {
        return cc015Type;
    }

    @SneakyThrows
    public static Class<?> generateDDNTAClassFromMessageType(MessageTypes messageType) {
        return Class.forName(MessageTypes.class.getPackageName() + "." + messageType.value() + "Type");
    }

    @RequiredArgsConstructor
    enum MessagTypesConvertibleToCC029CEnum {
        CD001C(CD001CType.class.getSimpleName()),
        CD003C(CD003CType.class.getSimpleName()),
        CD160C(CD160CType.class.getSimpleName()),
        CD165C(CD165CType.class.getSimpleName()),
        CD050C(CD050CType.class.getSimpleName()),
        CD115C(CD115CType.class.getSimpleName()),
        CD038C(CD038CType.class.getSimpleName()),
        CC015C(CC015CType.class.getSimpleName());

        private final String classSimpleName;

        public static MessagTypesConvertibleToCC029CEnum fromValue(String value) {
            for (MessagTypesConvertibleToCC029CEnum e : values()) {
                if (e.classSimpleName.equals(value)) {
                    return e;
                }
            }
            return null;
        }
    }

    @RequiredArgsConstructor
    enum MessageTypesConvertibleToCC013CEnum {
        CC013C(CC013CType.class.getSimpleName());

        private final String classSimpleName;

        public static MessageTypesConvertibleToCC013CEnum fromValue(String value) {
            for (MessageTypesConvertibleToCC013CEnum e : values()) {
                if (e.classSimpleName.equals(value)) {
                    return e;
                }
            }
            return null;
        }
    }
}

package com.intrasoft.ermis.transit.management.core.service;

import com.intrasoft.ermis.transit.common.mappers.MessageTypeMapper;
import com.intrasoft.ermis.transit.common.mappers.MessageTypeMapperImpl;
import com.intrasoft.ermis.transit.management.core.service.report.ReportMessageTranslatorImpl;
import com.intrasoft.ermis.transit.management.domain.Message;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class ReportMessageTranslatorUT {

    @Spy
    private MessageTypeMapper messageTypeMapper = new MessageTypeMapperImpl();
    @InjectMocks
    private ReportMessageTranslatorImpl messageTranslator;
    @Test
    void testGenerateCC029CTypeMessageFromOriginalMessage() {

        Message originalMessage = Message.builder()
                                         .id("123")
                                         .messageType("CC015C")
                                         .payload("<xml>...</xml>")
                                         .build();

        Message resultMessage = messageTranslator.generateCC029CTypeMessageFromOriginalMessage(originalMessage);

        assertNotNull(resultMessage);
        assertEquals("CC029C", resultMessage.getMessageType());
    }

    @Test
    void testGenerateCC029CTypeMessageFromOriginalMessageWhenAlreadyCC029C() {
        // Test the case where input type is already CC029C
        Message originalMessage = Message.builder()
                                         .id("123")
                                         .messageType("CC029C")
                                         .payload("<xml>...</xml>")
                                         .build();

        Message resultMessage = messageTranslator.generateCC029CTypeMessageFromOriginalMessage(originalMessage);

        assertNotNull(resultMessage);
        assertEquals(originalMessage, resultMessage);
    }

}
