/*
 * Copyright (c) 2024-2025  The AdvantEDGE Authors
 *
 * 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.
 */

package sssmgr

import (
	"encoding/json"
	"errors"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"

	log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger"

	uuid "github.com/google/uuid"
)

// Sensors-Sharing Service Manager
type SssMgr struct {
	name                 string
	namespace            string
	bindingProtocol      string
	host                 string
	port                 int
	cse_name             string
	hostId               string
	mutex                sync.Mutex
	wg                   sync.WaitGroup
	refreshTicker        *time.Ticker
	sss_discovery_notify func(map[string]interface{})
	sss_status_notify    func(map[string]interface{})
	sss_data_notify      func(map[string]interface{})
}

type IotPlatformInfo struct {
	Address       string
	Port          int
	Name          string
	IotPlatformId string
}

type Point struct {
	Latitude  float64
	Longitude float64
}

type SensorCharacteristic struct {
	CharacteristicName          string
	CharacteristicValue         string
	CharacteristicUnitOfMeasure *string
}

type SensorDiscoveryInfo struct {
	SensorIdentifier         string
	SensorType               string
	SensorPropertyList       []string
	SensorCharacteristicList []SensorCharacteristic
	SensorPosition           *Point
	IotPlatformId            string
	Flex                     map[string]interface{}
}

var registeredIotPlatformsMap = map[string]IotPlatformInfo{} // List of discovered IOT Plateform
var sensorsMap = map[string]SensorDiscoveryInfo{}            // Map sensors by sensorIdentifier
var sensorsPerPlatformMap = map[string][]string{}            // Map dsensorIdentifiers per platform

// Timer to refresh devices list for all IoT platform
const refreshTickerExpeary = 10 // In seconds

// Enable profiling
const profiling = false

var profilingTimers map[string]time.Time

var protocol SssMgrBindingProtocol

var subscriptionListPerSubId map[string]SensorDiscoveryInfo

// NewSssMgr - Creates and initializes a new SSS Traffic Manager
func NewSssMgr(name string, namespace string, bindingProtocol string, host string, port int, hostId string, cse_name string, sss_discovery_notify func(map[string]interface{}), sss_status_notify func(map[string]interface{}), sss_data_notify func(map[string]interface{})) (tm *SssMgr, err error) {
	if name == "" {
		err = errors.New("Missing connector name")
		return nil, err
	}

	// Create new Traffic Manager
	tm = new(SssMgr)
	tm.name = name
	if namespace != "" {
		tm.namespace = namespace
	} else {
		tm.namespace = "default"
	}

	tm.bindingProtocol = bindingProtocol
	tm.host = host
	tm.port = port
	tm.cse_name = cse_name
	tm.hostId = hostId
	if tm.bindingProtocol == "MQTT" {
		if tm.host == "" {
			err := errors.New("Host not set for MQTTP protocol")
			log.Error(err.Error())
			return nil, err
		}
		if tm.port == 0 {
			tm.port = 1883
		}
		protocol = NewSssMgrMqtt()
	} else if tm.bindingProtocol == "REST_HTTP" {
		if tm.port == 0 {
			tm.port = 80
		}
		protocol = NewSssMgrHttp()
	} else {
		err := errors.New("Binding protocol not set")
		log.Error(err.Error())
		return nil, err
	}
	if hostId == "" {
		err := errors.New("hostId not set")
		log.Error(err.Error())
		return nil, err
	}
	if cse_name == "" {
		err := errors.New("cse_name not set")
		log.Error(err.Error())
		return nil, err
	}

	err = protocol.init(tm, tm.notify)
	if err != nil {
		log.Error(err.Error())
		return nil, err
	}

	tm.sss_discovery_notify = sss_discovery_notify
	tm.sss_status_notify = sss_status_notify
	tm.sss_data_notify = sss_data_notify

	tm.init()

	return tm, nil
}

// Profiling init
func (tm *SssMgr) init() {
	if profiling {
		profilingTimers = make(map[string]time.Time)
	}

	registeredIotPlatformsMap = make(map[string]IotPlatformInfo, 1)
	registeredIotPlatformsMap[tm.hostId] = IotPlatformInfo{
		Address:       tm.host,
		Port:          tm.port,
		Name:          tm.cse_name,
		IotPlatformId: tm.hostId,
	}
	sensorsMap = make(map[string]SensorDiscoveryInfo, 0)
	sensorsPerPlatformMap = make(map[string][]string, 0)
	tm.refreshTicker = nil
	subscriptionListPerSubId = make(map[string]SensorDiscoveryInfo, 0)
}

// DeleteSssMgr -
func (tm *SssMgr) DeleteSssMgr() (err error) {
	tm.stopRefreshTicker()

	if protocol != nil {
		protocol.uninit()
		protocol = nil
	}

	sensorsMap = nil
	sensorsPerPlatformMap = nil
	subscriptionListPerSubId = nil

	return nil
}

func (tm *SssMgr) startRefreshTicker() {
	log.Debug(">>> sssmgr.startRefreshTicker")
	tm.refreshTicker = time.NewTicker(refreshTickerExpeary * time.Second)
	go func() {
		if tm.refreshTicker != nil {
			for range tm.refreshTicker.C {
				// Refresh the list of devices
				tm.wg.Add(1)
				err := tm.populateDevicesPerIotPlatforms()
				if err != nil {
					log.Error(err)
				}
				log.Debug("sssmgr.startRefreshTicker: Before Done()")
				tm.wg.Done()
				log.Debug("sssmgr.startRefreshTicker: Before Done()")
			} // End of 'for' statement
			log.Debug("sssmgr.startRefreshTicker: Leaving time loop")
		}
	}()
}

