package org.github.tess1o.geopulse.streaming.service.trips;

import jakarta.enterprise.context.ApplicationScoped;
import lombok.extern.slf4j.Slf4j;
import org.github.tess1o.geopulse.streaming.config.TimelineConfig;
import org.github.tess1o.geopulse.streaming.model.domain.DataGap;
import org.github.tess1o.geopulse.streaming.model.domain.Stay;
import org.github.tess1o.geopulse.streaming.model.domain.TimelineEvent;
import org.github.tess1o.geopulse.streaming.model.domain.Trip;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@ApplicationScoped
@Slf4j
public class StreamingSingleTripAlgorithm extends AbstractTripAlgorithm {
    public List<TimelineEvent> apply(UUID userId,
                                     List<TimelineEvent> events,
                                     TimelineConfig config,
                                     String environmentDatasetVersion) {
        List<TimelineEvent> processedEvents = new ArrayList<>();

        Stay currentStay = null;
        List<Trip> tripsToMerge = new ArrayList<>();

        for (TimelineEvent event : events) {
            if (event instanceof Stay) {
                Stay stay = (Stay) event;

                // If we have accumulated trips, merge them into one
                if (!tripsToMerge.isEmpty()) {
                    Trip mergedTrip = mergeTripSegments(userId, tripsToMerge, config, environmentDatasetVersion);
                    if (mergedTrip != null) {
                        if (isValidTrip(mergedTrip, config)) {
                            processedEvents.add(mergedTrip);
                        } else if (currentStay != null) {
                            // CRITICAL: Always include trip between stays to maintain timeline continuity
                            // Even if it's very short, we need SOME movement between different locations
                            log.warn("Including short trip between stays for continuity: {}m, {}min (below normal thresholds)",
                                    mergedTrip.getDistanceMeters(), mergedTrip.getDuration().toMinutes());
                            processedEvents.add(mergedTrip);
                        } else {
                            log.debug("Dropping leading trip below normal thresholds before first stay: {}m, {}min",
                                    mergedTrip.getDistanceMeters(), mergedTrip.getDuration().toMinutes());
                        }
                    }
                } else if (currentStay != null && tripsToMerge.isEmpty()) {
                    // No detected trip between consecutive stays:
                    // - same location -> keep as-is for optional downstream merge pass
                    // - different location -> synthesize continuity trip here
                    if (isSameLocation(currentStay, stay)) {
                        log.debug("Consecutive stays at same location '{}' with no trips detected - " +
                                "left as-is in post-processing; downstream merge pass may consolidate when merge is enabled",
                                stay.getLocationName());
                    } else {
                        Trip continuityTrip = createContinuityTripBetweenStays(currentStay, stay, config);
                        if (continuityTrip != null) {
                            log.warn("Including continuity trip between consecutive stays: '{}' -> '{}' ({}m, {}min)",
                                    currentStay.getLocationName(), stay.getLocationName(),
                                    continuityTrip.getDistanceMeters(), continuityTrip.getDuration().toMinutes());
                            processedEvents.add(continuityTrip);
                        } else {
                            log.warn("Could not create continuity trip between consecutive stays: '{}' -> '{}'",
                                    currentStay.getLocationName(), stay.getLocationName());
                        }
                    }
                }

                // Reset for next segment
                tripsToMerge.clear();
                currentStay = stay;
                processedEvents.add(stay);

            } else if (event instanceof Trip) {
                tripsToMerge.add((Trip) event);

            } else {
                // Data gaps and other events finalize the current trip segment
                if (!tripsToMerge.isEmpty()) {
                    Trip mergedTrip = mergeTripSegments(userId, tripsToMerge, config, environmentDatasetVersion);
                    if (mergedTrip != null) {
                        if (isValidTrip(mergedTrip, config)) {
                            processedEvents.add(mergedTrip);
                        } else if (event instanceof DataGap) {
                            // Keep short movement before a data gap to preserve continuity
                            // and allow downstream stay/gap interpretation to retain context.
                            log.warn("Including short trip before data gap for continuity: {}m, {}min",
                                    mergedTrip.getDistanceMeters(), mergedTrip.getDuration().toMinutes());
                            processedEvents.add(mergedTrip);
                        }
                    }
                    tripsToMerge.clear();
                }
                processedEvents.add(event);
            }
        }

        // Handle any remaining trips at the end
        if (!tripsToMerge.isEmpty()) {
            Trip mergedTrip = mergeTripSegments(userId, tripsToMerge, config, environmentDatasetVersion);
            if (mergedTrip != null) {
                if (isValidTrip(mergedTrip, config)) {
                    processedEvents.add(mergedTrip);
                } else if (currentStay != null) {
                    // Include for continuity if we had a preceding stay
                    log.warn("Including final short trip for continuity: {}m, {}min",
                            mergedTrip.getDistanceMeters(), mergedTrip.getDuration().toMinutes());
                    processedEvents.add(mergedTrip);
                }
            }
        }

        log.debug("Single algorithm: processed {} events into {}", events.size(), processedEvents.size());
        return processedEvents;
    }
}
