package ctbrec.sites.jasmin;

import ctbrec.Config;
import ctbrec.recorder.download.StreamSource;
import ctbrec.sites.Site;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URLEncoder;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static ctbrec.io.HttpConstants.USER_AGENT;
import static java.nio.charset.StandardCharsets.UTF_8;

public class LiveJasminStreamRegistration {

    private static final Logger LOG = LoggerFactory.getLogger(LiveJasminStreamRegistration.class);
    private static final String KEY_EVENT = "event";
    private static final String KEY_FUNC_NAME = "funcName";

    private final Site site;
    private final LiveJasminModelInfo modelInfo;
    private final CyclicBarrier barrier = new CyclicBarrier(2);

    public LiveJasminStreamRegistration(Site site, LiveJasminModelInfo modelInfo) {
        this.site = site;
        this.modelInfo = modelInfo;
    }

    List<StreamSource> getStreamSources() {
        var streamSources = new LinkedList<StreamSource>();
        try {
            Request webSocketRequest = new Request.Builder()
                    .url(modelInfo.getWebsocketUrl())
                    .addHeader(USER_AGENT, Config.getInstance().getSettings().httpUserAgentMobile)
                    .build();
            LOG.debug("Websocket: {}", modelInfo.getWebsocketUrl());
            site.getHttpClient().newWebSocket(webSocketRequest, new WebSocketListener() {
                @Override
                public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
                    LOG.debug("onOpen");
                    JSONObject register = new JSONObject()
                            .put(KEY_EVENT, "register")
                            .put("applicationId", "memberChat/jasmin" + modelInfo.getPerformerId() + modelInfo.getSbHash())
                            .put("connectionData", new JSONObject()
                                    .put("jasmin2App", false)
                                    .put("isMobileClient", true)
                                    .put("platform", "mobile")
                                    .put("chatID", "freechat")
                                    .put("sessionID", modelInfo.getSessionId())
                                    .put("jsm2SessionId", modelInfo.getJsm2session())
                                    .put("userType", "user")
                                    .put("performerId", modelInfo.getPerformerId())
                                    .put("clientRevision", "")
                                    .put("playerVer", "nanoPlayerVersion: 4.12.1 appCodeName: Mozilla appName: Netscape appVersion: 5.0 (iPad; CPU OS 10_14 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Mobile/15E148 Safari/605.1.15 platform: iPad")
                                    .put("livejasminTvmember", false)
                                    .put("newApplet", true)
                                    .put("livefeedtype", JSONObject.NULL)
                                    .put("gravityCookieId", "")
                                    .put("passparam", "")
                                    .put("clientInstanceId", modelInfo.getClientInstanceId())
                                    .put("armaVersion", "39.158.0")
                                    .put("isPassive", false)
                                    .put("brandID", "jasmin")
                                    .put("cobrandId", "")
                                    .put("subbrand", "livejasmin")
                                    .put("siteName", "LiveJasmin")
                                    .put("siteUrl", "https://m." + LiveJasmin.baseDomain)
                                    .put("chatHistoryRequired", false)
                                    .put("peekPatternJsm2", true)
                            );
                    webSocket.send(register.toString());
                    webSocket.send(new JSONObject().put(KEY_EVENT, "ping").toString());
                    webSocket.send(new JSONObject()
                            .put(KEY_EVENT, "call")
                            .put(KEY_FUNC_NAME, "makeActive")
                            .put("data", new JSONArray())
                            .toString());
                    webSocket.send(new JSONObject()
                            .put(KEY_EVENT, "call")
                            .put(KEY_FUNC_NAME, "setVideo")
                            .put("data", new JSONArray()
                                    .put(JSONObject.NULL)
                                    .put(false)
                                    .put(true)
                                    .put(modelInfo.getJsm2session())
                            )
                            .toString());
                    webSocket.send(new JSONObject()
                            .put(KEY_EVENT, "connectSharedObject")
                            .put("name", "data/chat_so")
                            .toString());
                    //webSocket.close(1000, "Good bye");
                }

                @Override
                public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
                    LOG.error("onFailure", t);
                    awaitBarrier();
                    webSocket.close(1000, "");
                }

                @Override
                public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
                    JSONObject message = new JSONObject(text);
                    if (message.opt(KEY_EVENT).equals("pong")) {
                        new Thread(() -> {
                            try {
                                Thread.sleep(message.optInt("nextPing"));
                            } catch (InterruptedException e) {
                                Thread.currentThread().interrupt();
                            }
                            webSocket.send(new JSONObject().put(KEY_EVENT, "ping").toString());
                        }).start();
                    } else if (message.optString(KEY_EVENT).equals("updateSharedObject") && message.optString("name").equals("data/chat_so")) {
                        LOG.trace(message.toString(2));
                        JSONArray list = message.getJSONArray("list");
                        for (int i = 0; i < list.length(); i++) {
                            JSONObject attribute = list.getJSONObject(i);
                            if (attribute.optString("name").equals("streamList")) {
                                JSONObject value = attribute.getJSONObject("newValue");
                                JSONObject patterns = value.getJSONObject("patterns");
                                String freePattern = patterns.getString("free");
                                JSONArray streams = value.getJSONArray("streams");
                                for (int j = 0; j < streams.length(); j++) {
                                    JSONObject stream = streams.getJSONObject(j);
                                    addStreamSource(streamSources, freePattern, stream);
                                }
                                webSocket.close(1000, "");
                            }
                        }
                    } else if (!message.optString(KEY_FUNC_NAME).equals("chatHistory")) {
                        LOG.trace("onMessageT {}", new JSONObject(text).toString(2));
                    }
                }

                @Override
                public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString bytes) {
                    LOG.trace("onMessageB");
                    super.onMessage(webSocket, bytes);
                }