func (tm *SssMgr) stopRefreshTicker() {
	if tm.refreshTicker != nil {
		// Refresh the list of devices
		tm.wg.Add(1)
		tm.refreshTicker.Stop()
		tm.refreshTicker = nil
		registeredIotPlatformsMap = nil
		sensorsMap = nil
		sensorsPerPlatformMap = nil
		tm.wg.Done()
		log.Debug("Refresh loop stopped")
	}
}

func (tm *SssMgr) SensorDiscoveryInfoAll() (sensors []SensorDiscoveryInfo, err error) {
	if profiling {
		profilingTimers["SensorDiscoveryInfoAll"] = time.Now()
	}

	log.Info(">>> SensorDiscoveryInfoAll")

	err = tm.populateDevicesPerIotPlatforms() // FIXME FSCOM User timer. See startRefreshTicker
	if err != nil {
		return sensors, err
	}

	tm.wg.Wait()
	log.Info("SensorDiscoveryInfoAll: After Synchro")

	sensors = make([]SensorDiscoveryInfo, 0)
	if len(registeredIotPlatformsMap) == 0 {
		return sensors, nil
	}

	for _, v := range sensorsMap {
		log.Info("SensorDiscoveryInfoAll: adding sensor: ", v)
		sensors = append(sensors, v)
	} // End of 'for' statement
	log.Info("SensorDiscoveryInfoAll: sensors: ", sensors)

	if profiling {
		now := time.Now()
		log.Debug("SensorDiscoveryInfoAll: ", now.Sub(profilingTimers["SensorDiscoveryInfoAll"]))
	}

	return sensors, nil
}

func (tm *SssMgr) GetSensor(sensorIdentifier string) (sensor SensorDiscoveryInfo, err error) {
	if profiling {
		profilingTimers["GetSensor"] = time.Now()
	}

	log.Info(">>> GetSensor: sensorIdentifier: ", sensorIdentifier)

	tm.wg.Wait()
	log.Info("GetSensor: After Synchro")

	if val, ok := sensorsMap[sensorIdentifier]; !ok {
		err = errors.New("Wrong Device identifier")
		return sensor, err
	} else {
		sensor = val
	}

	if profiling {
		now := time.Now()
		log.Debug("GetSensor: ", now.Sub(profilingTimers["GetSensor"]))
	}
	log.Info("GetSensor: sensor: ", sensor)

	return sensor, nil
}

/*
 * func populateDevicesPerIotPlatforms IoT devices for all registered Iot platform
 * @return {struct} nil on success, error otherwise
 */
func (tm *SssMgr) populateDevicesPerIotPlatforms() error {

	tm.mutex.Lock()
	defer tm.mutex.Unlock()

	if profiling {
		profilingTimers["populateDevicesPerIotPlatforms"] = time.Now()
	}

	if len(registeredIotPlatformsMap) == 0 {
		return nil
	}

	// Refresh the list of devices for all registered Iot platform
	for _, iotPlatform := range registeredIotPlatformsMap {
		log.Debug("populateDevicesPerIotPlatforms: processing: ", iotPlatform.Address)
		err := tm.populateSensors(iotPlatform, "3")
		if err != nil {
			log.Error("populateDevicesPerIotPlatforms: ", err)
			continue
		}
		err = tm.populateSensors(iotPlatform, "28")
		if err != nil {
			log.Error("populateDevicesPerIotPlatforms: ", err)
			continue
		}
	} // End of 'for' statement

	if profiling {
		now := time.Now()
		log.Debug("populateDevicesPerIotPlatforms: ", now.Sub(profilingTimers["populateDevicesPerIotPlatforms"]))
	}

	return nil
}

/*
 * func PopulateDevices IoT devices for the specified Iot platform
 * @param {string} iotPlatformId contains the IoT platform identifier
 * @return {struct} nil on success, error otherwise
 */
