package at.procon.dip.ingestion.camel;

import at.procon.dip.domain.document.SourceType;
import at.procon.dip.ingestion.config.DipIngestionProperties;
import at.procon.dip.ingestion.mail.MailServerEndpointUriFactory;
import at.procon.dip.ingestion.spi.OriginalContentStoragePolicy;
import at.procon.dip.ingestion.spi.SourceDescriptor;
import at.procon.dip.ingestion.service.DocumentIngestionGateway;
import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode;
import at.procon.dip.runtime.config.RuntimeMode;
import jakarta.mail.Message;
import java.io.ByteArrayOutputStream;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.camel.Exchange;
import org.apache.camel.LoggingLevel;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mail.MailMessage;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

@Component
@ConditionalOnRuntimeMode(RuntimeMode.NEW)
@RequiredArgsConstructor
@Slf4j
public class GenericMailIngestionRoute extends RouteBuilder {

    static final String ROUTE_ID_MAIL_CONSUMER = "dip-mail-consumer";
    static final String ROUTE_ID_MAIL_INGEST = "dip-mail-ingest";

    private final DipIngestionProperties properties;
    private final DocumentIngestionGateway ingestionGateway;
    private final MailServerEndpointUriFactory endpointUriFactory;

    @Override
    public void configure() {
        if (!properties.isEnabled() || !properties.isMailAdapterEnabled() || !properties.getMailRoute().isEnabled()) {
            log.info("NEW mail ingestion Camel route disabled");
            return;
        }

        String consumerUri = endpointUriFactory.buildConsumerUri(properties.getMailRoute());
        log.info("Configuring NEW mail ingestion route (protocol={}, host={}, folder={}, user={})",
                properties.getMailRoute().getProtocol(),
                properties.getMailRoute().getHost(),
                properties.getMailRoute().getFolderName(),
                properties.getMailRoute().getUsername());

        errorHandler(deadLetterChannel("direct:dip-mail-error")
                .maximumRedeliveries(3)
                .redeliveryDelay(5000)
                .retryAttemptedLogLevel(LoggingLevel.WARN)
                .logStackTrace(true));

        from("direct:dip-mail-error")
                .routeId("dip-mail-error")
                .process(exchange -> {
                    Exception exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
                    String subject = exchange.getIn().getHeader("mailSubject", String.class);
                    if (exception != null) {
                        log.error("NEW mail ingestion failed for subject '{}': {}", subject, exception.getMessage(), exception);
                    }
                });

        from(consumerUri)
                .routeId(ROUTE_ID_MAIL_CONSUMER)
                .to("direct:dip-mail-ingest");

        from("direct:dip-mail-ingest")
                .routeId(ROUTE_ID_MAIL_INGEST)
                .process(this::ingestMailExchange)
                .log(LoggingLevel.INFO, "Imported mail message into NEW ingestion pipeline: ${header.mailSubject}");
    }

    private void ingestMailExchange(Exchange exchange) throws Exception {
        Message mailMessage = resolveMessage(exchange);
        if (mailMessage == null) {
            log.warn("Received null mail message, skipping NEW mail ingestion");
            return;
        }

        String subject = mailMessage.getSubject();
        String from = mailMessage.getFrom() != null && mailMessage.getFrom().length > 0
                ? mailMessage.getFrom()[0].toString() : null;
        String messageId = firstHeader(mailMessage, "Message-ID");
        String providerMessageKey = firstNonBlankHeader(exchange, "CamelMailUid", "uid", "mailUid", "MailUid");
        String providerThreadKey = firstNonBlankHeader(exchange, "CamelMailThreadId", "mailThreadId", "MailThreadId");

        exchange.getIn().setHeader("mailSubject", subject);

        byte[] rawMime;
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            mailMessage.writeTo(baos);
            rawMime = baos.toByteArray();
        }

