package at.procon.eventhub.tachographfilesession.service;

import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.processing.driverworkingtime.model.DriverWorkingTimeDerivedProjectionBundle;
import at.procon.eventhub.processing.driverworkingtime.service.DriverWorkingTimeReusableProjectionBuilder;
import at.procon.eventhub.tachographfilesession.model.DriverExtractionSession;
import at.procon.eventhub.tachographfilesession.model.ResolvedDriverTimeline;
import at.procon.eventhub.tachographfilesession.model.TachographEsperDrivingDerivedProjectionBundle;
import at.procon.eventhub.tachographfilesession.model.TachographFileSession;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Tachograph-owned compatibility facade that adapts legacy file-session and event-based callers
 * onto the neutral driver working-time projection engine.
 */
@Component
public class TachographDerivedProjectionCompatibilityFacade {

    private final DriverTimelineBuilder driverTimelineBuilder;
    private final RawSourceDriverTimelineEventBuilder rawSourceEventBuilder;
    private final TachographEventTimelineReconstructionHelper reconstructionHelper;
    private final EventHubProperties properties;
    private final DriverWorkingTimeReusableProjectionBuilder commonBuilder;

    public TachographDerivedProjectionCompatibilityFacade(
            DriverTimelineBuilder driverTimelineBuilder,
            EventHubProperties properties
    ) {
        this(
                driverTimelineBuilder,
                null,
                new TachographEventTimelineReconstructionHelper(),
                properties,
                new DriverWorkingTimeReusableProjectionBuilder(properties)
        );
    }

    public TachographDerivedProjectionCompatibilityFacade(
            DriverTimelineBuilder driverTimelineBuilder,
            RawSourceDriverTimelineEventBuilder rawSourceEventBuilder,
            EventHubProperties properties
    ) {
        this(
                driverTimelineBuilder,
                rawSourceEventBuilder,
                new TachographEventTimelineReconstructionHelper(),
                properties,
                new DriverWorkingTimeReusableProjectionBuilder(properties)
        );
    }

    @Autowired
    public TachographDerivedProjectionCompatibilityFacade(
            DriverTimelineBuilder driverTimelineBuilder,
            RawSourceDriverTimelineEventBuilder rawSourceEventBuilder,
            TachographEventTimelineReconstructionHelper reconstructionHelper,
            EventHubProperties properties,
            DriverWorkingTimeReusableProjectionBuilder commonBuilder
    ) {
        this.driverTimelineBuilder = driverTimelineBuilder;
        this.rawSourceEventBuilder = rawSourceEventBuilder;
        this.reconstructionHelper = reconstructionHelper == null
                ? new TachographEventTimelineReconstructionHelper()
                : reconstructionHelper;
        this.properties = properties;
        this.commonBuilder = commonBuilder;
    }

    public DriverWorkingTimeReusableProjectionBuilder commonBuilder() {
        return commonBuilder;
    }