func (tm *SssMgr) populateSensors(iotPlatformInfo IotPlatformInfo, type_ string) error {
	if profiling {
		profilingTimers["populateSensors"] = time.Now()
	}

	log.Info(">>> populateSensors: iotPlatformInfo=", iotPlatformInfo)

	// 1. Get the list of the AE
	// Build the context
	var ctx = SssMgrBindingProtocolContext{
		host: iotPlatformInfo.Address,
		port: iotPlatformInfo.Port,
		name: iotPlatformInfo.Name,
		to:   iotPlatformInfo.Name,
		from: "Admin", // FIXME FSCOM How to get it
		op:   2,       // RETRIEVE
		ty:   -1,
		rqi:  uuid.New().String(),
		rvi:  []string{"4"}, // FIXME FSCOM How to get it
		code: 200,
	}
	// Build the queries
	queries := map[string]string{}
	queries["fu"] = "1" // Filter usage
	queries["ty"] = type_
	ctx.queries = queries

	err, resp := protocol.send(ctx)
	if err != nil {
		log.Error("OneM2M_create: ", err.Error())
		return err
	}
	log.Debug("populateSensors: resp: ", resp)
	log.Debug("populateSensors: TypeOf(resp): ", reflect.TypeOf(resp))
	oneM2M_uril := resp.(map[string]interface{})
	log.Debug("populateSensors: oneM2M_uril: ", oneM2M_uril)
	log.Debug("populateSensors: TypeOf(oneM2M_uril): ", reflect.TypeOf(oneM2M_uril))
	log.Debug("populateSensors: len(oneM2M_uril): ", len(oneM2M_uril))
	// Loop for each CIN and build the sensor list
	for _, v := range oneM2M_uril["m2m:uril"].([]interface{}) {
		log.Debug("populateSensors: Processing key: v: ", v)
		log.Debug("populateSensors: Processing key: TypeOf(v): ", reflect.TypeOf(v))
		s := v.(string)
		if s == "laboai-cse-in/acpCreateACPs" || s == "laboai-cse-in/CAdmin" {
			// FIXME FSCOM Bug in MQTT DISCOVERY request which does not provide the same response that HTTP DISCOVERY with the same filter criteria
			continue // Discard it
		}
		ctx.to = s
		ctx.queries["fu"] = "2"
		err, resp := protocol.send(ctx)
		if err != nil {
			log.Error("populateSensors: ", err.Error())
			continue
		}
		log.Debug("populateSensors: resp: ", resp)
		log.Debug("populateSensors: type(resp): ", reflect.TypeOf(resp))
		if resp.(map[string]interface{}) == nil {
			continue
		}
		// oneM2M_cin := resp.(map[string]interface{})["m2m:cnt"].(map[string]interface{})
		// log.Debug("populateSensors: type(oneM2M_cin): ", reflect.TypeOf(oneM2M_cin))
		// log.Debug("populateSensors: len(oneM2M_cin): ", len(oneM2M_cin))
		// log.Debug("populateSensors: oneM2M_cin: ", oneM2M_cin)
		var sensor = SensorDiscoveryInfo{
			IotPlatformId: iotPlatformInfo.IotPlatformId,
		}
		if type_ == "28" {
			sensor.SensorType = "FLX"
			sensor.Flex = map[string]interface{}{} // FIXME FSCOM How to create flex container map from list of attributes recieved? Q&D: extract all attributes != attributes[AE/CNT/CNI]???
			// ==> Use cnd which contains the link to download the onthology or the reference to the oneM2M standard
		}
		sensor, err = tm.oneM2M_deserialize(sensor, resp.(map[string]interface{}))
		if err != nil {
			log.Warn("populateSensors: ", err.Error())
			continue
		}

		log.Info("populateSensors: sensor: ", sensor)
		sensorsMap[sensor.SensorIdentifier] = sensor
		sensorsPerPlatformMap[sensor.IotPlatformId] = append(sensorsPerPlatformMap[sensor.IotPlatformId], sensor.SensorIdentifier)
	} // End of 'for' statement

	log.Info("populateSensors: sensorsMap: ", sensorsMap)
	log.Info("populateSensors: sensorsPerPlatformMap: ", sensorsPerPlatformMap)

	if profiling {
		now := time.Now()
		log.Debug("populateSensors: ", now.Sub(profilingTimers["populateSensors"]))
	}

	return nil
}

