package at.procon.eventhub.tachograph.service;

import at.procon.eventhub.config.EventHubProperties;
import at.procon.eventhub.dto.AcquisitionStrategy;
import at.procon.eventhub.dto.EventFamily;
import at.procon.eventhub.importing.ImportChunkPlanner;
import at.procon.eventhub.importing.ImportPlanDto;
import at.procon.eventhub.importing.ImportPlanItemDto;
import at.procon.eventhub.tachograph.dto.TachographImportRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;

@Service
public class TachographImportPlanService {

    private final EventHubProperties properties;
    private final ImportChunkPlanner chunkPlanner;
    private final TachographExtractionDefinitionRegistry definitionRegistry;

    public TachographImportPlanService(
            EventHubProperties properties,
            ImportChunkPlanner chunkPlanner,
            TachographExtractionDefinitionRegistry definitionRegistry
    ) {
        this.properties = properties;
        this.chunkPlanner = chunkPlanner;
        this.definitionRegistry = definitionRegistry;
    }

    public ImportPlanDto createPlan(TachographImportRequest request) {
        List<ImportPlanItemDto> items = new ArrayList<>();
        for (EventFamily family : request.eventFamilies()) {
            items.addAll(itemsFor(family, request.acquisitionStrategy()));
        }
        validateJdbcExtractions(items, request.eventFamilies());
        return new ImportPlanDto(
                request.tenantKey(),
                request.mode(),
                request.acquisitionStrategy(),
                request.refreshMasterDataFirst(),
                request.importScope(),
                request.sourceGroup(),
                request.eventSource(),
                chunkPlanner.chunksFor(request, properties.getTachograph().getDefaultChunkDays()),
                items
        );
    }

    private List<ImportPlanItemDto> itemsFor(EventFamily family, AcquisitionStrategy strategy) {
        return switch (family) {
            case DRIVER_ACTIVITY -> List.of(
                    item(family, "VEHICLE_UNIT", "VU_ACTIVITY", List.of("VUActivity"), "VEHICLE", "Vehicle-unit driver activity point events", strategy),
                    item(family, "DRIVER_CARD", "CARD_ACTIVITY", List.of("CardActivity"), "DRIVER", "Driver-card activity point events", strategy)
            );
            case DRIVER_CARD -> List.of(
                    item(family, "VEHICLE_UNIT", "IW_CYCLE", List.of("IWCycle"), "BOTH", "Card insert/withdraw events from VU cycles", strategy),
                    item(family, "DRIVER_CARD", "CARD_VEHICLES_USED", List.of("CardVehiclesUsed"), "DRIVER", "Card insert/withdraw/use events from card vehicle usage", strategy)
            );
            case POSITION -> List.of(
                    item(family, "VEHICLE_UNIT", "VU_POSITION", List.of("VUGnssAccumulatedDriving"), "VEHICLE", "Periodic GNSS position points from VU", strategy),
                    item(family, "DRIVER_CARD", "CARD_POSITION", List.of("CardGnssAccumulatedDriving"), "DRIVER", "Periodic GNSS position points from driver card", strategy)
            );
            case BORDER_CROSSING -> List.of(
                    item(family, "VEHICLE_UNIT", "VU_BORDER_CROSSING", List.of("VUBorderCrossing"), "VEHICLE", "Border crossing events from VU", strategy),
                    item(family, "DRIVER_CARD", "CARD_BORDER_CROSSING", List.of("CardBorderCrossing"), "DRIVER", "Border crossing events from driver card", strategy)
            );
            case LOAD_UNLOAD -> List.of(
                    item(family, "VEHICLE_UNIT", "VU_LOAD_UNLOAD", List.of("VULoadUnload"), "VEHICLE", "Load/unload operation events from VU", strategy),
                    item(family, "DRIVER_CARD", "CARD_LOAD_UNLOAD", List.of("CardLoadUnload"), "DRIVER", "Load/unload operation events from driver card", strategy)
            );
            case SPECIFIC_CONDITION -> List.of(
                    item(family, "VEHICLE_UNIT", "VU_SPECIFIC_CONDITION", List.of("VUSpecificCondition"), "VEHICLE", "Out-of-scope and ferry/train events from VU", strategy),
                    item(family, "DRIVER_CARD", "CARD_SPECIFIC_CONDITION", List.of("CardSpecificCondition"), "DRIVER", "Out-of-scope and ferry/train events from driver card", strategy)
            );
            case PLACE -> List.of(
                    item(family, "VEHICLE_UNIT", "VU_PLACE", List.of("VUPlaces"), "VEHICLE", "Start/end place events from VU", strategy),
                    item(family, "DRIVER_CARD", "CARD_PLACE", List.of("CardPlaces"), "DRIVER", "Start/end place events from driver card", strategy)
            );
            case SPEEDING -> List.of(
                    item(family, "VEHICLE_UNIT", "SPEEDING_EVENTS", List.of("SpeedingEvents"), "VEHICLE", "Speeding begin/end events", strategy)
            );
        };
    }

    private ImportPlanItemDto item(
            EventFamily family,
            String sourceKind,
            String extractionCode,
            List<String> sourceTables,
            String entityAxis,
            String description,
            AcquisitionStrategy strategy
    ) {
        return new ImportPlanItemDto(family, sourceKind, extractionCode, sourceTables, entityAxis, description, strategy);
    }

    private void validateJdbcExtractions(List<ImportPlanItemDto> items, Set<EventFamily> requestedFamilies) {
        String jdbcUrl = properties.getTachograph().getDatasource().getJdbcUrl();
        if (jdbcUrl == null || jdbcUrl.isBlank()) {
            return;
        }

        List<String> unsupportedCodes = items.stream()
                .map(ImportPlanItemDto::extractionCode)
                .filter(code -> definitionRegistry.findByCode(code).isEmpty())
                .distinct()
                .sorted()
                .toList();
        if (unsupportedCodes.isEmpty()) {
            return;
        }

        List<String> supportedCodes = definitionRegistry.supportedCodes().stream()
                .sorted()
                .toList();
        List<String> supportedFamilies = definitionRegistry.definitions().stream()
                .map(definition -> definition.eventFamily().name())
                .distinct()
                .sorted()
                .toList();
        String requestedFamilyNames = requestedFamilies.stream()
                .map(EventFamily::name)
                .sorted()
                .collect(Collectors.joining(", "));

        throw new UnsupportedTachographExtractionException(
                "Tachograph JDBC extraction is enabled, but the plan contains extraction codes without JDBC definitions: "
                        + unsupportedCodes
                        + ". Supported JDBC extraction codes are: "
                        + supportedCodes
                        + ". Requested event families: ["
                        + requestedFamilyNames
                        + "]. "
                        + "Use only supported event families "
                        + supportedFamilies
                        + " for JDBC execution, or add the missing codes to TachographExtractionDefinitionRegistry."
        );
    }
}