    public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
            TachographFileSession session,
            DriverExtractionSession driverSession,
            int significantDrivingMinutes,
            int minimumRestPeriodMinutes
    ) {
        if (session == null || driverSession == null || driverSession.driverKey() == null) {
            return TachographDriverWorkingTimeAdapter.emptyLegacyBundle();
        }
        if (drivingDerivedProjectionInputMode() == EventHubProperties.DrivingDerivedProjectionInputMode.EVENTS) {
            return buildEsperDrivingDerivedProjectionBundleFromEvents(
                    session.sessionId(),
                    driverSession.driverKey(),
                    rawSourceEventBuilder().buildRawEventBundle(session, driverSession).allEvents(),
                    significantDrivingMinutes,
                    minimumRestPeriodMinutes
            );
        }
        ResolvedDriverTimeline timeline = driverTimelineBuilder.build(session, driverSession);
        return buildEsperDrivingDerivedProjectionBundle(
                session.sessionId(),
                driverSession.driverKey(),
                timeline,
                significantDrivingMinutes,
                minimumRestPeriodMinutes
        );
    }

    public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
            TachographFileSession session,
            int significantDrivingMinutes,
            int minimumRestPeriodMinutes
    ) {
        if (session == null || session.driversByKey() == null || session.driversByKey().isEmpty()) {
            return TachographDriverWorkingTimeAdapter.emptyLegacyBundle();
        }

        List<TachographEsperDrivingDerivedProjectionBundle> bundles = session.driversByKey().values().stream()
                .map(driverSession -> buildEsperDrivingDerivedProjectionBundle(
                        session,
                        driverSession,
                        significantDrivingMinutes,
                        minimumRestPeriodMinutes
                ))
                .toList();

        return TachographDriverWorkingTimeAdapter.mergeLegacyBundles(bundles);
    }

    public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundle(
            UUID sessionId,
            String driverKey,
            ResolvedDriverTimeline timeline,
            int significantDrivingMinutes,
            int minimumRestPeriodMinutes
    ) {
        if (timeline == null || driverKey == null || driverKey.isBlank()) {
            return TachographDriverWorkingTimeAdapter.emptyLegacyBundle();
        }
        DriverWorkingTimeDerivedProjectionBundle bundle = commonBuilder.buildDerivedProjectionBundle(
                TachographDriverWorkingTimeAdapter.toProcessingInput(
                        sessionId == null ? new UUID(0L, 0L) : sessionId,
                        driverKey,
                        timeline,
                        null,
                        null,
                        significantDrivingMinutes,
                        minimumRestPeriodMinutes,
                        List.of()
                )
        );
        return TachographDriverWorkingTimeAdapter.toLegacyBundle(bundle);
    }

    public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundleFromEvents(
            List<EventHubEventDto> events,
            int significantDrivingMinutes,
            int minimumRestPeriodMinutes
    ) {
        return buildEsperDrivingDerivedProjectionBundleFromEvents(
                null,
                null,
                events,
                significantDrivingMinutes,
                minimumRestPeriodMinutes
        );
    }

    public TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundleFromEvents(
            UUID fallbackSessionId,
            String fallbackDriverKey,
            List<EventHubEventDto> events,
            int significantDrivingMinutes,
            int minimumRestPeriodMinutes
    ) {
        if (fallbackDriverKey == null) {
            Map<String, List<EventHubEventDto>> eventsByDriver = reconstructionHelper.groupEventsByDriverKey(events);
            if (eventsByDriver.isEmpty()) {
                return TachographDriverWorkingTimeAdapter.emptyLegacyBundle();
            }
            if (eventsByDriver.size() > 1) {
                return buildEsperDrivingDerivedProjectionBundleFromGroupedEvents(
                        fallbackSessionId,
                        eventsByDriver,
                        significantDrivingMinutes,
                        minimumRestPeriodMinutes
                );
            }
            Map.Entry<String, List<EventHubEventDto>> onlyDriver = eventsByDriver.entrySet().iterator().next();
            fallbackDriverKey = onlyDriver.getKey();
            events = onlyDriver.getValue();
        }

        ResolvedDriverTimeline timeline = reconstructionHelper.reconstructMergedTimelineFromEvents(
                fallbackSessionId,
                fallbackDriverKey,
                events
        );
        return buildEsperDrivingDerivedProjectionBundle(
                fallbackSessionId == null ? new UUID(0L, 0L) : fallbackSessionId,
                fallbackDriverKey,
                timeline,
                significantDrivingMinutes,
                minimumRestPeriodMinutes
        );
    }

    private TachographEsperDrivingDerivedProjectionBundle buildEsperDrivingDerivedProjectionBundleFromGroupedEvents(
            UUID fallbackSessionId,
            Map<String, List<EventHubEventDto>> eventsByDriver,
            int significantDrivingMinutes,
            int minimumRestPeriodMinutes
    ) {
        List<TachographEsperDrivingDerivedProjectionBundle> bundles = eventsByDriver.entrySet().stream()
                .map(entry -> buildEsperDrivingDerivedProjectionBundleFromEvents(
                        fallbackSessionId,
                        entry.getKey(),
                        entry.getValue(),
                        significantDrivingMinutes,
                        minimumRestPeriodMinutes
                ))
                .toList();

        return TachographDriverWorkingTimeAdapter.mergeLegacyBundles(bundles);
    }

    private EventHubProperties.DrivingDerivedProjectionInputMode drivingDerivedProjectionInputMode() {
        return properties.getTachographFileSession().getProcessing().getDrivingDerivedProjectionInputMode();
    }

    private RawSourceDriverTimelineEventBuilder rawSourceEventBuilder() {
        if (rawSourceEventBuilder == null) {
            throw new IllegalStateException(
                    "Driving-derived projection input mode EVENTS requires RawSourceDriverTimelineEventBuilder"
            );
        }
        return rawSourceEventBuilder;
    }
}