func (tm *SssMgr) OneM2M_create(sensor SensorDiscoveryInfo, path string) (sensorResp SensorDiscoveryInfo, err error) {

	if profiling {
		profilingTimers["OneM2M_create"] = time.Now()
	}

	log.Info(">>> OneM2M_create: sensor=", sensor)

	if sensor.IotPlatformId == "" {
		err = errors.New("IotPlatformId fiels shall be set")
		log.Error("OneM2M_create: ", err.Error())
		return sensorResp, err
	}

	tm.wg.Wait()
	log.Info("OneM2M_create: After Synchro")

	// Create the initial payload dictionary
	var bodyMap = map[string]map[string]interface{}{}
	// Initialize the entry
	if sensor.SensorType == "AE" { // FIXME FSCOM Clarify how to map Deviceinfo with oneM2M AE/CNT/fexContainer
		bodyMap["m2m:ae"] = make(map[string]interface{}, 0)
		bodyMap["m2m:ae"]["api"] = "Norg.etsi." + sensor.IotPlatformId + "." + sensor.SensorIdentifier
		bodyMap["m2m:ae"]["rn"] = sensor.SensorIdentifier
		bodyMap["m2m:ae"]["rr"] = false
		bodyMap["m2m:ae"]["srv"] = []string{"4"}
		// Add metadata
		if len(sensor.SensorCharacteristicList) != 0 {
			for _, val := range sensor.SensorCharacteristicList {
				log.Debug("OneM2M_create: Adding AE metadata: ", val)
				bodyMap["m2m:ae"][val.CharacteristicName] = val.CharacteristicValue
			} // End of 'for' statement
		}
	} else if sensor.SensorType == "CNT" {
		bodyMap["m2m:cnt"] = make(map[string]interface{}, 0)
		bodyMap["m2m:cnt"]["mbs"] = 10000
		bodyMap["m2m:cnt"]["mni"] = 10
		bodyMap["m2m:cnt"]["rn"] = sensor.SensorIdentifier
		// Add metadata
		if len(sensor.SensorCharacteristicList) != 0 {
			for _, val := range sensor.SensorCharacteristicList {
				log.Debug("OneM2M_create: Adding CNT metadata: ", val)
				bodyMap["m2m:cnt"][val.CharacteristicName] = val.CharacteristicValue
			} // End of 'for' statement
		}
	} else if sensor.SensorType == "CIN" {
		bodyMap["m2m:cin"] = make(map[string]interface{}, 0)
		bodyMap["m2m:cin"]["cnf"] = "text/plain:0"
		bodyMap["m2m:cin"]["rn"] = sensor.SensorIdentifier
		// Add metadata
		if len(sensor.SensorCharacteristicList) != 0 {
			for _, val := range sensor.SensorCharacteristicList {
				log.Debug("OneM2M_create: Adding CIN metadata: ", val)
				bodyMap["m2m:cin"][val.CharacteristicName] = val.CharacteristicValue
			} // End of 'for' statement
		}
	} else if sensor.SensorType == "FLX" {
		// Sanity checks
		if sensor.Flex == nil {
			err = errors.New("flex parameter shall be present")
			log.Error("OneM2M_create: ", err.Error())
			return sensorResp, err
		} else if _, ok := sensor.Flex["type"]; !ok {
			err = errors.New("'type' entry is required")
			log.Error("OneM2M_create: ", err.Error())
			return sensorResp, err
		} else if _, ok := sensor.Flex["type"].(string); !ok {
			err = errors.New("'type' entry is required")
			log.Error("OneM2M_create: ", err.Error())
			return sensorResp, err
		} else if _, ok := sensor.Flex["cnd"]; !ok {
			err = errors.New("'cnd' entry is required")
			log.Error("OneM2M_create: ", err.Error())
			return sensorResp, err
		} else if _, ok := sensor.Flex["cnd"].(string); !ok {
			err = errors.New("'cnd' entry is required")
			log.Error("OneM2M_create: ", err.Error())
			return sensorResp, err
		}
		k := sensor.Flex["type"].(string)
		bodyMap[k] = make(map[string]interface{}, 0)
		bodyMap[k]["rn"] = sensor.SensorIdentifier
		for i, v := range sensor.Flex {
			if i == "type" {
				continue // skip it
			}
			bodyMap[k][i] = v
		} // End of 'for' statement
	} else {
		err = errors.New("OneM2M_create: Invalid type")
		log.Error("OneM2M_create: ", err.Error())
		return sensorResp, err
	}

	// Send it and get the result
	var ctx = SssMgrBindingProtocolContext{
		host: registeredIotPlatformsMap[sensor.IotPlatformId].Address,
		port: registeredIotPlatformsMap[sensor.IotPlatformId].Port,
		name: registeredIotPlatformsMap[sensor.IotPlatformId].Name,
		to:   registeredIotPlatformsMap[sensor.IotPlatformId].Name,
		from: sensor.IotPlatformId,
		op:   1, // CREATE
		rqi:  uuid.New().String(),
		rvi:  []string{"4"}, // FIXME FSCOM How to get it
		body: bodyMap,
		code: 201,
	}
	if path != "" {
		ctx.to = path
	}
	if sensor.SensorType == "AE" {
		ctx.ty = 2
	} else if sensor.SensorType == "CNT" {
		ctx.ty = 3
	} else if sensor.SensorType == "CIN" {
		ctx.ty = 4
	} else if sensor.SensorType == "FLX" {
		ctx.ty = 28
	} else {
		err = errors.New("OneM2M_create: Invalid type")
		log.Error("send: ", err.Error())
		return sensorResp, err
	}

	err, resp := protocol.send(ctx)
	if err != nil {
		log.Error("OneM2M_create: ", err.Error())
		return sensorResp, err
	}
	log.Debug("OneM2M_create: resp: ", resp)
	log.Debug("OneM2M_create: TypeOf(resp): ", reflect.TypeOf(resp))
	if _, ok := resp.(map[string]interface{}); !ok {
		err = errors.New("Interface not available")
		log.Error("OneM2M_create: ", err.Error())
		return sensorResp, err
	}
	if val, ok := resp.(map[string]interface{})["m2m:dbg"]; ok {
		err = errors.New(val.(string))
		log.Error("OneM2M_create: ", err.Error())
		return sensorResp, err
	}

	// Add additional entries
	sensorResp.SensorIdentifier = sensor.SensorIdentifier
	sensorResp.SensorType = sensor.SensorType
	sensorResp.IotPlatformId = sensor.IotPlatformId
	sensorResp.SensorPosition = sensor.SensorPosition
	sensorResp.Flex = sensor.Flex
	sensorResp, err = tm.oneM2M_deserialize(sensorResp, resp.(map[string]interface{}))
	if err != nil {
		log.Error("OneM2M_create: ", err.Error())
		return sensorResp, err
	}
	log.Debug("OneM2M_create: sensorResp: ", sensorResp)

	if profiling {
		now := time.Now()
		log.Debug("OneM2M_create: ", now.Sub(profilingTimers["OneM2M_create"]))
	}

	return sensorResp, nil
}

