package at.procon.eventhub.yellowfox.service;

import at.procon.eventhub.dto.CardSlot;
import at.procon.eventhub.dto.CardStatus;
import at.procon.eventhub.dto.DriverRefDto;
import at.procon.eventhub.dto.EventDetailsDto;
import at.procon.eventhub.dto.EventDomain;
import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.dto.EventHubPackageRequest;
import at.procon.eventhub.dto.EventLifecycle;
import at.procon.eventhub.dto.EventSourceDto;
import at.procon.eventhub.dto.EventType;
import at.procon.eventhub.dto.GeoPointDto;
import at.procon.eventhub.dto.ImportScopeDto;
import at.procon.eventhub.dto.SourceGroupRefDto;
import at.procon.eventhub.dto.SourceGroupType;
import at.procon.eventhub.dto.VehicleRefDto;
import at.procon.eventhub.service.EventDetailsFactory;
import at.procon.eventhub.yellowfox.dto.YellowFoxD8BookingDto;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import org.springframework.stereotype.Component;

@Component
public class YellowFoxD8BookingEventMapper {

    public static final String DETAIL_SOURCE = "YELLOWFOX_D8";

    private final EventDetailsFactory detailsFactory;

    public YellowFoxD8BookingEventMapper(EventDetailsFactory detailsFactory) {
        this.detailsFactory = detailsFactory;
    }

    public EventHubEventDto map(YellowFoxD8BookingDto source) {
        NormalizedEvent normalized = normalize(source.eventType(), source.state());
        return event(source, sourceEventId(source, "D8_BOOKING"), normalized, detailsFor(source, normalized), false);
    }

    public EventHubEventDto mapIgnitionTransition(YellowFoxD8BookingDto source, Integer previousIgnitionState) {
        if (source.ignition() == null) {
            return null;
        }
        boolean ignitionOn = source.ignition() == 1;
        NormalizedEvent normalized = new NormalizedEvent(
                EventDomain.IGNITION,
                ignitionOn ? EventType.IGNITION_ON : EventType.IGNITION_OFF,
                ignitionOn ? EventLifecycle.ON : EventLifecycle.OFF,
                null,
                null
        );
        Map<String, Object> attributes = baseAttributes(source, normalized);
        attributes.put("previousIgnitionState", previousIgnitionState);
        attributes.put("previousIgnitionMeaning", ignitionMeaning(previousIgnitionState));
        attributes.put("derivedFrom", "YELLOWFOX_D8_BOOKING");
        attributes.put("sourceEventId", sourceEventId(source, "D8_BOOKING"));
        EventDetailsDto details = new EventDetailsDto("IGNITION", detailsFactory.payloadFromMap(attributes));
        return event(source, sourceEventId(source, "D8_IGNITION") + ":" + (ignitionOn ? "ON" : "OFF"), normalized, details, true);
    }

    private EventHubEventDto event(
            YellowFoxD8BookingDto source,
            String externalSourceEventId,
            NormalizedEvent normalized,
            EventDetailsDto details,
            boolean derived
    ) {
        EventSourceDto eventSource = eventSource(source);
        LocalDate businessDate = source.occurredAt().toLocalDate();
        var occurredFrom = businessDate.atStartOfDay(source.occurredAt().getOffset()).toOffsetDateTime();
        var occurredTo = businessDate.plusDays(1).atStartOfDay(source.occurredAt().getOffset()).toOffsetDateTime();
        SourceGroupRefDto sourceGroup = sourceGroup(source);
        EventHubPackageRequest packageInfo = new EventHubPackageRequest(
                tenantOrDefault(source.tenantKey()),
                eventSource,
                sourceGroup,
                ImportScopeDto.tenantAll(occurredFrom, occurredTo),
                normalized.domain().name(),
                businessDate,
                eventSource.stableKey() + ":" + normalized.domain().name() + ":" + businessDate
        );

        return new EventHubEventDto(
                UUID.randomUUID(),
                externalSourceEventId,
                derived && normalized.domain() == EventDomain.IGNITION ? null : source.driverRef(),
                source.vehicleRef(),
                source.occurredAt(),
                source.receivedPartnerAt(),
                OffsetDateTime.now(),
                normalized.domain(),
                normalized.type(),
                normalized.lifecycle(),
                source.odometerM(),
                point(source),
                details,
                null,
                detailsFactory.payloadFromMap(payload(source, derived)),
                false,
                packageInfo
        );
    }

