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.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

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

import static org.springframework.http.HttpStatus.CONFLICT;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE;

@Service
public class MissionService {
    private final UavAccessProperties properties;
    private final FlightHubOpenApiClient client;
    private final JdbcTemplate jdbc;
    private final OutboxEventService outbox;

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

    @Transactional
    public Map<String, Object> dispatch(MissionDispatchRequest request) {
        if (!properties.getFlighthub().isFlightControlEnabled()) {
            throw new ResponseStatusException(
                    CONFLICT,
                    "Flight mission dispatch is disabled; enable FLIGHTHUB_FLIGHT_CONTROL_ENABLED only after safety acceptance"
            );
        }
        List<Map<String, Object>> previousCommands = jdbc.queryForList(
                "select response_payload::text as response_payload from access_commands where idempotency_key=?",
                request.idempotencyKey()
        );
        if (!previousCommands.isEmpty()) {
            return Jsons.map(String.valueOf(previousCommands.get(0).get("response_payload")));
        }
        Map<String, Object> wayline = requireImportedWayline(request.connectionId(), request.routeVersionId());
        String waylineId = text(wayline.get("vendor_wayline_id"));
        Map<String, Object> check = client.get(
                "/openapi/v2.0/task/api/v1/workspaces/" + project() + "/flight-tasks/check",
                Map.of("sn", request.deviceSn(), "wayline_uuid", waylineId)
        );
        List<Map<String, Object>> errors = mapList(check.get("errors"));
        List<Map<String, Object>> blocking = errors.stream()
                .filter(item -> integer(item.get("level"), 0) >= 2)
                .toList();
        if (!blocking.isEmpty()) {
            throw new ResponseStatusException(CONFLICT, "FlightHub preflight check failed: " + Jsons.write(blocking));
        }

        String accessCommandId = "access-command-" + UUID.randomUUID();
        String requestId = UUID.randomUUID().toString();
        Instant now = Instant.now();
        Map<String, Object> vendorRequest = vendorMissionRequest(request, waylineId);
        jdbc.update("""
                        insert into access_commands(
                            id,connection_id,platform_mission_id,idempotency_key,command_type,endpoint,
                            request_id,request_payload,state,requested_at,updated_at
                        ) values (?,?,?,?,?,?,?,?::jsonb,'SENDING',?,?)
                        """,
                accessCommandId, request.connectionId(), request.platformMissionId(), request.idempotencyKey(),
                "MISSION_DISPATCH",
                "/openapi/v2.0/task/api/v1/workspaces/{workspace_id}/flight-tasks/advanced",
                requestId, Jsons.write(vendorRequest), Timestamp.from(now), Timestamp.from(now)
        );

        Map<String, Object> vendorResponse = client.post(
                "/openapi/v2.0/task/api/v1/workspaces/" + project() + "/flight-tasks/advanced",
                vendorRequest
        );
        String vendorTaskId = findFirstString(
                vendorResponse,
                Set.of("task_uuid", "uuid", "flight_task_uuid", "id")
        );
        if (vendorTaskId.isBlank()) {
            jdbc.update("""
                    update access_commands set state='UNKNOWN', response_payload=?::jsonb,
                        vendor_message='Response did not contain task UUID', updated_at=? where id=?
                    """, Jsons.write(vendorResponse), Timestamp.from(Instant.now()), accessCommandId);
            throw new ResponseStatusException(
                    SERVICE_UNAVAILABLE,
                    "FlightHub accepted the task but did not return a recognizable task UUID"
            );
        }

        Map<String, Object> receipt = new LinkedHashMap<>();
        receipt.put("accepted", true);
        receipt.put("async", true);
        receipt.put("vendor_mission_id", vendorTaskId);
        receipt.put("vendor_transaction_id", requestId);
        receipt.put("status", "DISPATCHED");
        receipt.put("message", "FlightHub accepted the mission; final state is asynchronous");
        receipt.put("preflight", check);
        receipt.put("vendor_response", vendorResponse);

        jdbc.update("""
                        update access_commands set state='ACCEPTED', vendor_task_id=?,
                            response_payload=?::jsonb, accepted_at=?, updated_at=? where id=?
                        """,
                vendorTaskId, Jsons.write(receipt), Timestamp.from(Instant.now()),
                Timestamp.from(Instant.now()), accessCommandId
        );
        jdbc.update("""
                        insert into mission_vendor_bindings(
                            id,connection_id,project_uuid,platform_mission_id,platform_task_id,vendor_device_id,
                            vendor_task_uuid,vendor_status,normalized_status,request_snapshot,response_snapshot,
                            created_at,updated_at
                        ) values (?,?,?,?,?,?,?,'accepted','DISPATCHED',?::jsonb,?::jsonb,?,?)
                        """,
                "mission-binding-" + UUID.randomUUID(),
                request.connectionId(), project(), request.platformMissionId(), request.platformTaskId(),
                request.deviceSn(), vendorTaskId, Jsons.write(vendorRequest), Jsons.write(vendorResponse),
                Timestamp.from(now), Timestamp.from(now)
        );
        outbox.enqueue(
                "uav.access.mission.status.changed.v1",
                request.platformMissionId(),
                "mission-dispatched:" + request.platformMissionId(),
                Map.of(
                        "connection_id", request.connectionId(),
                        "mission_id", request.platformMissionId(),
                        "task_id", request.platformTaskId(),
                        "vendor_task_uuid", vendorTaskId,
                        "status", "DISPATCHED",
                        "progress", 0
                )
        );
        return receipt;
    }

