package org.github.tess1o.geopulse.gpssource.rest;

import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.RequestScoped;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.github.tess1o.geopulse.auth.service.CurrentUserService;
import org.github.tess1o.geopulse.gps.integrations.owntracks.mqtt.MqttConfiguration;
import org.github.tess1o.geopulse.gpssource.model.*;
import org.github.tess1o.geopulse.gpssource.service.GpsSourceService;
import org.github.tess1o.geopulse.gpssource.service.GpsSourceTypeTelemetryConfigService;
import org.github.tess1o.geopulse.shared.api.ApiResponse;
import org.github.tess1o.geopulse.shared.gps.GpsSourceType;

import java.util.List;
import java.util.UUID;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

@Path("/api/gps/source")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed({"USER", "ADMIN"})
@RequestScoped
@Slf4j
@Tag(name = "User: GPS Sources", description = "Manage GPS source configuration, telemetry mappings, and status.")
public class GpsSourceConfigResource {

    private final GpsSourceService gpsSourceService;
    private final GpsSourceTypeTelemetryConfigService telemetryConfigService;
    private final CurrentUserService currentUserService;
    private final MqttConfiguration mqttConfiguration;

    public GpsSourceConfigResource(GpsSourceService gpsSourceService,
                                   GpsSourceTypeTelemetryConfigService telemetryConfigService,
                                   CurrentUserService currentUserService,
                                   MqttConfiguration mqttConfiguration) {
        this.gpsSourceService = gpsSourceService;
        this.telemetryConfigService = telemetryConfigService;
        this.currentUserService = currentUserService;
        this.mqttConfiguration = mqttConfiguration;
    }

    @Path("/")
    @GET
    public Response getGpsSourceConfigs() {
        UUID userId = currentUserService.getCurrentUserId();
        List<GpsSourceConfigDTO> configs = gpsSourceService.findGpsSourceConfigs(userId);
        return Response.ok(configs).build();
    }

    @Path("/defaults")
    @GET
    public Response getDefaultFilteringValues() {
        var defaults = java.util.Map.of(
            "filterInaccurateData", gpsSourceService.isDefaultFilterInaccurateDataEnabled(),
            "maxAllowedAccuracy", gpsSourceService.getDefaultMaxAllowedAccuracy(),
            "maxAllowedSpeed", gpsSourceService.getDefaultMaxAllowedSpeed(),
            "enableDuplicateDetection", gpsSourceService.isDefaultDuplicateDetectionEnabled(),
            "duplicateDetectionThresholdMinutes", gpsSourceService.getDefaultDuplicateDetectionThresholdMinutes()
        );
        return Response.ok(defaults).build();
    }

    @Path("/owntracks/mqtt-config")
    @GET
    public Response getOwnTracksMqttConfig() {
        OwnTracksMqttConfigDTO config = OwnTracksMqttConfigDTO.builder()
                .mqttEnabled(mqttConfiguration.isMqttEnabled())
                .brokerHost(mqttConfiguration.getBrokerHost())
                .brokerPort(mqttConfiguration.getBrokerPort())
                .tlsEnabled(mqttConfiguration.isTlsEnabled())
                .build();
        return Response.ok(config).build();
    }

    @Path("/telemetry/{sourceType}")
    @GET
    public Response getTelemetryMapping(@PathParam("sourceType") String sourceTypeValue) {
        try {
            UUID userId = currentUserService.getCurrentUserId();
            GpsSourceType sourceType = parseSourceType(sourceTypeValue);
            return Response.ok(telemetryConfigService.getResolvedConfig(userId, sourceType)).build();
        } catch (IllegalArgumentException e) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(ApiResponse.error(e.getMessage()))
                    .build();
        }
    }

    @Path("/telemetry/{sourceType}")
    @PUT
    public Response upsertTelemetryMapping(@PathParam("sourceType") String sourceTypeValue,
                                           List<GpsTelemetryMappingEntry> mapping) {
        try {
            UUID userId = currentUserService.getCurrentUserId();
            GpsSourceType sourceType = parseSourceType(sourceTypeValue);
            GpsSourceTypeTelemetryConfigDTO result = telemetryConfigService.upsertConfig(userId, sourceType, mapping);
            return Response.ok(result).build();
        } catch (IllegalArgumentException e) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(ApiResponse.error(e.getMessage()))
                    .build();
        }
    }

    @Path("/telemetry/{sourceType}")
    @DELETE
    public Response resetTelemetryMapping(@PathParam("sourceType") String sourceTypeValue) {
        try {
            UUID userId = currentUserService.getCurrentUserId();
            GpsSourceType sourceType = parseSourceType(sourceTypeValue);
            telemetryConfigService.resetConfig(userId, sourceType);
            return Response.noContent().build();
        } catch (IllegalArgumentException e) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(ApiResponse.error(e.getMessage()))
                    .build();
        }
    }

    @Path("/")
    @POST
    //TODO: duplication check!!!
    public Response addGpsSourceCnfig(CreateGpsSourceConfigDto config) {
        try {
            config.setUserId(currentUserService.getCurrentUserId());
            GpsSourceConfigDTO dto = gpsSourceService.addGpsSourceConfig(config);
            return Response.ok(dto).build();
        } catch (IllegalArgumentException e) {
            log.error("Unable to add config", e);
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(ApiResponse.error(e.getMessage()))
                    .build();
        }
    }

    @Path("/{id}")
    @DELETE
    public Response deleteGpsSourceConfig(@PathParam("id") UUID configId) {
        try {
            UUID userId = currentUserService.getCurrentUserId();
            boolean isDeleted = gpsSourceService.deleteGpsSourceConfig(configId, userId);
            if (!isDeleted) {
                return Response.status(Response.Status.NOT_FOUND).build();
            }
        } catch (Exception e) {
            log.error("Unable to delete config", e);
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(ApiResponse.error("Invalid config id"))
                    .build();
        }
        return Response.ok().build();
    }

    @Path("/{id}/status")
    @PUT
    public Response updateStatus(@PathParam("id") UUID configId, UpdateGpsSourceConfigStatusDto newStatus) {
        try {
            UUID userId = currentUserService.getCurrentUserId();
            boolean isDeleted = gpsSourceService.updateGpsConfigSourceStatus(configId, userId, newStatus.isStatus());
            if (!isDeleted) {
                return Response.status(Response.Status.NOT_FOUND).build();
            }
        } catch (Exception e) {
            log.error("Unable to update status", e);
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(ApiResponse.error("Unable to change the status"))
                    .build();
        }
        return Response.ok().build();
    }

    @Path("/")
    @PUT
    public Response updateGpsConfigSource(UpdateGpsSourceConfigDto config) {
        log.info("Updating config {}", config);
        try {
            UUID userId = currentUserService.getCurrentUserId();
            boolean updated = gpsSourceService.updateGpsConfigSource(config, userId);
            if (!updated) {
                return Response.status(Response.Status.NOT_FOUND).build();
            }
        } catch (Exception e) {
            log.error("Unable to update config", e);
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(ApiResponse.error("Invalid config id"))
                    .build();
        }
        return Response.ok().build();
    }

    private GpsSourceType parseSourceType(String sourceTypeValue) {
        if (sourceTypeValue == null || sourceTypeValue.isBlank()) {
            throw new IllegalArgumentException("Source type is required");
        }

        try {
            return GpsSourceType.valueOf(sourceTypeValue.trim().toUpperCase());
        } catch (IllegalArgumentException ex) {
            throw new IllegalArgumentException("Unsupported source type: " + sourceTypeValue);
        }
    }
}
