package com.retrytech.strangerapp.utils.media;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.SortedMap;
import java.util.TreeMap;

import static com.retrytech.strangerapp.utils.media.Utils.crc32;

public class AccessToken {
    private static final String VER = "006";
    private String appId;
    private String appCertificate;
    private String channelName;
    private String uid;
    private byte[] signature;
    private byte[] messageRawContent;
    private int crcChannelName;
    private int crcUid;
    private PrivilegeMessage message;

    public AccessToken(String appId, String appCertificate, String channelName, String uid) {
        this.appId = appId;
        this.appCertificate = appCertificate;
        this.channelName = channelName;
        this.uid = uid;
        this.crcChannelName = 0;
        this.crcUid = 0;
        this.message = new PrivilegeMessage();
    }

    public static String getVersion() {
        return VER;
    }

    public static byte[] generateSignature(String appCertificate,
                                           String appID, String channelName, String uid, byte[] message) throws InvalidKeyException, NoSuchAlgorithmException {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            baos.write(appID.getBytes());
            baos.write(channelName.getBytes());
            baos.write(uid.getBytes());
            baos.write(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Utils.hmacSign(appCertificate, baos.toByteArray());
    }

    public String build() throws InvalidKeyException, NoSuchAlgorithmException {
        if (!Utils.isUUID(appId)) {
            return "";
        }

        if (!Utils.isUUID(appCertificate)) {
            return "";
        }

        messageRawContent = Utils.pack(message);
        signature = generateSignature(appCertificate,
                appId, channelName, uid, messageRawContent);
        crcChannelName = crc32(channelName);
        crcUid = crc32(uid);

        PackContent packContent = new PackContent(signature, crcChannelName, crcUid, messageRawContent);
        byte[] content = Utils.pack(packContent);
        return getVersion() + this.appId + Utils.base64Encode(content);
    }

    public void addPrivilege(Privileges privilege, int expireTimestamp) {
        message.messages.put(privilege.intValue, expireTimestamp);
    }

    public boolean fromString(String token) {
        if (!getVersion().equals(token.substring(0, Utils.VERSION_LENGTH))) {
            return false;
        }

        try {
            appId = token.substring(Utils.VERSION_LENGTH, Utils.VERSION_LENGTH + Utils.APP_ID_LENGTH);
            PackContent packContent = new PackContent();
            Utils.unpack(Utils.base64Decode(token.substring(Utils.VERSION_LENGTH + Utils.APP_ID_LENGTH, token.length())), packContent);
            signature = packContent.signature;
            crcChannelName = packContent.crcChannelName;
            crcUid = packContent.crcUid;
            messageRawContent = packContent.rawMessage;
            Utils.unpack(messageRawContent, message);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    public enum Privileges {
        K_JOIN_CHANNEL(1),
        K_PUBLISH_AUDIO_STREAM(2),
        K_PUBLISH_VIDEO_STREAM(3),
        K_PUBLISH_DATA_STREAM(4),

        // For RTM only
        K_RTM_LOGIN(1000);

        // The following privileges have not
        // been implemented yet.


        private short intValue;


        Privileges(int value) {
            intValue = (short) value;
        }
    }

    public class PrivilegeMessage implements PackableEx {
        private int salt;
        private int ts;
        private SortedMap<Short, Integer> messages;

        public PrivilegeMessage() {
            salt = Utils.randomInt();
            ts = Utils.getTimestamp() + 24 * 3600;
            messages = new TreeMap<>();
        }

        @Override
        public ByteBuf marshal(ByteBuf out) {
            return out.put(salt).put(ts).putIntMap(messages);
        }

        @Override
        public void unmarshal(ByteBuf in) {
            salt = in.readInt();
            ts = in.readInt();
            messages = in.readIntMap();
        }
    }

    public class PackContent implements PackableEx {
        private byte[] signature;
        private int crcChannelName;
        private int crcUid;
        private byte[] rawMessage;

        public PackContent() {
            // Nothing done
        }

        public PackContent(byte[] signature, int crcChannelName, int crcUid, byte[] rawMessage) {
            this.signature = signature;
            this.crcChannelName = crcChannelName;
            this.crcUid = crcUid;
            this.rawMessage = rawMessage;
        }

        @Override
        public ByteBuf marshal(ByteBuf out) {
            return out.put(signature).put(crcChannelName).put(crcUid).put(rawMessage);
        }

        @Override
        public void unmarshal(ByteBuf in) {
            signature = in.readBytes();
            crcChannelName = in.readInt();
            crcUid = in.readInt();
            rawMessage = in.readBytes();
        }
    }
}
