/*
 *
 *    Copyright (c) 2025 Project CHIP Authors
 *    All rights reserved.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
#include "camera-device.h"
#include "webrtc-provider-manager.h"
#include <iomanip>
#include <jsmn.h>
#include <signaling_serializer.h>
#include <sstream>
#include <string.h>
#include <webrtc-kvs_esp_port_utils.h>

using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::WebRTCTransportProvider;

using namespace Camera;

static const char * TAG = "webrtc-kvs_esp_port_utils";

static size_t gSDPLength = CONFIG_MAX_LARGE_BUFFER_SIZE_BYTES;
static char peerClientId[SS_MAX_SIGNALING_CLIENT_ID_LEN + 1];

extern CameraDevice gCameraDevice;

static std::atomic<unsigned int> peerConnectionCounter{ 0 }; // Starts from 0

static std::string json_unescape(const std::string  &input)
{
    std::string output;
    size_t i = 0;
    while (i < input.length()) {
        if (input[i] == '\\' && i + 1 < input.length()) {
            char next = input[i + 1];
            switch (next) {
            case '\"':
                output += '\"';
                i += 2;
                break;
            case '\\':
                output += '\\';
                i += 2;
                break;
            case 'b':
                output += '\b';
                i += 2;
                break;
            case 'f':
                output += '\f';
                i += 2;
                break;
            case 'n':
                output += '\n';
                i += 2;
                break;
            case 'r':
                output += '\r';
                i += 2;
                break;
            case 't':
                output += '\t';
                i += 2;
                break;
            case 'u':
                if (i + 5 < input.length()) {
                    std::istringstream iss(input.substr(i + 2, 4));
                    unsigned int code;
                    if (iss >> std::hex >> code) {
                        output += static_cast<char>(code); // For ASCII-range only
                    }
                    i += 6;
                } else {
                    output += '?'; // malformed
                    i += 2;
                }
                break;
            default:
                output += next;
                i += 2;
            }
        } else {
            output += input[i];
            ++i;
        }
    }
    return output;
}

std::string json_escape(const std::string  &input)
{
    std::string output;
    for (char c : input) {
        switch (c) {
        case '\"':
            output += "\\\"";
            break;
        case '\\':
            output += "\\\\";
            break;
        case '\b':
            output += "\\b";
            break;
        case '\f':
            output += "\\f";
            break;
        case '\n':
            output += "\\n";
            break;
        case '\r':
            output += "\\r";
            break;
        case '\t':
            output += "\\t";
            break;
        default:
            if (static_cast<unsigned char>(c) < 0x20) {
                char buf[7];
                snprintf(buf, sizeof(buf), "\\u%04x", c);
                output += buf;
            } else {
                output += c;
            }
        }
    }
    return output;
}

static int extract_sdp(const char * json, char * sdp_buf, size_t sdp_buf_len)
{
    if (json == nullptr || sdp_buf == nullptr || sdp_buf_len == 0) {
        ChipLogError(Camera, "extract_sdp failed");
        return -1;
    }

    jsmn_parser parser;
    jsmntok_t tokens[64];
    int ret;

    jsmn_init(&parser);
    ret = jsmn_parse(&parser, json, strlen(json), tokens, sizeof(tokens) / sizeof(tokens[0]));
    if (ret < 0) {
        printf("Failed to parse JSON: %d\n", ret);
        return -1;
    }

    for (int i = 1; i < ret; i++) {
        if (tokens[i].type == JSMN_STRING && strncmp(json + tokens[i].start, "sdp", tokens[i].end - tokens[i].start) == 0) {
            int len = tokens[i + 1].end - tokens[i + 1].start;
            if (len >= sdp_buf_len) {
                len = sdp_buf_len - 1;
            }
            strncpy(sdp_buf, json + tokens[i + 1].start, len);
            sdp_buf[len] = '\0';
            return 0;
        }
    }

    return -1; // SDP not found
}

static int extract_candidate(const char * json, char * sdp_buf, size_t sdp_buf_len)
{
    // Sanity checks for input parameters
    if (json == nullptr || sdp_buf == nullptr || sdp_buf_len == 0) {
        ChipLogError(Camera, "extract_candidate failed");
        return -1;
    }

    jsmn_parser parser;
    jsmntok_t tokens[64];
    int ret;

    jsmn_init(&parser);
    ret = jsmn_parse(&parser, json, strlen(json), tokens, sizeof(tokens) / sizeof(tokens[0]));
    if (ret < 0) {
        printf("Failed to parse JSON: %d\n", ret);
        return -1;
    }

    for (int i = 1; i < ret; i++) {
        if (tokens[i].type == JSMN_STRING && strncmp(json + tokens[i].start, "candidate", tokens[i].end - tokens[i].start) == 0) {
            int len = tokens[i + 1].end - tokens[i + 1].start;
            if (len >= sdp_buf_len) {
                len = sdp_buf_len - 1;
            }
            strncpy(sdp_buf, json + tokens[i + 1].start, len);
            sdp_buf[len] = '\0';
            return 0;
        }
    }

    return -1; // Candidate not found
}

void webrtc_bridge_message_received_cb(void * data, int len)
{
    // handle message
    printf("Received Message from P4-Streamer: \n%.*s\n", len, (char *) data);

    // Use nothrow to check for allocation failure
    std::unique_ptr<signaling_msg_t> msg(new (std::nothrow) signaling_msg_t());
    if (msg == nullptr) {
        ChipLogError(Camera, "webrtc_bridge_message_received_cb: failed to allocate signaling_msg_t");
        return;
    }

    deserialize_signaling_message((const char *) data, len, msg.get());

    char sdp_buf[gSDPLength];
    switch (msg->messageType) {
    case SIGNALING_MSG_TYPE_OFFER:
        if (extract_sdp(msg->payload, sdp_buf, sizeof(sdp_buf)) == 0) {
            ESP_LOGD(TAG, "Extracted SDP:\n%s\n", sdp_buf);
        }
        break;
    case SIGNALING_MSG_TYPE_ANSWER:
        if (extract_sdp(msg->payload, sdp_buf, sizeof(sdp_buf)) == 0) {
            ESP_LOGD(TAG, "Extracted SDP:\n%s\n", sdp_buf);
        }
        break;
    case SIGNALING_MSG_TYPE_ICE_CANDIDATE:
        if (extract_candidate(msg->payload, sdp_buf, sizeof(sdp_buf)) == 0) {
            ESP_LOGD(TAG, "Extracted Candidate:\n%s\n", sdp_buf);
        }
        break;
    default:
        ESP_LOGE(TAG, "Unknown message type\n");
        goto cleanup;
        break;
    }

    {
        uint16_t sessionId = 0;
        snprintf(peerClientId, sizeof(peerClientId), "%s", msg->peerClientId);
        printf("Peer Client ID: \n%s\n", peerClientId);

        sessionId = static_cast<uint16_t>(strtoul(peerClientId, nullptr, 0)); // base 0 auto-detects "0x"

        printf("Session ID: %u\n", sessionId);

        std::string unescaped_msg = json_unescape(std::string(sdp_buf));

        ESP_LOGD(TAG, "unescaped msg: \n%s\n", unescaped_msg.c_str());

        if (msg->messageType == SIGNALING_MSG_TYPE_OFFER) {
            WebRTCTransportProvider::Delegate  &delegateRef = gCameraDevice.GetWebRTCProviderDelegate();
            auto * webrtcMgr                                = static_cast<WebRTCProviderManager *>(&delegateRef);
            if (webrtcMgr != nullptr) {
                WebrtcTransport * transport = webrtcMgr->GetTransport(sessionId);
                if (transport != nullptr) {
                    transport->OnLocalDescription(unescaped_msg, SDPType::Offer);
                    printf("Set SDP Offer to WebRTCProviderManager\n");
                } else {
                    printf("Transport is not found for sessionID: %u\n", sessionId);
                }
            } else {
                printf("Delegate is not of type WebRTCProviderManager\n");
            }
        } else if (msg->messageType == SIGNALING_MSG_TYPE_ANSWER) {
            WebRTCTransportProvider::Delegate  &delegateRef = gCameraDevice.GetWebRTCProviderDelegate();
            auto * webrtcMgr                                = static_cast<WebRTCProviderManager *>(&delegateRef);
            if (webrtcMgr != nullptr) {
                WebrtcTransport * transport = webrtcMgr->GetTransport(sessionId);
                if (transport != nullptr) {
                    transport->OnLocalDescription(unescaped_msg, SDPType::Answer);
                    printf("Set SDP Answer to WebRTCProviderManager\n");
                } else {
                    printf("Transport is not found for sessionID: %u\n", sessionId);
                }
                printf("Set SDP Answer to WebRTCProviderManager\n");
            } else {
                printf("Delegate is not of type WebRTCProviderManager\n");
            }
        } else if (msg->messageType == SIGNALING_MSG_TYPE_ICE_CANDIDATE) {
            WebRTCTransportProvider::Delegate  &delegateRef = gCameraDevice.GetWebRTCProviderDelegate();
            auto * webrtcMgr                                = static_cast<WebRTCProviderManager *>(&delegateRef);
            if (webrtcMgr != nullptr) {
                WebrtcTransport * transport = webrtcMgr->GetTransport(sessionId);
                if (transport != nullptr) {
                    transport->OnICECandidate(unescaped_msg); // todo: session id based
                    printf("Set Candidate to WebRTCProviderManager\n");
                } else {
                    printf("Transport is not found for sessionID: %u\n", sessionId);
                }
                printf("Set Candidate to WebRTCProviderManager\n");
            } else {
                printf("Delegate is not of type WebRTCProviderManager\n");
            }
        }
    }

cleanup:
    if (msg->payload) {
        free(msg->payload);
    }
}

std::string generateMonotonicPeerConnectionId()
{
    // Atomically get the current ID and increment it
    unsigned int id = peerConnectionCounter.fetch_add(1, std::memory_order_relaxed);

    // Convert the ID to a zero-padded 8-digit hex string
    std::ostringstream oss;
    oss << "0x" << std::setw(8) << std::setfill('0') << std::hex << id;

    return oss.str();
}