                @Override
                public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
                    LOG.debug("onClosed {} {}", code, reason);
                    super.onClosed(webSocket, code, reason);
                }

                @Override
                public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
                    LOG.trace("onClosing {} {}", code, reason);
                    awaitBarrier();
                }
            });

            LOG.debug("Waiting for websocket to return");
            awaitBarrier();
            LOG.debug("Websocket is done. Stream sources {}", streamSources);
        } catch (Exception e) {
            LOG.error("Couldn't determine stream sources", e);
        }
        return streamSources;
    }

    private void addStreamSource(LinkedList<StreamSource> streamSources, String pattern, JSONObject stream) {
        int w = stream.getInt("width");
        int h = stream.getInt("height");
        int bitrate = stream.getInt("bitrate") * 1024;
        String name = stream.getString("name");
        String streamName = pattern.replace("{$streamname}", name);

        String rtmpUrl = "rtmp://{ip}/memberChat/jasmin{modelName}{sb_hash}?sessionId-{sessionId}|clientInstanceId-{clientInstanceId}"
                .replace("{ip}", modelInfo.getSbIp())
                .replace("{modelName}", modelInfo.getPerformerId())
                .replace("{sb_hash}", modelInfo.getSbHash())
                .replace("{sessionId}", modelInfo.getSessionId())
                .replace("{clientInstanceId}", modelInfo.getClientInstanceId());

        String hlsUrl = "https://dss-hls-{ipWithDashes}.dditscdn.com/h5live/http/playlist.m3u8?url={rtmpUrl}&stream={streamName}"
                .replace("{ipWithDashes}", modelInfo.getSbIp().replace('.', '-'))
                .replace("{rtmpUrl}", URLEncoder.encode(rtmpUrl, UTF_8))
                .replace("{streamName}", URLEncoder.encode(streamName, UTF_8));

        LiveJasminStreamSource streamSource = new LiveJasminStreamSource();
        streamSource.mediaPlaylistUrl = hlsUrl;
        streamSource.width = w;
        streamSource.height = h;
        streamSource.bandwidth = bitrate;
        streamSource.rtmpUrl = rtmpUrl;
        streamSource.streamName = streamName;
        streamSources.add(streamSource);
    }

    private void awaitBarrier() {
        try {
            barrier.await(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            LOG.error(e.getLocalizedMessage(), e);
        } catch (TimeoutException | BrokenBarrierException e) {
            LOG.error(e.getLocalizedMessage(), e);
        }
    }
}