func (tm *SssMgr) OneM2M_discovery(type_ string, iotPlatformId string) (sensorResp []SensorDiscoveryInfo, err error) {

	if profiling {
		profilingTimers["OneM2M_discovery"] = time.Now()
	}

	log.Info(">>> OneM2OneM2M_discovery_get: path: ", type_)
	log.Info(">>> OneM2M_discovery: iotPlatformId: ", iotPlatformId)

	if iotPlatformId == "" {
		err = errors.New("IotPlatformId fiels shall be set")
		log.Error("OneM2M_discovery: ", err.Error())
		return nil, err
	}

	tm.wg.Wait()
	log.Info("OneM2M_discovery: After Synchro")

	// 1. Get the list of the AE
	// Build the context
	var ctx = SssMgrBindingProtocolContext{
		host: registeredIotPlatformsMap[iotPlatformId].Address,
		port: registeredIotPlatformsMap[iotPlatformId].Port,
		name: registeredIotPlatformsMap[iotPlatformId].Name,
		to:   registeredIotPlatformsMap[iotPlatformId].Name,
		from: "Admin", // FIXME FSCOM How to get it
		op:   2,       // RETRIEVE
		ty:   -1,
		rqi:  uuid.New().String(),
		rvi:  []string{"4"}, // FIXME FSCOM How to get it
		code: 200,
	}
	// Build the queries parameters
	queries := map[string]string{}
	queries["fu"] = "1" // Filter usage
	if type_ == "AE" {
		ctx.ty = 2
		queries["ty"] = "2"
	} else if type_ == "CNT" {
		ctx.ty = 3
		queries["ty"] = "3"
	} else if type_ == "CIN" {
		ctx.ty = 4
		queries["ty"] = "4"
	} else {
		err = errors.New("OneM2M_discovery: Invalid type")
		log.Error("OneM2M_discovery: ", err.Error())
		return nil, err
	}
	ctx.queries = queries

	err, resp := protocol.send(ctx)
	if err != nil {
		log.Error("OneM2M_create: ", err.Error())
		return nil, err
	}
	log.Debug("OneM2M_discovery: resp: ", resp)
	log.Debug("OneM2M_discovery: TypeOf(resp): ", reflect.TypeOf(resp))
	oneM2M_uril := resp.(map[string]interface{})
	log.Debug("OneM2M_discovery: oneM2M_uril: ", oneM2M_uril)
	log.Debug("OneM2M_discovery: TypeOf(oneM2M_uril): ", reflect.TypeOf(oneM2M_uril))
	log.Debug("OneM2M_discovery: len(oneM2M_uril): ", len(oneM2M_uril))
	sensorResp = make([]SensorDiscoveryInfo, len(oneM2M_uril))
	// Loop for each CIN and build the sensor list
	sensorResp = make([]SensorDiscoveryInfo, 0)
	for _, v := range oneM2M_uril["m2m:uril"].([]interface{}) {
		log.Debug("OneM2M_discovery: Processing key: v: ", v)
		log.Debug("OneM2M_discovery: Processing key: TypeOf(v): ", reflect.TypeOf(v))
		s := v.(string)
		if s == "laboai-cse-in/acpCreateACPs" || s == "laboai-cse-in/CAdmin" {
			// FIXME FSCOM Bug in MQTT DISCOVERY request which does not provide the same response that HTTP DISCOVERY with the same filter criteria
			continue // Discard it
		}
		r, err := tm.OneM2M_get(v.(string), iotPlatformId)
		if err != nil {
			log.Error("OneM2M_discovery: ", err.Error())
			return nil, err
		}
		sensorResp = append(sensorResp, r)
	} // End of 'for' statement
	log.Debug("OneM2M_discovery: len(sensorResp): ", len(sensorResp))
	log.Debug("OneM2M_discovery: sensorResp: ", sensorResp)

	if profiling {
		now := time.Now()
		log.Debug("OneM2M_discovery: ", now.Sub(profilingTimers["OneM2M_discovery"]))
	}

	return sensorResp, nil
}

func (tm *SssMgr) OneM2M_get(path string, iotPlatformId string) (sensorResp SensorDiscoveryInfo, err error) {

	if profiling {
		profilingTimers["OneM2M_get"] = time.Now()
	}

	log.Info(">>> OneM2M_get: path: ", path)
	log.Info(">>> OneM2M_get: iotPlatformId: ", iotPlatformId)

	if path == "" || iotPlatformId == "" {
		err = errors.New("OneM2M_get: Invalid parameters")
		log.Error("OneM2M_get: ", err.Error())
		return sensorResp, err
	}

	tm.wg.Wait()
	log.Info("OneM2M_get: After Synchro")

	// 1. Get the list of the AE
	// Build the context
	var ctx = SssMgrBindingProtocolContext{
		host: registeredIotPlatformsMap[iotPlatformId].Address,
		port: registeredIotPlatformsMap[iotPlatformId].Port,
		name: registeredIotPlatformsMap[iotPlatformId].Name,
		to:   path,
		from: "Admin", // FIXME FSCOM How to get it
		op:   2,       // RETRIEVE
		ty:   -1,
		rqi:  uuid.New().String(),
		rvi:  []string{"4"}, // FIXME FSCOM How to get it
		code: 200,
	}

	err, resp := protocol.send(ctx)
	if err != nil {
		log.Error("OneM2M_get: ", err.Error())
		return sensorResp, err
	}
	log.Debug("OneM2M_get: resp: ", resp)
	log.Debug("OneM2M_get: TypeOf(resp): ", reflect.TypeOf(resp))
	if _, ok := resp.(map[string]interface{}); !ok {
		log.Error("OneM2M_get: Interface not available")
		return sensorResp, err
	}

	// Add additional entries
	sensorResp.IotPlatformId = iotPlatformId
	sensorResp, err = tm.oneM2M_deserialize(sensorResp, resp.(map[string]interface{}))

	if err != nil {
		log.Error("OneM2M_get: ", err.Error())
		return sensorResp, err
	}
	log.Debug("OneM2M_get: sensorResp: ", sensorResp)

	if profiling {
		now := time.Now()
		log.Debug("OneM2M_get: ", now.Sub(profilingTimers["OneM2M_get"]))
	}

	return sensorResp, nil
}