    @Transactional
    public Map<String, Object> command(String platformMissionId, MissionCommandRequest request) {
        List<Map<String, Object>> previous = jdbc.queryForList(
                "select response_payload::text as response_payload from access_commands where idempotency_key=?",
                request.idempotencyKey()
        );
        if (!previous.isEmpty()) {
            return Jsons.map(String.valueOf(previous.get(0).get("response_payload")));
        }
        Map<String, Object> binding = requireMission(platformMissionId);
        String command = request.command().toUpperCase(Locale.ROOT);
        if ("MISSION_START".equals(command)) {
            Map<String, Object> result = Map.of(
                    "accepted", true,
                    "async", true,
                    "vendor_transaction_id", "mission-create:" + binding.get("vendor_task_uuid"),
                    "command", command,
                    "message", "Immediate FlightHub task starts through mission creation; awaiting execution event"
            );
            insertCompletedCommand(binding, request, result, "ACCEPTED");
            return result;
        }
        if (!properties.getFlighthub().isFlightControlEnabled()) {
            throw new ResponseStatusException(
                    CONFLICT,
                    "Flight control is disabled; set FLIGHTHUB_FLIGHT_CONTROL_ENABLED=true only after safety acceptance"
            );
        }
        String action = switch (command) {
            case "MISSION_PAUSE" -> "pause";
            case "MISSION_RESUME" -> "resume";
            case "MISSION_CANCEL", "RETURN_HOME" -> "cancel";
            default -> throw new ResponseStatusException(CONFLICT, "Unsupported FlightHub mission command: " + command);
        };
        String requestId = UUID.randomUUID().toString();
        Map<String, Object> body = Map.of(
                "drone_sn", binding.get("vendor_device_id"),
                "action", action
        );
        Map<String, Object> vendorResponse = client.put(
                "/openapi/v2.0/drc/api/v2/projects/" + project() + "/flight-wayline-tasks/commands",
                body
        );
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("accepted", true);
        result.put("async", true);
        result.put("vendor_transaction_id", requestId);
        result.put("command", command);
        result.put("vendor_action", action);
        result.put("message", "FlightHub accepted the command; awaiting state event");
        result.put("vendor_response", vendorResponse);
        insertCompletedCommand(binding, request, result, "ACCEPTED");
        return result;
    }

    public Map<String, Object> binding(String platformMissionId) {
        return new LinkedHashMap<>(requireMission(platformMissionId));
    }