        Map<String, String> attributes = new LinkedHashMap<>();
        putIfHasText(attributes, "subject", subject);
        putIfHasText(attributes, "from", from);
        putIfHasText(attributes, "providerType", properties.getMailRoute().getProtocol().name());
        putIfHasText(attributes, "providerProtocol", properties.getMailRoute().getProtocol().camelScheme());
        putIfHasText(attributes, "providerAccountKey", accountKey());
        putIfHasText(attributes, "providerFolderKey", properties.getMailRoute().getFolderName());
        putIfHasText(attributes, "providerMessageKey", providerMessageKey);
        putIfHasText(attributes, "providerThreadKey", providerThreadKey);
        putIfHasText(attributes, "messageId", messageId);

        SourceDescriptor descriptor = new SourceDescriptor(
                null,
                SourceType.MAIL,
                buildSourceIdentifier(providerMessageKey, messageId),
                buildSourceUri(providerMessageKey),
                fallbackMailFileName(subject),
                "message/rfc822",
                rawMime,
                null,
                mailMessage.getReceivedDate() == null
                        ? OffsetDateTime.now()
                        : mailMessage.getReceivedDate().toInstant().atZone(ZoneId.systemDefault()).toOffsetDateTime(),
                OriginalContentStoragePolicy.DEFAULT,
                attributes
        );
        ingestionGateway.ingest(descriptor);
    }

    private Message resolveMessage(Exchange exchange) {
        MailMessage camelMailMessage = exchange.getIn().getBody(MailMessage.class);
        if (camelMailMessage != null) {
            return camelMailMessage.getMessage();
        }
        return exchange.getIn().getBody(Message.class);
    }

    private String buildSourceIdentifier(String providerMessageKey, String messageId) {
        if (StringUtils.hasText(providerMessageKey)) {
            return properties.getMailRoute().getProtocol().name() + ":" + accountKey() + ":"
                    + defaultIfBlank(properties.getMailRoute().getFolderName(), "INBOX") + ":" + providerMessageKey;
        }
        if (StringUtils.hasText(messageId)) {
            return messageId;
        }
        return UUID.randomUUID().toString();
    }

    private String buildSourceUri(String providerMessageKey) {
        StringBuilder uri = new StringBuilder();
        uri.append(properties.getMailRoute().getProtocol().camelScheme())
                .append("://")
                .append(properties.getMailRoute().getHost())
                .append("/")
                .append(defaultIfBlank(properties.getMailRoute().getFolderName(), "INBOX"));
        if (StringUtils.hasText(providerMessageKey)) {
            uri.append("#").append(providerMessageKey);
        }
        return uri.toString();
    }

    private String fallbackMailFileName(String subject) {
        String safeSubject = !StringUtils.hasText(subject) ? "mail-message" : subject.replaceAll("[^A-Za-z0-9._-]", "_");
        return safeSubject + ".eml";
    }

    private String firstHeader(Message message, String name) {
        try {
            String[] values = message.getHeader(name);
            return values != null && values.length > 0 ? values[0] : null;
        } catch (Exception e) {
            return null;
        }
    }

    private String firstNonBlankHeader(Exchange exchange, String... names) {
        for (String name : names) {
            String value = exchange.getIn().getHeader(name, String.class);
            if (StringUtils.hasText(value)) {
                return value;
            }
            Object objectValue = exchange.getIn().getHeader(name);
            if (objectValue != null) {
                value = String.valueOf(objectValue);
                if (StringUtils.hasText(value)) {
                    return value;
                }
            }
        }
        return null;
    }

    private String accountKey() {
        return defaultIfBlank(properties.getMailRoute().getAccountKey(), properties.getMailRoute().getUsername());
    }

    private void putIfHasText(Map<String, String> attributes, String key, String value) {
        if (StringUtils.hasText(value)) {
            attributes.put(key, value);
        }
    }

    private String defaultIfBlank(String value, String defaultValue) {
        return StringUtils.hasText(value) ? value : defaultValue;
    }
}
