package com.ai.trackwalker.uavaccess.wayline;

import com.ai.trackwalker.uavaccess.config.UavAccessProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE;

@Component
public class WpmlKmzCompiler {
    private final UavAccessProperties properties;

    public WpmlKmzCompiler(UavAccessProperties properties) {
        this.properties = properties;
    }

    public byte[] compile(WaylineUploadRequest request) {
        List<String> missing = properties.getFlighthub().missingRequiredConfiguration(true);
        if (!missing.isEmpty()) {
            throw new ResponseStatusException(
                    SERVICE_UNAVAILABLE,
                    "WPML compiler requires deployment parameters: " + String.join(", ", missing)
            );
        }
        if (request.waypoints() == null || request.waypoints().size() < 2) {
            throw new ResponseStatusException(BAD_REQUEST, "A wayline requires at least two waypoints");
        }
        long timestamp = request.publishedAtEpochMs() == null ? 0L : request.publishedAtEpochMs();
        String template = templateKml(request, timestamp);
        String execution = waylinesWpml(request, timestamp);
        try {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            try (ZipOutputStream zip = new ZipOutputStream(output, StandardCharsets.UTF_8)) {
                put(zip, "wpmz/template.kml", template);
                put(zip, "wpmz/waylines.wpml", execution);
            }
            return output.toByteArray();
        } catch (IOException exception) {
            throw new IllegalStateException("Unable to build KMZ", exception);
        }
    }

    private String templateKml(WaylineUploadRequest request, long timestamp) {
        UavAccessProperties.Wpml wpml = properties.getFlighthub().getWpml();
        double speed = number(request.flightParameters(), "speed_mps", 5.0);
        double rth = number(
                request.flightParameters(),
                "rth_altitude_m",
                properties.getFlighthub().getMission().getRthAltitudeM()
        );
        StringBuilder placemarks = new StringBuilder();
        for (int index = 0; index < request.waypoints().size(); index++) {
            Map<String, Object> point = request.waypoints().get(index);
            placemarks.append("""
                        <Placemark>
                          <Point><coordinates>%s,%s</coordinates></Point>
                          <wpml:index>%d</wpml:index>
                          <wpml:ellipsoidHeight>%s</wpml:ellipsoidHeight>
                          <wpml:height>%s</wpml:height>
                          <wpml:useGlobalHeight>0</wpml:useGlobalHeight>
                          <wpml:useGlobalSpeed>1</wpml:useGlobalSpeed>
                          <wpml:useGlobalHeadingParam>1</wpml:useGlobalHeadingParam>
                          <wpml:useGlobalTurnParam>1</wpml:useGlobalTurnParam>
                        </Placemark>
                    """.formatted(
                    decimal(point, "longitude", "lng"),
                    decimal(point, "latitude", "lat"),
                    index,
                    decimal(point, "altitude_m", "altitude"),
                    decimal(point, "altitude_m", "altitude")
            ));
        }
        return """
                <?xml version="1.0" encoding="UTF-8"?>
                <kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/%s">
                  <Document>
                    <wpml:author>%s</wpml:author>
                    <wpml:createTime>%d</wpml:createTime>
                    <wpml:updateTime>%d</wpml:updateTime>
                    %s
                    <Folder>
                      <wpml:templateType>waypoint</wpml:templateType>
                      <wpml:templateId>0</wpml:templateId>
                      <wpml:waylineCoordinateSysParam>
                        <wpml:coordinateMode>WGS84</wpml:coordinateMode>
                        <wpml:heightMode>EGM96</wpml:heightMode>
                        <wpml:positioningType>GPS</wpml:positioningType>
                      </wpml:waylineCoordinateSysParam>
                      <wpml:autoFlightSpeed>%s</wpml:autoFlightSpeed>
                      <wpml:globalHeight>50</wpml:globalHeight>
                      <wpml:caliFlightEnable>0</wpml:caliFlightEnable>
                      <wpml:gimbalPitchMode>manual</wpml:gimbalPitchMode>
                %s
                    </Folder>
                  </Document>
                </kml>
                """.formatted(
                xml(wpml.getVersion()),
                xml(wpml.getAuthor()),
                timestamp,
                timestamp,
                missionConfig(speed, rth),
                speed,
                placemarks
        );
    }