    private Map<String, Object> vendorMissionRequest(MissionDispatchRequest request, String waylineId) {
        UavAccessProperties.Mission defaults = properties.getFlighthub().getMission();
        Map<String, Object> options = request.options() == null ? Map.of() : request.options();
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("task_type", 1);
        body.put("time_zone", defaults.getTimeZone());
        body.put("sn", request.dockSn() == null || request.dockSn().isBlank() ? request.deviceSn() : request.dockSn());
        body.put("connect_sn", request.deviceSn());
        if (request.dockSn() != null && !request.dockSn().isBlank()) body.put("landing_dock_sn", request.dockSn());
        body.put("name", request.name() == null || request.name().isBlank()
                ? "铁路巡检-" + request.platformTaskId()
                : request.name());
        body.put("default_name", false);
        body.put("min_battery_capacity", integer(options.get("minimum_battery_percent"), defaults.getMinimumBatteryPercent()));
        body.put("resumable_status", integer(options.get("resumable_status"), defaults.getResumableStatus()));
        body.put("out_of_control_action_in_flight", integer(
                options.get("out_of_control_action_in_flight"),
                defaults.getOutOfControlAction()
        ));
        body.put("rth_altitude", integer(options.get("rth_altitude_m"), defaults.getRthAltitudeM()));
        body.put("wayline_uuid", waylineId);
        body.put("cloud_to_cloud_id", request.platformMissionId());
        if (!defaults.getDeviceType().isBlank()) body.put("device_type", defaults.getDeviceType());
        return body;
    }

    private void insertCompletedCommand(
            Map<String, Object> binding,
            MissionCommandRequest request,
            Map<String, Object> result,
            String state
    ) {
        Instant now = Instant.now();
        jdbc.update("""
                        insert into access_commands(
                            id,connection_id,platform_command_id,platform_mission_id,idempotency_key,
                            command_type,endpoint,request_id,vendor_task_id,request_payload,response_payload,
                            state,requested_at,accepted_at,updated_at
                        ) values (?,?,?,?,?,?,?,?,?,?::jsonb,?::jsonb,?,?,?,?)
                        """,
                "access-command-" + UUID.randomUUID(),
                binding.get("connection_id"),
                request.platformCommandId(),
                binding.get("platform_mission_id"),
                request.idempotencyKey(),
                request.command().toUpperCase(Locale.ROOT),
                "/openapi/v2.0/drc/api/v2/projects/{uuid}/flight-wayline-tasks/commands",
                result.get("vendor_transaction_id"),
                binding.get("vendor_task_uuid"),
                Jsons.write(request.parameters() == null ? Map.of() : request.parameters()),
                Jsons.write(result),
                state,
                Timestamp.from(now),
                Timestamp.from(now),
                Timestamp.from(now)
        );
    }

    private Map<String, Object> requireImportedWayline(String connectionId, String routeVersionId) {
        List<Map<String, Object>> rows = jdbc.queryForList("""
                select * from route_vendor_artifacts
                where connection_id=? and project_uuid=? and platform_route_version_id=? and import_state='IMPORTED'
                order by created_at desc limit 1
                """, connectionId, project(), routeVersionId);
        if (rows.isEmpty()) {
            throw new ResponseStatusException(CONFLICT, "Route version has no imported FlightHub wayline artifact");
        }
        return rows.get(0);
    }

    private Map<String, Object> requireMission(String platformMissionId) {
        List<Map<String, Object>> rows = jdbc.queryForList(
                "select * from mission_vendor_bindings where platform_mission_id=?",
                platformMissionId
        );
        if (rows.isEmpty()) throw new ResponseStatusException(NOT_FOUND, "Mission binding not found");
        return rows.get(0);
    }

    @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 String findFirstString(Object value, Set<String> keys) {
        if (value instanceof Map<?, ?> map) {
            for (String key : keys) {
                Object candidate = map.get(key);
                if (candidate != null && !String.valueOf(candidate).isBlank()) return String.valueOf(candidate);
            }
            for (Object nested : map.values()) {
                String candidate = findFirstString(nested, keys);
                if (!candidate.isBlank()) return candidate;
            }
        } else if (value instanceof List<?> list) {
            for (Object nested : list) {
                String candidate = findFirstString(nested, keys);
                if (!candidate.isBlank()) return candidate;
            }
        }
        return "";
    }

    private String project() {
        return properties.getFlighthub().getProjectUuid();
    }

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

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