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

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public final class Jsatl12AttachmentTracker {

    private final Map<String, Map<String, FileState>> filesByPhone = new HashMap<>();

    public synchronized void registerAttachmentList(String phone, byte[] body) {
        String key = normalizePhone(phone);
        byte[] raw = body == null ? new byte[0] : body;
        if (raw.length < 57) {
            throw new IllegalArgumentException("jsatl12 T1210 body too short: " + raw.length);
        }
        int total = raw[56] & 0xFF;
        int pos = 57;
        Map<String, FileState> files = filesByPhone.computeIfAbsent(key, ignored -> new HashMap<>());
        for (int i = 0; i < total && pos < raw.length; i++) {
            int nameLen = raw[pos++] & 0xFF;
            if (pos + nameLen + 4 > raw.length) {
                throw new IllegalArgumentException("jsatl12 T1210 item truncated at index " + i);
            }
            String name = new String(raw, pos, nameLen, StandardCharsets.UTF_8).trim();
            pos += nameLen;
            long size = Jsatl12DataPacket.readDword(raw, pos);
            pos += 4;
            files.put(name, new FileState(name, 0, size));
        }
    }

    public synchronized Jsatl12DataPacket recordDataPacket(String phone, byte[] rawPacket) {
        Jsatl12DataPacket packet = Jsatl12DataPacket.parse(rawPacket);
        String key = normalizePhone(phone);
        Map<String, FileState> files = filesByPhone.computeIfAbsent(key, ignored -> new HashMap<>());
        FileState state = files.computeIfAbsent(packet.name(),
                ignored -> new FileState(packet.name(), 0, packet.offset() + packet.length()));
        state.ranges.add(new Range(packet.offset(), packet.offset() + packet.length()));
        return packet;
    }

    public synchronized Jsatl12UploadCompletionAck completeUpload(String phone, byte[] body) {
        T1212 completion = parseT1212(body);
        String key = normalizePhone(phone);
        Map<String, FileState> files = filesByPhone.computeIfAbsent(key, ignored -> new HashMap<>());
        FileState state = files.computeIfAbsent(completion.name(),
                ignored -> new FileState(completion.name(), completion.type(), completion.size()));
        mergeUnknownFileInto(key, state);
        state.type = completion.type();
        state.size = completion.size();

        List<Jsatl12UploadCompletionAck.Range> missing = missingRanges(state);
        if (missing.isEmpty()) {
            files.remove(completion.name());
            if (files.isEmpty()) {
                filesByPhone.remove(key);
            }
            return new Jsatl12UploadCompletionAck(completion.name(), completion.type(), 0, List.of());
        }
        return new Jsatl12UploadCompletionAck(completion.name(), completion.type(), 1, missing);
    }

    private void mergeUnknownFileInto(String ownerKey, FileState target) {
        if ("unknown".equals(ownerKey)) {
            return;
        }
        Map<String, FileState> unknownFiles = filesByPhone.get("unknown");
        if (unknownFiles == null) {
            return;
        }
        FileState unknown = unknownFiles.remove(target.name);
        if (unknown == null) {
            return;
        }
        target.ranges.addAll(unknown.ranges);
        if (unknownFiles.isEmpty()) {
            filesByPhone.remove("unknown");
        }
    }

    public synchronized Set<String> knownFiles(String phone) {
        Map<String, FileState> files = filesByPhone.get(normalizePhone(phone));
        if (files == null) {
            return Set.of();
        }
        return Set.copyOf(new HashSet<>(files.keySet()));
    }

    public synchronized String ownerPhone(String fileName) {
        String name = fileName == null ? "" : fileName.trim();
        if (name.isBlank()) {
            return "";
        }
        for (Map.Entry<String, Map<String, FileState>> entry : filesByPhone.entrySet()) {
            if (!"unknown".equals(entry.getKey()) && entry.getValue().containsKey(name)) {
                return entry.getKey();
            }
        }
        return "";
    }

    private static List<Jsatl12UploadCompletionAck.Range> missingRanges(FileState state) {
        List<Range> ranges = new ArrayList<>(state.ranges);
        ranges.sort((a, b) -> Long.compare(a.start, b.start));
        List<Jsatl12UploadCompletionAck.Range> missing = new ArrayList<>();
        long cursor = 0;
        for (Range range : ranges) {
            if (range.end <= cursor) {
                continue;
            }
            if (range.start > cursor) {
                missing.add(new Jsatl12UploadCompletionAck.Range(cursor, range.start - cursor));
            }
            cursor = Math.max(cursor, range.end);
            if (cursor >= state.size) {
                break;
            }
        }
        if (cursor < state.size) {
            missing.add(new Jsatl12UploadCompletionAck.Range(cursor, state.size - cursor));
        }
        return List.copyOf(missing);
    }

    private static T1212 parseT1212(byte[] body) {
        byte[] raw = body == null ? new byte[0] : body;
        if (raw.length < 6) {
            throw new IllegalArgumentException("jsatl12 T1212 body too short: " + raw.length);
        }
        int nameLen = raw[0] & 0xFF;
        if (raw.length < 1 + nameLen + 1 + 4) {
            throw new IllegalArgumentException("jsatl12 T1212 body truncated");
        }
        String name = new String(raw, 1, nameLen, StandardCharsets.UTF_8).trim();
        int type = raw[1 + nameLen] & 0xFF;
        long size = Jsatl12DataPacket.readDword(raw, 2 + nameLen);
        return new T1212(name, type, size);
    }

    private static String normalizePhone(String phone) {
        return phone == null || phone.isBlank() ? "unknown" : phone;
    }

    private static final class FileState {
        private final String name;
        private final List<Range> ranges = new ArrayList<>();
        private int type;
        private long size;

        private FileState(String name, int type, long size) {
            this.name = name;
            this.type = type;
            this.size = Math.max(0, size);
        }
    }

    private record Range(long start, long end) {}

    private record T1212(String name, int type, long size) {}
}