func (tm *SssMgr) OneM2M_subscribe(iotPlatformId string, path string) (subscription string, err error) {
	if profiling {
		profilingTimers["OneM2M_subscribe"] = time.Now()
	}

	log.Info(">>> OneM2M_subscribe: path: ", path)
	log.Info(">>> OneM2M_subscribe: iotPlatformId: ", iotPlatformId)

	if path == "" || iotPlatformId == "" {
		err = errors.New("OneM2M_subscribe: Invalid parameters")
		log.Error("OneM2M_subscribe: ", err.Error())
		return "", err
	}

	tm.wg.Wait()
	log.Info("OneM2M_subscribe: After Synchro")

	// 1. Get the list of the AE
	// Build the context
	var ctx = SssMgrBindingProtocolContext{
		host: registeredIotPlatformsMap[iotPlatformId].Address,
		port: registeredIotPlatformsMap[iotPlatformId].Port,
		name: registeredIotPlatformsMap[iotPlatformId].Name,
		to:   path,
		from: registeredIotPlatformsMap[iotPlatformId].IotPlatformId,
		op:   1, // SUBSCRIBE
		ty:   23,
		rqi:  uuid.New().String(),
		rvi:  []string{"4"}, // FIXME FSCOM How to get it
		code: 200,
	}
	var bodyMap = map[string]map[string]interface{}{}
	bodyMap["m2m:sub"] = make(map[string]interface{}, 0)
	net := make(map[string][]int)
	net["net"] = []int{1, 2, 3, 4}
	bodyMap["m2m:sub"]["enc"] = net
	if tm.bindingProtocol == "MQTT" {
		bodyMap["m2m:sub"]["nu"] = []string{"mqtt://172.29.10.56:1883"} // FIXME FSCOM The URI of the listener
	} else {
		bodyMap["m2m:sub"]["nu"] = []string{"http://172.29.10.52:31122/"} // FIXME FSCOM The URI of the listener
	}
	bodyMap["m2m:sub"]["rn"] = uuid.New().String()
	ctx.body = bodyMap

	err, resp := protocol.send(ctx)
	if err != nil {
		log.Error("OneM2M_subscribe: ", err.Error())
		return "", err
	}
	log.Debug("OneM2M_subscribe: resp: ", resp)
	log.Debug("OneM2M_subscribe: TypeOf(resp): ", reflect.TypeOf(resp))
	if _, ok := resp.(map[string]interface{}); !ok {
		log.Error("OneM2M_create: Interface not available")
		return "", err
	}

	if _, ok := resp.(map[string]interface{})["m2m:sub"].(map[string]interface{})["ri"]; !ok {
		err = errors.New("Subsciption identifier not present")
		log.Error("OneM2M_subscribe: ", err.Error())
		return "", err
	}
	subId := resp.(map[string]interface{})["m2m:sub"].(map[string]interface{})["ri"].(string)
	log.Debug("OneM2M_subscribe: subId: ", subId)

	// Add additional entries
	var v = SensorDiscoveryInfo{
		SensorIdentifier: subId,
		SensorType:       "SUB",
		IotPlatformId:    iotPlatformId,
	}
	v, err = tm.oneM2M_deserialize(v, resp.(map[string]interface{}))
	if err != nil {
		log.Error("OneM2M_subscribe: ", err.Error())
		return "", err
	}
	log.Debug("OneM2M_subscribe: v: ", v)

	subscriptionListPerSubId[subId] = v

	//map[m2m:sub:map[ct:20250327T065259,502192 enc:map[net:[1 2 3 4]] et:20300326T065259,314830 lt:20250327T065259,502192 nct:1 nu:[http://172.29.10.52:31122] pi:C7feaadbb0400 ri:sub415201783625924167 rn:e31e93f4-8514-4c83-8854-364eee1ad305 ty:23]]

	if profiling {
		now := time.Now()
		log.Debug("OneM2M_subscribe: ", now.Sub(profilingTimers["OneM2M_subscribe"]))
	}

	return subId, nil
}