    private String waylinesWpml(WaylineUploadRequest request, long timestamp) {
        UavAccessProperties.Wpml wpml = properties.getFlighthub().getWpml();
        double speed = number(request.flightParameters(), "speed_mps", 5.0);
        double rth = number(
                request.flightParameters(),
                "rth_altitude_m",
                properties.getFlighthub().getMission().getRthAltitudeM()
        );
        StringBuilder placemarks = new StringBuilder();
        for (int index = 0; index < request.waypoints().size(); index++) {
            Map<String, Object> point = request.waypoints().get(index);
            double pointSpeed = number(point, "speed_mps", speed);
            placemarks.append("""
                        <Placemark>
                          <Point><coordinates>%s,%s</coordinates></Point>
                          <wpml:index>%d</wpml:index>
                          <wpml:executeHeight>%s</wpml:executeHeight>
                          <wpml:waypointSpeed>%s</wpml:waypointSpeed>
                          <wpml:waypointHeadingParam>
                            <wpml:waypointHeadingMode>followWayline</wpml:waypointHeadingMode>
                          </wpml:waypointHeadingParam>
                          <wpml:waypointTurnParam>
                            <wpml:waypointTurnMode>toPointAndStopWithDiscontinuityCurvature</wpml:waypointTurnMode>
                            <wpml:waypointTurnDampingDist>0</wpml:waypointTurnDampingDist>
                          </wpml:waypointTurnParam>
                          <wpml:useStraightLine>1</wpml:useStraightLine>
                        </Placemark>
                    """.formatted(
                    decimal(point, "longitude", "lng"),
                    decimal(point, "latitude", "lat"),
                    index,
                    decimal(point, "altitude_m", "altitude"),
                    pointSpeed
            ));
        }
        return """
                <?xml version="1.0" encoding="UTF-8"?>
                <kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/%s">
                  <Document>
                    <wpml:author>%s</wpml:author>
                    <wpml:createTime>%d</wpml:createTime>
                    <wpml:updateTime>%d</wpml:updateTime>
                    %s
                    <Folder>
                      <wpml:templateId>0</wpml:templateId>
                      <wpml:executeHeightMode>WGS84</wpml:executeHeightMode>
                      <wpml:waylineId>0</wpml:waylineId>
                      <wpml:distance>0</wpml:distance>
                      <wpml:duration>0</wpml:duration>
                      <wpml:autoFlightSpeed>%s</wpml:autoFlightSpeed>
                %s
                    </Folder>
                  </Document>
                </kml>
                """.formatted(
                xml(wpml.getVersion()),
                xml(wpml.getAuthor()),
                timestamp,
                timestamp,
                missionConfig(speed, rth),
                speed,
                placemarks
        );
    }

    private String missionConfig(double speed, double rth) {
        UavAccessProperties.Wpml wpml = properties.getFlighthub().getWpml();
        return """
                <wpml:missionConfig>
                  <wpml:flyToWaylineMode>safely</wpml:flyToWaylineMode>
                  <wpml:finishAction>goHome</wpml:finishAction>
                  <wpml:exitOnRCLost>executeLostAction</wpml:exitOnRCLost>
                  <wpml:executeRCLostAction>goBack</wpml:executeRCLostAction>
                  <wpml:takeOffSecurityHeight>%s</wpml:takeOffSecurityHeight>
                  <wpml:globalTransitionalSpeed>%s</wpml:globalTransitionalSpeed>
                  <wpml:droneInfo>
                    <wpml:droneEnumValue>%s</wpml:droneEnumValue>
                    <wpml:droneSubEnumValue>%s</wpml:droneSubEnumValue>
                  </wpml:droneInfo>
                  <wpml:payloadInfo>
                    <wpml:payloadEnumValue>%s</wpml:payloadEnumValue>
                    <wpml:payloadSubEnumValue>%s</wpml:payloadSubEnumValue>
                    <wpml:payloadPositionIndex>0</wpml:payloadPositionIndex>
                  </wpml:payloadInfo>
                </wpml:missionConfig>
                """.formatted(
                rth,
                speed,
                xml(wpml.getDroneEnumValue()),
                xml(wpml.getDroneSubEnumValue()),
                xml(wpml.getPayloadEnumValue()),
                xml(wpml.getPayloadSubEnumValue())
        );
    }

    private void put(ZipOutputStream zip, String name, String content) throws IOException {
        ZipEntry entry = new ZipEntry(name);
        entry.setTime(0L);
        zip.putNextEntry(entry);
        zip.write(content.getBytes(StandardCharsets.UTF_8));
        zip.closeEntry();
    }

    private String decimal(Map<String, Object> point, String primary, String fallback) {
        Object value = point.get(primary);
        if (value == null) value = point.get(fallback);
        if (!(value instanceof Number) && value != null) {
            try {
                value = Double.parseDouble(String.valueOf(value));
            } catch (NumberFormatException ignored) {
                value = null;
            }
        }
        if (!(value instanceof Number number)) {
            throw new ResponseStatusException(BAD_REQUEST, "Waypoint is missing numeric " + primary);
        }
        return Double.toString(number.doubleValue());
    }

    private double number(Map<String, Object> values, String key, double fallback) {
        if (values == null) return fallback;
        Object value = values.get(key);
        if (value instanceof Number number) return number.doubleValue();
        if (value != null) {
            try {
                return Double.parseDouble(String.valueOf(value));
            } catch (NumberFormatException ignored) {
                return fallback;
            }
        }
        return fallback;
    }

    private String xml(String value) {
        if (value == null) return "";
        return value.replace("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&apos;");
    }
}