    public EventSourceDto eventSource(YellowFoxD8BookingDto source) {
        return new EventSourceDto(
                "YELLOWFOX",
                "TELEMATICS_PLATFORM",
                "YELLOWFOX_D8",
                source.sourceInstanceKey(),
                source.tenantProviderSettingKey(),
                source.externalFleetKey()
        );
    }

    private SourceGroupRefDto sourceGroup(YellowFoxD8BookingDto source) {
        if (source.externalFleetKey() == null || source.externalFleetKey().isBlank()) {
            return null;
        }
        return new SourceGroupRefDto(
                SourceGroupType.FLEET,
                source.externalFleetKey(),
                source.externalFleetKey(),
                source.externalFleetName() == null ? source.externalFleetKey() : source.externalFleetName()
        );
    }

    private GeoPointDto point(YellowFoxD8BookingDto source) {
        if (source.latitude() == null && source.longitude() == null) {
            return null;
        }
        return new GeoPointDto(source.latitude(), source.longitude());
    }

    private String tenantOrDefault(String value) {
        return value == null || value.isBlank() ? "default" : value.trim();
    }

    private String sourceEventId(YellowFoxD8BookingDto source, String prefix) {
        String eventId = source.eventId() == null || source.eventId().isBlank() ? "UNKNOWN_EVENT" : source.eventId().trim();
        return prefix + ":" + eventId + ":" + source.occurredAt();
    }

    private Map<String, Object> payload(YellowFoxD8BookingDto source, boolean derived) {
        Map<String, Object> payload = new LinkedHashMap<>();
        if (source.payload() != null) {
            payload.putAll(source.payload());
        }
        payload.put("provider", "YELLOWFOX");
        payload.put("sourceKind", "TELEMATICS_PLATFORM");
        payload.put("sourceKey", "YELLOWFOX_D8");
        payload.put("derived", derived);
        payload.put("yellowFoxEventType", source.eventType());
        payload.put("yellowFoxState", source.state());
        payload.put("yellowFoxIgnition", source.ignition());
        payload.put("yellowFoxKey", source.key());
        return payload;
    }

    private EventDetailsDto detailsFor(YellowFoxD8BookingDto source, NormalizedEvent normalized) {
        String type = switch (normalized.domain()) {
            case DRIVER_ACTIVITY -> "DRIVER_ACTIVITY";
            case DRIVER_CARD -> "DRIVER_CARD";
            default -> "YELLOWFOX_D8_BOOKING";
        };
        return new EventDetailsDto(type, detailsFactory.payloadFromMap(baseAttributes(source, normalized)));
    }

    private Map<String, Object> baseAttributes(YellowFoxD8BookingDto source, NormalizedEvent normalized) {
        Map<String, Object> attributes = new LinkedHashMap<>();
        attributes.put("source", DETAIL_SOURCE);
        attributes.put("yellowFoxEventType", source.eventType());
        attributes.put("yellowFoxState", source.state());
        attributes.put("yellowFoxStateMeaning", stateMeaning(source.eventType(), source.state()));
        attributes.put("yellowFoxKey", source.key());
        attributes.put("ignitionState", source.ignition());
        attributes.put("ignitionMeaning", ignitionMeaning(source.ignition()));
        attributes.put("odometer", source.odometerM());
        attributes.put("slot", normalized.slot() == null ? null : normalized.slot().name());
        attributes.put("slotNumber", normalized.slotNumber());
        attributes.put("cardSlot", normalized.slot() == null ? null : normalized.slot().name());
        attributes.put("eventDomain", normalized.domain().name());
        attributes.put("eventType", normalized.type().name());
        attributes.put("lifecycle", normalized.lifecycle().name());
        removeNullValues(attributes);
        return attributes;
    }