func (tm *SssMgr) OneM2M_delete(sensor SensorDiscoveryInfo) (err error) {

	if profiling {
		profilingTimers["OneM2M_delete"] = time.Now()
	}

	log.Info(">>> OneM2M_delete: sensor=", sensor)

	if sensor.SensorIdentifier == "" {
		err = errors.New("OneM2M_delete: Cannot find \"ri\" value")
		log.Error("OneM2M_delete: ", err.Error())
		return err
	}

	if sensor.IotPlatformId == "" {
		err = errors.New("IotPlatformId fiels shall be set")
		log.Error("OneM2M_delete: ", err.Error())
		return err
	}

	tm.wg.Wait()
	log.Info("OneM2M_delete: After Synchro")

	// Send it and get the result
	var ctx = SssMgrBindingProtocolContext{
		host: registeredIotPlatformsMap[sensor.IotPlatformId].Address,
		port: registeredIotPlatformsMap[sensor.IotPlatformId].Port,
		name: registeredIotPlatformsMap[sensor.IotPlatformId].Name,
		to:   sensor.SensorIdentifier,
		from: sensor.IotPlatformId,
		op:   4, // DELETE
		ty:   -1,
		rqi:  uuid.New().String(),
		rvi:  []string{"4"}, // FIXME FSCOM How to get it
		code: 200,
	}
	err, _ = protocol.send(ctx)
	if err != nil {
		log.Error("OneM2M_delete: ", err.Error())
		return err
	}

	if profiling {
		now := time.Now()
		log.Debug("OneM2M_delete: ", now.Sub(profilingTimers["OneM2M_delete"]))
	}

	return nil
}

func (tm *SssMgr) OneM2M_delete_subscription(subId string) (err error) {

	if profiling {
		profilingTimers["OneM2M_delete_subscription"] = time.Now()
	}

	log.Info(">>> OneM2M_delete_subscription: sensor=", subId)

	if subId == "" {
		err = errors.New("subId fiels shall be set")
		log.Error("OneM2M_delete_subscription: ", err.Error())
		return err
	}

	if _, ok := subscriptionListPerSubId[subId]; !ok {
		err = errors.New("Unkmown subscription identifier")
		log.Error("OneM2M_delete_subscription: ", err.Error())
		return err
	}

	tm.wg.Wait()
	log.Info("OneM2M_delete_subscription: After Synchro")

	// Send it and get the result
	var ctx = SssMgrBindingProtocolContext{
		host: registeredIotPlatformsMap[subscriptionListPerSubId[subId].IotPlatformId].Address,
		port: registeredIotPlatformsMap[subscriptionListPerSubId[subId].IotPlatformId].Port,
		name: registeredIotPlatformsMap[subscriptionListPerSubId[subId].IotPlatformId].Name,
		to:   subscriptionListPerSubId[subId].SensorIdentifier,
		from: subscriptionListPerSubId[subId].IotPlatformId,
		op:   4, // DELETE
		ty:   -1,
		rqi:  uuid.New().String(),
		rvi:  []string{"4"}, // FIXME FSCOM How to get it
		code: 200,
	}
	err, _ = protocol.send(ctx)
	if err != nil {
		log.Error("OneM2M_delete_subscription: ", err.Error())
		return err
	}

	delete(subscriptionListPerSubId, subId)
	log.Info("OneM2M_delete_subscription: New subscriptionListPerSubId: ", subscriptionListPerSubId)

	if profiling {
		now := time.Now()
		log.Debug("OneM2M_delete_subscription: ", now.Sub(profilingTimers["OneM2M_delete_subscription"]))
	}

	return nil
}

func (tm *SssMgr) notify(sub map[string]interface{}) {
	log.Debug(">>> notify: sub: ", sub)

	// Sanity checks
	if _, ok := sub["nev"]; !ok {
		log.Warn("notify: nev entry is missing")
		return
	}
	if _, ok := sub["nev"].(map[string]interface{}); !ok {
		log.Warn("notify: nev entry has unexpected type")
		return
	}
	if _, ok := sub["nev"].(map[string]interface{}); !ok {
		log.Warn("notify: nev entry has an unexpected type")
		return
	}
	if _, ok := sub["sur"]; !ok {
		log.Warn("notify: sur entry is missing")
		return
	}

	// Process subscription
	s := strings.Split(sub["sur"].(string), "/")
	log.Debug("notify: s: ", s)
	log.Debug("notify: CSE-ID: ", s[1])
	log.Debug("notify: subURL: ", strings.Join(s[2:], "/"))

	rep := sub["nev"].(map[string]interface{})
	log.Debug("notify: rep: ", rep["rep"].(map[string]interface{}))
	if _, ok := rep["rep"]; !ok {
		log.Warn("notify: rep entry is missing")
		return
	}
	if _, ok := rep["rep"].(map[string]interface{}); !ok {
		log.Warn("notify: rep entry has unexpected type")
		return
	}
	r := rep["rep"].(map[string]interface{})
	log.Debug("notify: r: ", r)
	// FIXME FSCOM How to mange the 3 levels of subscriptions???
	if v, ok := r["m2m:ae"]; ok { // Discovery notification
		log.Debug("notify: AE level")
		if tm.sss_discovery_notify != nil {
			go func() {
				tm.sss_discovery_notify(v.(map[string]interface{}))
			}()
		}
		return
	} else if v, ok := r["m2m:cnt"]; ok { // Status notification
		log.Debug("notify: CNT level")
		if tm.sss_status_notify != nil {
			go func() {
				tm.sss_status_notify(v.(map[string]interface{}))
			}()
		}
		return
	} else if v, ok := r["m2m:cin"]; ok { // Data notification
		if tm.sss_data_notify != nil {
			go func() {
				tm.sss_data_notify(v.(map[string]interface{}))
			}()
		}
		log.Debug("notify: CIN level")
		return
	} else {
		log.Debug("notify: Unexpected level")
		return
	}
}

