package com.lingniu.ingest.gateway;

import com.lingniu.ingest.protocol.jt808.downlink.Jt808Commands;
import com.lingniu.ingest.protocol.jt808.model.Jt808Header;
import com.lingniu.ingest.protocol.jt808.model.Jt808Message;
import com.lingniu.ingest.session.CommandDispatcher;
import com.lingniu.ingest.session.DeviceSession;
import com.lingniu.ingest.session.SessionStore;
import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.UnaryOperator;

import static org.assertj.core.api.Assertions.assertThat;

class TerminalCommandControllerTest {

    @Test
    void queryAttributesDispatchesJt8088107InsteadOfNotImplemented() {
        RecordingDispatcher dispatcher = new RecordingDispatcher();
        TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher);

        CommandResult result = controller.attributes("13800138000");

        assertThat(result.success()).isTrue();
        assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x8107);
        assertThat(dispatcher.lastRequestCommand.body()).isEmpty();
    }

    @Test
    void alarmAckDispatchesJt8088203WithResponseSerialAndAlarmType() {
        RecordingDispatcher dispatcher = new RecordingDispatcher();
        TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher);

        CommandResult result = controller.alarmAck("13800138000", Map.of(
                "responseSerialNo", 0x1234,
                "type", 0x0000_0001));

        assertThat(result.success()).isTrue();
        assertThat(dispatcher.lastNotifyCommand.messageId()).isEqualTo(0x8203);
        assertThat(dispatcher.lastNotifyCommand.body()).containsExactly(
                0x12, 0x34,
                0x00, 0x00, 0x00, 0x01);
    }

    @Test
    void deleteAreaDispatchesJt8088601WithCommaSeparatedAreaIds() {
        RecordingDispatcher dispatcher = new RecordingDispatcher();
        TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher);

        CommandResult result = controller.deleteArea("13800138000", "1,0x01020304");

        assertThat(result.success()).isTrue();
        assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x8601);
        assertThat(dispatcher.lastRequestCommand.body()).containsExactly(
                0x02,
                0x00, 0x00, 0x00, 0x01,
                0x01, 0x02, 0x03, 0x04);
    }

    @Test
    void jt1078RealtimePlayDispatches9101ThroughSharedDispatcher() {
        RecordingDispatcher dispatcher = new RecordingDispatcher();
        TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher);

        CommandResult result = controller.jt1078RealtimePlay("13800138000", Map.of(
                "ip", "10.1.2.3",
                "tcpPort", 11078,
                "udpPort", 11079,
                "channelNo", 2,
                "mediaType", 1,
                "streamType", 0));

        assertThat(result.success()).isTrue();
        assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x9101);
        assertThat(dispatcher.lastRequestCommand.body()).containsExactly(
                0x08, '1', '0', '.', '1', '.', '2', '.', '3',
                0x2B, 0x46,
                0x2B, 0x47,
                0x02,
                0x01,
                0x00);
    }

    @Test
    void jt1078ResourceSearchDispatches9205AndAwaitsFileListResponse() {
        RecordingDispatcher dispatcher = new RecordingDispatcher();
        TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher);

        CommandResult result = controller.jt1078ResourceSearch("13800138000", Map.of(
                "channelNo", 3,
                "startTime", "2026-06-22 08:09:10",
                "endTime", "260622180000",
                "warnBit1", "0x01020304",
                "warnBit2", "0x05060708",
                "mediaType", 3,
                "streamType", 2,
                "storageType", 1));

        assertThat(result.success()).isTrue();
        assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x9205);
        assertThat(dispatcher.lastRequestCommand.body()).containsExactly(
                0x03,
                0x26, 0x06, 0x22, 0x08, 0x09, 0x10,
                0x26, 0x06, 0x22, 0x18, 0x00, 0x00,
                0x01, 0x02, 0x03, 0x04,
                0x05, 0x06, 0x07, 0x08,
                0x03,
                0x02,
                0x01);
    }

    private static final class RecordingDispatcher implements CommandDispatcher {
        private Jt808Commands.DownlinkCommand lastNotifyCommand;
        private Jt808Commands.DownlinkCommand lastRequestCommand;

        @Override
        public CompletableFuture<Void> notify(String sessionId, Object command) {
            this.lastNotifyCommand = (Jt808Commands.DownlinkCommand) command;
            return CompletableFuture.completedFuture(null);
        }

        @Override
        @SuppressWarnings("unchecked")
        public <T> CompletableFuture<T> request(String sessionId, Object command, Class<T> responseType, Duration timeout) {
            this.lastRequestCommand = (Jt808Commands.DownlinkCommand) command;
            Jt808Message response = new Jt808Message(
                    new Jt808Header(0x0107, 0, 0, false, Jt808Header.ProtocolVersion.V2013,
                            "13800138000", 1, 0, 0),
                    new com.lingniu.ingest.protocol.jt808.model.Jt808Body.Raw(0x0107, new byte[0]));
            return CompletableFuture.completedFuture((T) response);
        }
    }

    private static final class EmptySessionStore implements SessionStore {
        @Override public void put(DeviceSession session) {}
        @Override public Optional<DeviceSession> findBySessionId(String sessionId) { return Optional.empty(); }
        @Override public Optional<DeviceSession> findByVin(String vin) { return Optional.empty(); }
        @Override public Optional<DeviceSession> findByPhone(String phone) { return Optional.empty(); }
        @Override public Optional<DeviceSession> update(String sessionId, UnaryOperator<DeviceSession> updater) { return Optional.empty(); }
        @Override public void remove(String sessionId) {}
        @Override public int size() { return 0; }
    }
}