    private void removeNullValues(Map<String, Object> attributes) {
        attributes.entrySet().removeIf(entry -> entry.getValue() == null);
    }

    private NormalizedEvent normalize(Integer eventType, Integer state) {
        if (eventType == null || state == null) {
            return new NormalizedEvent(EventDomain.TELEMATICS_DATA, EventType.UNKNOWN_EVENT, EventLifecycle.SNAPSHOT, null, null);
        }
        return switch (eventType) {
            case 0 -> normalizeCardActivity(CardSlot.DRIVER, 1, state);
            case 1 -> normalizeCardActivity(CardSlot.CO_DRIVER, 2, state);
            case 2 -> normalizeDriverActivity(CardSlot.DRIVER, 1, state);
            case 3 -> normalizeDriverActivity(CardSlot.CO_DRIVER, 2, state);
            default -> new NormalizedEvent(EventDomain.TELEMATICS_DATA, EventType.UNKNOWN_EVENT, EventLifecycle.SNAPSHOT, null, null);
        };
    }

    private NormalizedEvent normalizeCardActivity(CardSlot slot, int slotNumber, Integer state) {
        return switch (state) {
            case 0 -> new NormalizedEvent(EventDomain.DRIVER_CARD, EventType.CARD_WITHDRAWN, EventLifecycle.WITHDRAW, slot, slotNumber);
            case 1 -> new NormalizedEvent(EventDomain.DRIVER_CARD, EventType.CARD_INSERTED, EventLifecycle.INSERT, slot, slotNumber);
            default -> new NormalizedEvent(EventDomain.DRIVER_CARD, EventType.UNKNOWN_EVENT, EventLifecycle.SNAPSHOT, slot, slotNumber);
        };
    }

    private NormalizedEvent normalizeDriverActivity(CardSlot slot, int slotNumber, Integer state) {
        return switch (state) {
            case 0 -> new NormalizedEvent(EventDomain.DRIVER_ACTIVITY, EventType.BREAK_REST, EventLifecycle.START, slot, slotNumber);
            case 1 -> new NormalizedEvent(EventDomain.DRIVER_ACTIVITY, EventType.AVAILABILITY, EventLifecycle.START, slot, slotNumber);
            case 2 -> new NormalizedEvent(EventDomain.DRIVER_ACTIVITY, EventType.WORK, EventLifecycle.START, slot, slotNumber);
            case 3 -> new NormalizedEvent(EventDomain.DRIVER_ACTIVITY, EventType.DRIVE, EventLifecycle.START, slot, slotNumber);
            default -> new NormalizedEvent(EventDomain.DRIVER_ACTIVITY, EventType.UNKNOWN_ACTIVITY, EventLifecycle.SNAPSHOT, slot, slotNumber);
        };
    }

    private String stateMeaning(Integer eventType, Integer state) {
        if (eventType == null || state == null) {
            return null;
        }
        if (eventType == 0 || eventType == 1) {
            return switch (state) {
                case 0 -> "CARD_REMOVED";
                case 1 -> "CARD_INSERTED";
                default -> "UNKNOWN_CARD_STATE";
            };
        }
        if (eventType == 2 || eventType == 3) {
            return switch (state) {
                case 0 -> "PAUSE_OR_REST";
                case 1 -> "STANDBY_TIME";
                case 2 -> "WORK_TIME";
                case 3 -> "STEERING_TIME";
                default -> "UNKNOWN_ACTIVITY_STATE";
            };
        }
        return "UNKNOWN_EVENT_TYPE";
    }

    private String ignitionMeaning(Integer ignition) {
        if (ignition == null) {
            return null;
        }
        return ignition == 1 ? "ON" : ignition == 0 ? "OFF" : "UNKNOWN";
    }

    private record NormalizedEvent(
            EventDomain domain,
            EventType type,
            EventLifecycle lifecycle,
            CardSlot slot,
            Integer slotNumber
    ) {
    }
}