func (tm *SssMgr) oneM2M_deserialize(sensor SensorDiscoveryInfo, response map[string]interface{}) (sensorResp SensorDiscoveryInfo, err error) {
	log.Debug(">>> oneM2M_deserialize: sensor: ", sensor)
	log.Debug(">>> oneM2M_deserialize: response: ", response)

	sensorResp = sensor // Same data structure

	if sensor.SensorType == "FLX" { // Extract flex specific attributes first
		// Sanity checks
		if sensor.Flex == nil {
			err = errors.New("Wrong 'flex' parameter value")
			log.Error("oneM2M_deserialize: ", err.Error())
			return sensorResp, err
		}
		log.Debug("oneM2M_deserialize: Extract flex specific attributes first")
		sensorResp.Flex = make(map[string]interface{})
		s := sensor.Flex["type"].(string)
		log.Debug("oneM2M_deserialize: s=", s)
		log.Debug("oneM2M_deserialize: response[s]: ", response[s])
		for k, v := range sensor.Flex {
			log.Debug("oneM2M_deserialize: Processing k= ", k, ", v= ", v)
			if e, ok := response[s].(map[string]interface{})[k]; ok {
				log.Debug("oneM2M_deserialize: ", k, " is a flex attribute: ", v)
				sensorResp.Flex[k] = e
				delete(response[s].(map[string]interface{}), k)
			}
		} // End of 'for' statement
		sensorResp.Flex["type"] = s
		log.Debug("oneM2M_deserialize: sensorResp.Flex: ", sensorResp.Flex)
	}

	for i, m := range response {
		log.Debug("==> ", i, " value is ", m)
		if _, ok := m.(map[string]interface{}); !ok {
			// Skip it
			log.Warn("oneM2M_deserialize: m is not map[string]interface{}")
			continue
		}
		// m is a map[string]interface.
		// loop over keys and values in the map.
		for k, v := range m.(map[string]interface{}) {
			log.Debug(k, " value is ", v)
			log.Debug("oneM2M_deserialize: type(v): ", reflect.TypeOf(v))

			if k == "ri" {
				if item, ok := v.(string); ok {
					sensorResp.SensorIdentifier = item
				} else {
					log.Error("oneM2M_deserialize: Failed to process ", k)
				}
			} else if k == "ty" {
				if item, ok := v.(float64); ok {
					switch item {
					case 2:
						sensorResp.SensorType = "AE"
					case 3:
						sensorResp.SensorType = "CNT"
					case 4:
						sensorResp.SensorType = "CIN"
					default:
						sensorResp.SensorType = strconv.FormatFloat(item, 'f', -1, 64)
					}
				} else {
					log.Error("oneM2M_deserialize: Failed to process ", k)
				}
			} else {
				if item, ok := v.(string); ok {
					sensorResp.SensorCharacteristicList = append(
						sensorResp.SensorCharacteristicList,
						SensorCharacteristic{
							CharacteristicName:  k,
							CharacteristicValue: string(item),
						})
				} else if item, ok := v.(float64); ok {
					sensorResp.SensorCharacteristicList = append(
						sensorResp.SensorCharacteristicList,
						SensorCharacteristic{
							CharacteristicName:  k,
							CharacteristicValue: strconv.FormatFloat(item, 'f', -1, 64),
						})
				} else if item, ok := v.(int64); ok {
					sensorResp.SensorCharacteristicList = append(
						sensorResp.SensorCharacteristicList,
						SensorCharacteristic{
							CharacteristicName:  k,
							CharacteristicValue: strconv.FormatInt(item, 10),
						})
				} else if item, ok := v.(bool); ok {
					sensorResp.SensorCharacteristicList = append(
						sensorResp.SensorCharacteristicList,
						SensorCharacteristic{
							CharacteristicName:  k,
							CharacteristicValue: strconv.FormatBool(item),
						})
				} else if item, ok := v.([]string); ok {
					sensorResp.SensorCharacteristicList = append(
						sensorResp.SensorCharacteristicList,
						SensorCharacteristic{
							CharacteristicName:  k,
							CharacteristicValue: strings.Join(item, ","),
						})
				} else if item, ok := v.([]int64); ok {
					log.Warn("oneM2M_deserialize: Failed to convert list of int64 into string: ", item)
				} else if item, ok := v.(map[string]interface{}); ok {
					v, err := json.Marshal(item)
					if err == nil {
						sensorResp.SensorCharacteristicList = append(
							sensorResp.SensorCharacteristicList,
							SensorCharacteristic{
								CharacteristicName:  k,
								CharacteristicValue: string(v),
							})
					} else {
						log.Warn("oneM2M_deserialize: ", err.Error())
					}
				} else if item, ok := v.([]interface{}); ok {
					log.Debug("oneM2M_deserialize: Got []interface {} for ", k)
					log.Debug("oneM2M_deserialize: ValueOf ", reflect.ValueOf(item))
					v, err := json.Marshal(item)
					if err == nil {
						sensorResp.SensorCharacteristicList = append(
							sensorResp.SensorCharacteristicList,
							SensorCharacteristic{
								CharacteristicName:  k,
								CharacteristicValue: string(v),
							})
					} else {
						log.Warn("oneM2M_deserialize: ", err.Error())
					}
				} else {
					log.Warn("oneM2M_deserialize: Failed to process: ", k)
				}
			}
		} // End of 'for' loop

	} // End of 'for' loop
	log.Debug("oneM2M_deserialize: sensorResp: ", sensorResp)

	return sensorResp, nil
}
