package com.ai.trackwalker.uavaccess.mission;

import com.ai.trackwalker.uavaccess.common.Jsons;
import com.ai.trackwalker.uavaccess.config.UavAccessProperties;
import com.ai.trackwalker.uavaccess.event.OutboxEventService;
import com.ai.trackwalker.uavaccess.flighthub.FlightHubOpenApiClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Component
public class MissionReconciliationJob {
    private static final Logger LOGGER = LoggerFactory.getLogger(MissionReconciliationJob.class);

    private final UavAccessProperties properties;
    private final FlightHubOpenApiClient client;
    private final JdbcTemplate jdbc;
    private final OutboxEventService outbox;

    public MissionReconciliationJob(
            UavAccessProperties properties,
            FlightHubOpenApiClient client,
            JdbcTemplate jdbc,
            OutboxEventService outbox
    ) {
        this.properties = properties;
        this.client = client;
        this.jdbc = jdbc;
        this.outbox = outbox;
    }

    @Scheduled(fixedDelayString = "${uav-access.flighthub.mission.reconciliation-delay-ms:15000}")
    public void reconcile() {
        if (!properties.getFlighthub().isEnabled()) return;
        List<Map<String, Object>> bindings = jdbc.queryForList("""
                select * from mission_vendor_bindings
                where normalized_status not in ('COMPLETED','CANCELLED','ABORTED','FAILED')
                  and vendor_task_uuid is not null
                order by updated_at
                limit 100
                """);
        if (bindings.isEmpty()) return;
        List<String> taskIds = bindings.stream()
                .map(row -> String.valueOf(row.get("vendor_task_uuid")))
                .toList();
        try {
            Map<String, Object> response = client.get(
                    "/openapi/v2.0/task/api/v1/workspaces/"
                            + properties.getFlighthub().getProjectUuid()
                            + "/flight-tasks/batch",
                    Map.of("task_uuids", taskIds)
            );
            for (Map<String, Object> task : mapList(response.get("list"))) {
                update(task, bindings);
            }
        } catch (RuntimeException exception) {
            LOGGER.warn("Unable to reconcile FlightHub missions", exception);
        }
    }

    private void update(Map<String, Object> task, List<Map<String, Object>> bindings) {
        String vendorTaskId = String.valueOf(task.getOrDefault("uuid", ""));
        Map<String, Object> binding = bindings.stream()
                .filter(row -> vendorTaskId.equals(String.valueOf(row.get("vendor_task_uuid"))))
                .findFirst()
                .orElse(null);
        if (binding == null) return;
        int vendorStatus = integer(task.get("status"), -1);
        String normalized = normalize(vendorStatus);
        int totalWaypoints = integer(task.get("total_waypoints"), 0);
        int currentWaypoint = integer(task.get("current_waypoint_index"), 0);
        int progress = totalWaypoints <= 0
                ? ("COMPLETED".equals(normalized) ? 100 : 0)
                : Math.max(0, Math.min(100, currentWaypoint * 100 / totalWaypoints));
        String oldStatus = String.valueOf(binding.get("normalized_status"));
        jdbc.update("""
                        update mission_vendor_bindings
                        set vendor_status=?,normalized_status=?,response_snapshot=?::jsonb,
                            last_reconciled_at=?,updated_at=?
                        where id=?
                        """,
                String.valueOf(vendorStatus), normalized, Jsons.write(task),
                Timestamp.from(Instant.now()), Timestamp.from(Instant.now()), binding.get("id")
        );
        if (!oldStatus.equals(normalized) || "FLYING".equals(normalized)) {
            Map<String, Object> payload = new LinkedHashMap<>();
            payload.put("connection_id", binding.get("connection_id"));
            payload.put("mission_id", binding.get("platform_mission_id"));
            payload.put("task_id", binding.get("platform_task_id"));
            payload.put("vendor_task_uuid", vendorTaskId);
            payload.put("vendor_status", vendorStatus);
            payload.put("status", normalized);
            payload.put("progress", progress);
            payload.put("current_waypoint", currentWaypoint);
            payload.put("media_upload_status", task.get("media_upload_status"));
            payload.put("exceptions", task.getOrDefault("exceptions", List.of()));
            String vendorUpdatedAt = String.valueOf(task.getOrDefault("updated_at", Instant.now().toString()));
            outbox.enqueue(
                    "uav.access.mission.status.changed.v1",
                    String.valueOf(binding.get("platform_mission_id")),
                    "mission-reconcile:" + vendorTaskId + ":" + vendorUpdatedAt + ":" + vendorStatus + ":" + progress,
                    payload
            );
        }
    }

    private String normalize(int status) {
        return switch (status) {
            case 0 -> "DISPATCHED";
            case 1, 7 -> "FAILED";
            case 2 -> "FLYING";
            case 3, 6 -> "PAUSED";
            case 4 -> "ABORTED";
            case 5, 8 -> "COMPLETED";
            case 9, 10 -> "PREPARING";
            default -> "UNKNOWN";
        };
    }

    @SuppressWarnings("unchecked")
    private List<Map<String, Object>> mapList(Object value) {
        if (!(value instanceof List<?> list)) return List.of();
        List<Map<String, Object>> result = new ArrayList<>();
        for (Object item : list) {
            if (item instanceof Map<?, ?> map) result.add((Map<String, Object>) map);
        }
        return result;
    }

    private int integer(Object value, int fallback) {
        if (value instanceof Number number) return number.intValue();
        try {
            return Integer.parseInt(String.valueOf(value));
        } catch (NumberFormatException ignored) {
            return fallback;
        }
    }
}
