package com.lingniu.ingest.protocol.jsatl12.inbound;

import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.api.pipeline.RawFrame;
import com.lingniu.ingest.identity.VehicleIdentity;
import com.lingniu.ingest.identity.VehicleIdentityLookup;
import com.lingniu.ingest.identity.VehicleIdentityResolver;
import com.lingniu.ingest.identity.VehicleIdentitySource;
import com.lingniu.ingest.protocol.jt808.codec.Jt808Escape;
import com.lingniu.ingest.protocol.jt808.codec.Jt808FrameEncoder;
import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder;
import com.lingniu.ingest.protocol.jt808.model.Jt808Body;
import com.lingniu.ingest.protocol.jt808.model.Jt808Message;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;

public final class Jsatl12JtMessageBridge {

    private final Jt808MessageDecoder decoder;
    private final VehicleIdentityResolver identityResolver;
    private final Jsatl12AttachmentTracker attachmentTracker;
    private final Consumer<RawFrame> dispatcher;

    public Jsatl12JtMessageBridge(Jt808MessageDecoder decoder, Consumer<RawFrame> dispatcher) {
        this(decoder, (VehicleIdentityResolver) null, dispatcher);
    }

    public Jsatl12JtMessageBridge(Jt808MessageDecoder decoder,
                                  VehicleIdentityResolver identityResolver,
                                  Consumer<RawFrame> dispatcher) {
        this(decoder, identityResolver, new Jsatl12AttachmentTracker(), dispatcher);
    }

    public Jsatl12JtMessageBridge(Jt808MessageDecoder decoder,
                                  Jsatl12AttachmentTracker attachmentTracker,
                                  Consumer<RawFrame> dispatcher) {
        this(decoder, null, attachmentTracker, dispatcher);
    }

    public Jsatl12JtMessageBridge(Jt808MessageDecoder decoder,
                                  VehicleIdentityResolver identityResolver,
                                  Jsatl12AttachmentTracker attachmentTracker,
                                  Consumer<RawFrame> dispatcher) {
        this.decoder = decoder;
        this.identityResolver = identityResolver;
        this.attachmentTracker = attachmentTracker == null ? new Jsatl12AttachmentTracker() : attachmentTracker;
        this.dispatcher = dispatcher;
    }

    public Optional<byte[]> dispatch(byte[] jtMessageBytes, String peer) {
        Instant receivedAt = Instant.now();
        String peerValue = peer == null ? "" : peer;
        byte[] raw = jtMessageBytes == null ? new byte[0] : jtMessageBytes;
        byte[] unescaped;
        Jt808Message message;
        try {
            unescaped = Jt808Escape.unescape(raw, 0, raw.length);
            message = decoder.decode(ByteBuffer.wrap(unescaped));
        } catch (RuntimeException e) {
            dispatchMalformed(raw, peerValue, e, receivedAt);
            return Optional.empty();
        }
        try {
            IdentityResolution identity = resolveIdentity(message.header().phone());
            Map<String, String> meta = new HashMap<>();
            meta.put("vin", identity.identity().vin());
            meta.put("phone", message.header().phone());
            meta.put("identityResolved", Boolean.toString(identity.identity().resolved()));
            meta.put("identitySource", identity.identity().source().name());
            if (identity.errorMessage() != null) {
                meta.put("identityError", "true");
                meta.put("identityErrorMessage", identity.errorMessage());
            }
            meta.put("peer", peerValue);
            meta.put("source", "jsatl12-signaling");
            dispatcher.accept(new RawFrame(
                    ProtocolId.JT808,
                    message.header().messageId(),
                    0,
                    message,
                    unescaped,
                    meta,
                    receivedAt));
            return handleJsatl12Signaling(message);
        } catch (RuntimeException e) {
            dispatchMalformed(raw, peerValue, e, receivedAt);
            return Optional.empty();
        }
    }

    private Optional<byte[]> handleJsatl12Signaling(Jt808Message message) {
        if (!(message.body() instanceof Jt808Body.Raw rawBody)) {
            return Optional.empty();
        }
        if (message.header().messageId() == 0x1210) {
            attachmentTracker.registerAttachmentList(message.header().phone(), rawBody.bytes());
            return Optional.empty();
        }
        if (message.header().messageId() == 0x1212) {
            Jsatl12UploadCompletionAck ack =
                    attachmentTracker.completeUpload(message.header().phone(), rawBody.bytes());
            return Optional.of(Jt808FrameEncoder.encode(
                    0x9212,
                    message.header().phone(),
                    message.header().serialNo(),
                    encodeAckBody(ack),
                    message.header().version()));
        }
        return Optional.empty();
    }

    private static byte[] encodeAckBody(Jsatl12UploadCompletionAck ack) {
        byte[] name = ack.name().getBytes(StandardCharsets.UTF_8);
        if (name.length > 255) {
            throw new IllegalArgumentException("jsatl12 completion ack filename too long: " + ack.name());
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream(4 + name.length + ack.missingRanges().size() * 8);
        out.write(name.length);
        out.write(name, 0, name.length);
        out.write(ack.type() & 0xFF);
        out.write(ack.result() & 0xFF);
        out.write(ack.missingRanges().size() & 0xFF);
        for (Jsatl12UploadCompletionAck.Range range : ack.missingRanges()) {
            writeDword(out, range.offset());
            writeDword(out, range.length());
        }
        return out.toByteArray();
    }

    private static void writeDword(ByteArrayOutputStream out, long value) {
        if (value < 0 || value > 0xFFFF_FFFFL) {
            throw new IllegalArgumentException("jsatl12 dword out of range: " + value);
        }
        out.write((int) ((value >>> 24) & 0xFF));
        out.write((int) ((value >>> 16) & 0xFF));
        out.write((int) ((value >>> 8) & 0xFF));
        out.write((int) (value & 0xFF));
    }

    private IdentityResolution resolveIdentity(String phone) {
        if (identityResolver == null) {
            return new IdentityResolution(
                    new VehicleIdentity(phone, false, VehicleIdentitySource.FALLBACK_PHONE),
                    null);
        }
        try {
            return new IdentityResolution(
                    identityResolver.resolve(new VehicleIdentityLookup(ProtocolId.JT808, "", phone, "", "")),
                    null);
        } catch (RuntimeException e) {
            return new IdentityResolution(
                    new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN),
                    e.getMessage() == null ? "" : e.getMessage());
        }
    }

    private void dispatchMalformed(byte[] raw, String peer, RuntimeException error, Instant receivedAt) {
        Map<String, String> meta = new HashMap<>();
        meta.put("vin", "unknown");
        meta.put("identityResolved", "false");
        meta.put("identitySource", VehicleIdentitySource.UNKNOWN.name());
        meta.put("peer", peer);
        meta.put("source", "jsatl12-jt-message");
        meta.put("parseError", "true");
        meta.put("parseErrorMessage", error.getMessage() == null ? "" : error.getMessage());
        dispatcher.accept(new RawFrame(
                ProtocolId.JSATL12,
                Jsatl12MessageId.MALFORMED_JT_MESSAGE,
                0,
                new Jsatl12MalformedJtMessage(raw, peer, error.getMessage(), receivedAt),
                raw,
                meta,
                receivedAt));
    }

    private record IdentityResolution(VehicleIdentity identity, String errorMessage) {}
}
