package com.ai.trackwalker.uavaccess.event;

import com.ai.trackwalker.uavaccess.common.Jsons;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Timestamp;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

@Service
public class VendorEventIngestService {
    private final JdbcTemplate jdbc;
    private final VendorStatusNormalizer statusNormalizer;
    private final OutboxEventService outbox;

    public VendorEventIngestService(
            JdbcTemplate jdbc,
            VendorStatusNormalizer statusNormalizer,
            OutboxEventService outbox
    ) {
        this.jdbc = jdbc;
        this.statusNormalizer = statusNormalizer;
        this.outbox = outbox;
    }

    @Transactional
    public Map<String, Object> ingest(
            String connectionId,
            String source,
            String vendorEventId,
            String eventType,
            String eventTime,
            Map<String, Object> payload
    ) {
        String stableEventId = vendorEventId == null || vendorEventId.isBlank()
                ? Jsons.sha256(source + ":" + eventType + ":" + Jsons.write(payload))
                : vendorEventId;
        String hash = Jsons.sha256(Jsons.write(payload));
        String inboxId = "inbox-" + UUID.randomUUID();
        Instant receivedAt = Instant.now();
        try {
            jdbc.update("""
                            insert into access_event_inbox(
                                id,connection_id,source,vendor_event_id,event_type,payload_hash,
                                raw_payload,event_time,received_at
                            ) values (?,?,?,?,?,?,?::jsonb,?,?)
                            """,
                    inboxId, connectionId, source, stableEventId, eventType, hash, Jsons.write(payload),
                    parseTime(eventTime), Timestamp.from(receivedAt)
            );
        } catch (DuplicateKeyException duplicate) {
            return Map.of("accepted", true, "duplicate", true, "vendor_event_id", stableEventId);
        }

        try {
            int emitted = translate(connectionId, stableEventId, eventType, payload);
            jdbc.update(
                    "update access_event_inbox set processed_at=? where id=?",
                    Timestamp.from(Instant.now()),
                    inboxId
            );
            return Map.of(
                    "accepted", true,
                    "duplicate", false,
                    "vendor_event_id", stableEventId,
                    "emitted_events", emitted
            );
        } catch (RuntimeException exception) {
            jdbc.update(
                    "update access_event_inbox set processing_error=? where id=?",
                    bounded(exception.getMessage()),
                    inboxId
            );
            throw exception;
        }
    }

    private int translate(
            String connectionId,
            String vendorEventId,
            String eventType,
            Map<String, Object> payload
    ) {
        int emitted = 0;
        String vendorTaskId = findFirstString(payload, Set.of(
                "task_uuid", "flight_task_uuid", "vendor_task_uuid", "flight_task_id"
        ));
        if (!vendorTaskId.isBlank()) {
            List<Map<String, Object>> bindings = jdbc.queryForList(
                    "select * from mission_vendor_bindings where connection_id=? and vendor_task_uuid=?",
                    connectionId,
                    vendorTaskId
            );
            if (!bindings.isEmpty()) {
                Map<String, Object> binding = bindings.get(0);
                Object rawStatus = findFirst(payload, Set.of("task_status", "status", "state"));
                String normalized = statusNormalizer.normalize(rawStatus);
                int progress = number(findFirst(payload, Set.of("progress", "percent", "percentage")), 0);
                jdbc.update("""
                                update mission_vendor_bindings set vendor_status=?, normalized_status=?,
                                    response_snapshot=?::jsonb, updated_at=? where id=?
                                """,
                        text(rawStatus), normalized, Jsons.write(payload),
                        Timestamp.from(Instant.now()), binding.get("id")
                );
                Map<String, Object> standard = new LinkedHashMap<>();
                standard.put("connection_id", connectionId);
                standard.put("mission_id", binding.get("platform_mission_id"));
                standard.put("task_id", binding.get("platform_task_id"));
                standard.put("vendor_task_uuid", vendorTaskId);
                standard.put("vendor_status", text(rawStatus));
                standard.put("status", normalized);
                standard.put("progress", progress);
                standard.put("raw_event_type", eventType);
                outbox.enqueue(
                        "uav.access.mission.status.changed.v1",
                        String.valueOf(binding.get("platform_mission_id")),
                        "vendor-event:" + vendorEventId + ":mission",
                        standard
                );
                emitted++;
            }
        }

        Object longitude = findFirst(payload, Set.of("longitude", "lng"));
        Object latitude = findFirst(payload, Set.of("latitude", "lat"));
        String deviceSn = findFirstString(payload, Set.of("device_sn", "sn", "drone_sn"));
        if (longitude != null && latitude != null && !deviceSn.isBlank()) {
            Map<String, Object> telemetry = new LinkedHashMap<>();
            telemetry.put("connection_id", connectionId);
            telemetry.put("device_sn", deviceSn);
            telemetry.put("longitude", longitude);
            telemetry.put("latitude", latitude);
            telemetry.put("altitude_m", findFirst(payload, Set.of("height", "altitude", "elevation")));
            telemetry.put("event_time", text(findFirst(payload, Set.of("timestamp", "event_time", "ts"))));
            telemetry.put("metrics", payload);
            if (!vendorTaskId.isBlank()) telemetry.put("vendor_task_uuid", vendorTaskId);
            outbox.enqueue(
                    "uav.access.telemetry.received.v1",
                    deviceSn,
                    "vendor-event:" + vendorEventId + ":telemetry",
                    telemetry
            );
            emitted++;
        }

        if (emitted == 0) {
            outbox.enqueue(
                    "uav.access.vendor.event.received.v1",
                    connectionId,
                    "vendor-event:" + vendorEventId + ":raw",
                    Map.of(
                            "connection_id", connectionId,
                            "source", eventType,
                            "payload", payload
                    )
            );
            emitted++;
        }
        return emitted;
    }

    private Object findFirst(Object value, Set<String> keys) {
        if (value instanceof Map<?, ?> map) {
            for (String key : keys) {
                if (map.containsKey(key) && map.get(key) != null) return map.get(key);
            }
            for (Object nested : map.values()) {
                Object candidate = findFirst(nested, keys);
                if (candidate != null) return candidate;
            }
        } else if (value instanceof List<?> list) {
            for (Object nested : list) {
                Object candidate = findFirst(nested, keys);
                if (candidate != null) return candidate;
            }
        }
        return null;
    }

    private String findFirstString(Object value, Set<String> keys) {
        Object result = findFirst(value, keys);
        return result == null ? "" : String.valueOf(result);
    }

    private Timestamp parseTime(String value) {
        if (value == null || value.isBlank()) return null;
        try {
            return Timestamp.from(Instant.parse(value));
        } catch (DateTimeParseException ignored) {
            try {
                long epoch = Long.parseLong(value);
                if (epoch < 10_000_000_000L) epoch *= 1000;
                return Timestamp.from(Instant.ofEpochMilli(epoch));
            } catch (NumberFormatException ignoredAgain) {
                return null;
            }
        }
    }

    private int number(Object value, int fallback) {
        if (value instanceof Number number) return Math.max(0, Math.min(100, number.intValue()));
        try {
            return Math.max(0, Math.min(100, Integer.parseInt(text(value))));
        } catch (NumberFormatException ignored) {
            return fallback;
        }
    }

    private String text(Object value) {
        return value == null ? "" : String.valueOf(value);
    }

    private String bounded(String value) {
        if (value == null) return "unknown";
        return value.substring(0, Math.min(2000, value.length()));
    }
}
