package localserver

import (
	"context"
	"encoding/json"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/pion/webrtc/v3"
)

const (
	JOIN_VIDEO_CHANNEL          = "join_video_channel"
	LEAVE_VIDEO_CHANNEL         = "leave_video_channel"
	LIST_VIDEO_CHANNELS         = "list_video_channels"
	GET_VIDEO_CHANNELS          = "get_video_channels"
	GET_VIDEO_CHANNEL           = "get_video_channel"
	CREATE_VIDEO_CHANNEL        = "create_video_channel"
	DELETE_VIDEO_CHANNEL        = "delete_video_channel"
	EDIT_VIDEO_CHANNEL_TYPE     = "edit_video_channel_type"
	EDIT_VIDEO_CHANNEL_NAME     = "edit_video_channel_name"
	ADD_VIDEO_CHANNEL_MEMBERS   = "add_video_channel_members"
	REMOVE_VIDEO_CHANNEL_MEMBER = "remove_video_channel_member"
)

const (
	USER_JOINED_VIDEO_CHANNEL    = "user_joined_video_channel"
	USER_LEFT_VIDEO_CHANNEL      = "user_left_video_channel"
	VIDEO_CHANNNEL_NAME_EDITED   = "video_channel_name_edited"
	VIDEO_CHANNNEL_TYPE_EDITED   = "video_channel_type_edited"
	VIDEO_CHANNEL_MEMBER_REMOVED = "video_channel_member_removed"
	VIDEO_CHANNEL_MEMBER_ADDED   = "video_channel_member_added"
	ADDED_IN_VIDEO_CHANNEL       = "added_in_video_channel"
	REMOVED_FROM_VIDEO_CHANNEL   = "removed_from_video_channel"
)

type VideoChannelMember struct {
}

type VideoChannelConfig struct {
	ID               string `json:"id"`
	Owner            string `json:"owner"`
	ChannelType      string `json:"channelType"`
	CurrentMembersId []string
	Members          []string `json:"members"`
}

type ZoneVideoChannelsHandler struct {
	ZoneId                    string
	ZoneName                  string
	HostId                    string
	ZoneMembersId             []string
	DataChannels              map[string]*DataChannel
	VideoChanDataChannels     map[string]*DataChannel
	VideoChanDataChannelsFlag *uint32
	DataChannelsFlag          *uint32
	VideoChannelsFlag         *uint32
	VideoChannels             map[string]*VideoChannel
	reqChans                  []chan<- *ZoneRequest
}

const MEETING string = "meeting"

func NewZoneVideoChannelsHandler(hostId string, zoneId string, owner string, authorizedMembers []string, dataChannels map[string]*DataChannel, dataChannelFlag *uint32) (zoneVideoChannelsHandler *ZoneVideoChannelsHandler, err error) {
	var dirs []fs.DirEntry
	dirs, err = os.ReadDir(filepath.Join(dataPath, "data", "zones", zoneId, "videoChannels"))
	if err != nil {
		if os.IsNotExist(err) {
			logger.Printf("creating videoChannels directory for zone %s...\n", zoneId)
			mkdirErr := os.MkdirAll(filepath.Join(dataPath, "data", "zones", zoneId, "videoChannels", MEETING), 0700)
			if mkdirErr != nil {
				return nil, mkdirErr
			}
			file, ferr := os.Create(filepath.Join(dataPath, "data", "zones", zoneId, "videoChannels", MEETING, "videoChannelConfig.json"))
			defer func() {
				_ = file.Close()
			}()
			if ferr != nil {
				return nil, ferr
			}
			baseConfig := VideoChannelConfig{
				ID:               MEETING,
				Owner:            owner,
				ChannelType:      "public",
				CurrentMembersId: make([]string, 0),
				Members:          make([]string, 0),
			}
			bs, jsonErr := json.Marshal(baseConfig)
			if jsonErr != nil {
				return nil, jsonErr
			}
			if _, writeErr := file.WriteString(string(bs)); writeErr != nil {
				return nil, writeErr
			}
			dirs, err = os.ReadDir(filepath.Join(dataPath, "data", "zones", zoneId, "videoChannels"))
			if err != nil {
				return nil, err
			}
		} else {
			return
		}
	}
	videoChannels := make(map[string]*VideoChannel)
	for _, videoChannel := range dirs {
		if strings.HasPrefix(videoChannel.Name(), ".") {
			continue
		}
		var bs []byte
		bs, err = os.ReadFile(filepath.Join(dataPath, "data", "zones", zoneId, "videoChannels", videoChannel.Name(), "videoChannelConfig.json"))
		if err != nil {
			return nil, err
		}
		logger.Println(string(bs))
		var vcc VideoChannelConfig
		if err = json.Unmarshal(bs, &vcc); err != nil {
			return nil, err
		}
		logger.Println("videoChannels data :", vcc.ID, vcc.ChannelType, vcc.Owner, vcc.Members)
		vc := NewVideoChannel(vcc.ID, vcc.Owner, vcc.ChannelType, vcc.Members, make([]string, 0), make(map[string]*VideoChannelMember))
		videoChannels[vc.ID] = vc
	}
	videoChannelFlag := uint32(0)
	videoChanDCFlag := uint32(0)
	zoneVideoChannelsHandler = &ZoneVideoChannelsHandler{
		ZoneId:                    zoneId,
		HostId:                    hostId,
		ZoneMembersId:             authorizedMembers,
		DataChannels:              dataChannels,
		VideoChanDataChannels:     make(map[string]*DataChannel),
		VideoChanDataChannelsFlag: &videoChanDCFlag,
		DataChannelsFlag:          dataChannelFlag,
		VideoChannels:             videoChannels,
		VideoChannelsFlag:         &videoChannelFlag,
	}
	return
}

func (zvch *ZoneVideoChannelsHandler) sendZoneRequest(reqType string, from string, payload map[string]interface{}) {
	go func() {
		for _, rc := range zvch.reqChans {
			rc <- &ZoneRequest{
				ReqType: reqType,
				From:    from,
				Payload: payload,
			}
		}
	}()
}

func (zvch *ZoneVideoChannelsHandler) sendDataChannelMessage(reqType string, from string, to string, payload map[string]interface{}) (<-chan struct{}, <-chan error) {
	done, errCh := make(chan struct{}), make(chan error)
	go func() {
		if err := atomicallyExecute(zvch.VideoChanDataChannelsFlag, func() (err error) {
			if _, ok := zvch.VideoChanDataChannels[to]; ok {
				bs, jsonErr := json.Marshal(&ZoneResponse{
					Type:    reqType,
					From:    from,
					To:      to,
					Payload: payload,
				})
				if jsonErr != nil {
					return jsonErr
				}
				err = zvch.VideoChanDataChannels[to].DataChannel.SendText(string(bs))
			}
			return
		}); err != nil {
			errCh <- err
			return
		}
		done <- struct{}{}
	}()
	return done, errCh
}

func (zvch *ZoneVideoChannelsHandler) Init(ctx context.Context, authorizedMembers []string) (err error) {
	for _, member := range authorizedMembers {
		if serr := zvch.SetAllPublicVideoChannelForUser(member); serr != nil {
			logger.Println(serr)
		}
	}
	return
}

func (zvch *ZoneVideoChannelsHandler) Subscribe(ctx context.Context, publisher <-chan *ZoneRequest) (reqChan chan *ZoneRequest, done chan struct{}, errCh chan error) {
	reqChan, done, errCh = make(chan *ZoneRequest), make(chan struct{}), make(chan error)
	zvch.reqChans = append(zvch.reqChans, reqChan)
	go func() {
		for {
			select {
			case <-ctx.Done():
				done <- struct{}{}
				return
			case req := <-publisher:
				if err := zvch.handleZoneRequest(ctx, req); err != nil {
					errCh <- err
				}
			}
		}
	}()
	return
}

func (zvch *ZoneVideoChannelsHandler) signalCandidate(from string, to string, candidate *webrtc.ICECandidate) (err error) {
	d, e := zvch.sendDataChannelMessage(string(VIDEO_CHANNEL_WEBRTC_CANDIDATE), from, to, map[string]interface{}{
		"from":          from,
		"to":            to,
		"candidate":     candidate.ToJSON().Candidate,
		"sdpMid":        *candidate.ToJSON().SDPMid,
		"sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)),
	})
	select {
	case <-d:
	case err = <-e:
	}
	return
}

func (zvch *ZoneVideoChannelsHandler) GetVideoChannels(userId string, channelsId ...interface{}) (err error) {
	videoChannels := make([]*VideoChannel, 0, len(channelsId))
	for _, id := range channelsId {
		if _, ok := id.(string); !ok {
			err = fmt.Errorf("id of wrong type")
			return
		}
		logger.Println("videoChannel from get videoChannels", id.(string))
		_ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
			if _, ok := zvch.VideoChannels[id.(string)]; ok {
				logger.Println(zvch.VideoChannels[id.(string)])
				videoChannels = append(videoChannels, zvch.VideoChannels[id.(string)])
			}
			return
		})
	}
	done, e := zvch.sendDataChannelMessage("get_video_channels_response", "node", userId, map[string]interface{}{
		"videoChannels": videoChannels,
	})
	select {
	case <-done:
		fmt.Println("done")
		fmt.Println(videoChannels)
	case err = <-e:
	}
	return
}

func (zvch *ZoneVideoChannelsHandler) ListVideoChannels() (videoChannels []*VideoChannel, err error) {
	videoChannels = make([]*VideoChannel, 0)
	_ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
		for _, videoChannel := range zvch.VideoChannels {
			videoChannels = append(videoChannels, videoChannel)
		}
		return
	})
	return
}

func (zvch *ZoneVideoChannelsHandler) AddNewVideoChannel(channelName string, owner string, channelType string, members []interface{}) (err error) {
	if channelName == "" {
		err = fmt.Errorf("not a valid channel name provided")
		return
	}
	if _, ok := zvch.VideoChannels[channelName]; ok {
		err = fmt.Errorf("an video channel with this name already exist")
		return
	}
	mkdirErr := os.MkdirAll(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelName), 0700)
	if mkdirErr != nil {
		return mkdirErr
	}
	file, ferr := os.Create(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelName, "videoChannelConfig.json"))
	defer func() {
		_ = file.Close()
	}()
	if ferr != nil {
		return ferr
	}
	m := make([]string, 0, len(members))
	for _, member := range members {
		if mbr, ok := member.(string); ok && mbr != owner {
			m = append(m, mbr)
		}
	}
	baseConfig := &VideoChannelConfig{
		ID:          channelName,
		Owner:       owner,
		ChannelType: channelType,
		Members:     append(m, owner),
	}
	bs, jsonErr := json.Marshal(baseConfig)
	if jsonErr != nil {
		return jsonErr
	}
	if _, writeErr := file.WriteString(string(bs)); writeErr != nil {
		return writeErr
	}
	var vcc VideoChannelConfig
	if err = json.Unmarshal(bs, &vcc); err != nil {
		return err
	}
	vc := NewVideoChannel(vcc.ID, vcc.Owner, vcc.ChannelType, vcc.Members, make([]string, 0), make(map[string]*VideoChannelMember))
	_ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
		vc.CurrentMembers = make(map[string]*VideoChannelMember)
		zvch.VideoChannels[vc.ID] = vc
		return
	})
	newVideoChannelForMembers := func(members []string) (err error) {
		for _, member := range members {
			zvch.sendZoneRequest(ADD_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{
				"userId":    member,
				"channelId": channelName,
			})
			done, e := zvch.sendDataChannelMessage("get_video_channels_response", "node", member, map[string]interface{}{
				"videoChannels": []*VideoChannel{zvch.VideoChannels[channelName]},
			})
			select {
			case <-done:
			case err = <-e:
				return
			}
		}
		return
	}
	switch vc.ChannelType {
	case BROADCAST:
		fallthrough
	case PUBLIC:
		err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
			err = newVideoChannelForMembers(zvch.ZoneMembersId)
			return
		})
	case PRIVATE:
		err = newVideoChannelForMembers(vc.Members)
	}
	return
}

func (zvch *ZoneVideoChannelsHandler) DeleteVideoChannel(channelId string) (err error) {
	if err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
		defer delete(zvch.VideoChannels, channelId)
		if _, ok := zvch.VideoChannels[channelId]; !ok {
			err = fmt.Errorf("no corresponding video channel")
			return
		}
		removeKnownVideoChannels := func(members []string) (err error) {
			for _, member := range members {
				zvch.sendZoneRequest(REMOVE_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{
					"userId":    member,
					"channelId": channelId,
				})
				done, e := zvch.sendDataChannelMessage(REMOVED_FROM_VIDEO_CHANNEL, "node", member, map[string]interface{}{
					"channelId": channelId,
					"userId":    member,
				})
				select {
				case <-done:
					continue
				case err = <-e:
					return
				}
			}
			return
		}
		switch zvch.VideoChannels[channelId].ChannelType {
		case BROADCAST:
			fallthrough
		case PUBLIC:
			err = removeKnownVideoChannels(zvch.ZoneMembersId)
		case PRIVATE:
			err = removeKnownVideoChannels(zvch.VideoChannels[channelId].Members)
		}
		return
	}); err != nil {
		return
	}
	if err = os.RemoveAll(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId)); err != nil {
		return
	}
	return
}

func (zvch *ZoneVideoChannelsHandler) EditVideoChannelName(channelId string, newVideoChannelId string) (err error) {
	if _, ok := zvch.VideoChannels[channelId]; !ok {
		err = fmt.Errorf("no coresponding videoChannel")
		return
	}
	bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"))
	if err != nil {
		return
	}
	var videoChannelConfig VideoChannelConfig
	if err = json.Unmarshal(bs, &videoChannelConfig); err != nil {
		return
	}
	videoChannelConfig.ID = newVideoChannelId
	bs, err = json.Marshal(&videoChannelConfig)
	if err = os.Rename(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId), filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", newVideoChannelId)); err != nil {
		return
	}
	f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", newVideoChannelId, "videoChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
	defer func() {
		_ = f.Close()
	}()
	if err != nil {
		return err
	}
	if _, err = f.Write(bs); err != nil {
		return err
	}
	videoChannel := NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember))
	_ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
		defer delete(zvch.VideoChannels, channelId)
		zvch.VideoChannels[newVideoChannelId] = videoChannel
		updateKnownVideoChannels := func(members []string) (err error) {
			for _, member := range members {
				zvch.sendZoneRequest(ADD_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{
					"userId":    member,
					"channelId": newVideoChannelId,
				})
				zvch.sendZoneRequest(REMOVE_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{
					"userId":    member,
					"channelId": channelId,
				})
				done, e := zvch.sendDataChannelMessage(VIDEO_CHANNNEL_NAME_EDITED, "node", member, map[string]interface{}{
					"formerVideoChannelId": channelId,
					"newVideoChannelId":    newVideoChannelId,
				})
				select {
				case <-done:
				case channelErr := <-e:
					logger.Println(channelErr)
				}
			}
			return
		}
		switch videoChannel.ChannelType {
		case BROADCAST:
			fallthrough
		case PUBLIC:
			err = updateKnownVideoChannels(zvch.ZoneMembersId)
		case PRIVATE:
			err = updateKnownVideoChannels(videoChannel.Members)
		}
		return
	})
	return
}

func (zvch *ZoneVideoChannelsHandler) EditVideoChannelType(channelId string, channelType string) (err error) {
	if _, ok := zvch.VideoChannels[channelId]; !ok {
		err = fmt.Errorf("no coresponding videoChannel")
		return
	}
	bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"))
	if err != nil {
		return
	}
	var videoChannelConfig VideoChannelConfig
	if err = json.Unmarshal(bs, &videoChannelConfig); err != nil {
		return
	}
	videoChannelConfig.ChannelType = channelType
	bs, err = json.Marshal(&videoChannelConfig)
	f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
	defer func() {
		_ = f.Close()
	}()
	if err != nil {
		return err
	}
	if _, err = f.Write(bs); err != nil {
		return err
	}
	videoChannel := NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember))
	switch channelType {
	case BROADCAST:
		fallthrough
	case PUBLIC:
		var members = []string{}
		_ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
			members = append(members, zvch.ZoneMembersId...)
			return
		})
		for _, member := range zvch.ZoneMembersId {
			if pubErr := zvch.AddVideoChannelsMembers(channelId, []any{member}); pubErr != nil {
				logger.Println(pubErr)
			}
			zvch.sendDataChannelMessage(VIDEO_CHANNNEL_TYPE_EDITED, "node", member, map[string]interface{}{
				"channelId":   channelId,
				"channelType": channelType,
			})
		}
	case PRIVATE:
		for _, member := range zvch.ZoneMembersId {
			zvch.sendDataChannelMessage(VIDEO_CHANNNEL_TYPE_EDITED, "node", member, map[string]interface{}{
				"channelId":   channelId,
				"channelType": channelType,
			})
		}
	}
	_ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
		zvch.VideoChannels[channelId] = videoChannel
		return
	})
	return
}

func (zvch *ZoneVideoChannelsHandler) AddVideoChannelsMembers(channelId string, members []interface{}) (err error) {
	if _, ok := zvch.VideoChannels[channelId]; !ok {
		err = fmt.Errorf("no coresponding videoChannel")
		return
	}
	membersStr, err := ToStringSlice(members)
	if err != nil {
		return
	}
	bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"))
	if err != nil {
		return
	}
	var videoChannelConfig VideoChannelConfig
	if err = json.Unmarshal(bs, &videoChannelConfig); err != nil {
		return
	}
	logger.Printf("%s - %s - %s -%v\n", videoChannelConfig.ID, videoChannelConfig.ChannelType, videoChannelConfig.Owner, members)
	addedMembers := make([]string, 0)
memberLoop:
	for _, videoChannelMember := range membersStr {
		logger.Println("entering broadcast loop")

		for _, member := range videoChannelConfig.Members {
			if member == videoChannelMember {
				continue memberLoop
			}
		}
		videoChannelConfig.Members = append(videoChannelConfig.Members, videoChannelMember)
		addedMembers = append(addedMembers, videoChannelMember)
		logger.Println("sending zone request", ADD_KNOWN_VIDEO_CHANNEL)
		zvch.sendZoneRequest(ADD_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{
			"userId":    videoChannelMember,
			"channelId": channelId,
		})
		logger.Println("--------------done")
	}

	bs, err = json.Marshal(&videoChannelConfig)
	if err != nil {
		return
	}
	f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
	defer func() {
		_ = f.Close()
	}()
	if err != nil {
		return err
	}
	if _, err = f.Write(bs); err != nil {
		return err
	}
	vc := NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember))
	_ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
		zvch.VideoChannels[channelId] = vc
		return
	})
	for _, member := range vc.Members {
		for _, m := range addedMembers {
			if member == m {
				if m != videoChannelConfig.Owner {
					go func() {
						bs, err = json.Marshal(map[string]any{
							"zoneId":    zvch.ZoneId,
							"channelId": channelId,
							"zoneHost":  NodeID,
						})
						if err != nil {
							return
						}
						zvch.sendZoneRequest(CREATE_NOTIFICATION, "node", map[string]interface{}{
							"type":       "added_in_video_channel",
							"title":      "Added in video channel 🎥",
							"body":       fmt.Sprintf("Added in channel %s in zone %s", channelId, zvch.ZoneName),
							"isPushed":   true,
							"payload":    string(bs),
							"recipients": []string{m},
						})
					}()
				}
				done, e := zvch.sendDataChannelMessage(ADDED_IN_VIDEO_CHANNEL, "node", member, map[string]interface{}{
					"videoChannel": vc,
				})
				select {
				case <-done:
				case err = <-e:
					logger.Println(err)
				}
			} else if _, ok := zvch.DataChannels[member]; ok {
				done, e := zvch.sendDataChannelMessage(VIDEO_CHANNEL_MEMBER_ADDED, "node", member, map[string]interface{}{
					"userId":    m,
					"channelId": vc.ID,
				})
				select {
				case <-done:
				case err = <-e:
					logger.Println(err)
				}
			}
		}
	}
	return
}

func (zvch *ZoneVideoChannelsHandler) RemoveVideoChannelMember(channelId string, channelMember string) (err error) {
	if _, ok := zvch.VideoChannels[channelId]; !ok {
		err = fmt.Errorf("no coresponding videoChannel")
		return
	}
	bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"))
	if err != nil {
		return
	}
	var videoChannelConfig VideoChannelConfig
	if err = json.Unmarshal(bs, &videoChannelConfig); err != nil {
		logger.Println(string(bs))
		logger.Println("json error right here")
		return
	}
	if channelMember == videoChannelConfig.Owner {
		err = fmt.Errorf("you cannot remove the owner from the videoChannel")
		return
	}
	var index int
	var contain bool
	for i, member := range videoChannelConfig.Members {
		if member == channelMember {
			index = i
			contain = true
			break
		}
	}
	if !contain {
		err = fmt.Errorf("member %s not in the channel %s", channelMember, channelId)
		return
	}
	videoChannelConfig.Members = append(videoChannelConfig.Members[:index], videoChannelConfig.Members[index+1:]...)

	bs, err = json.Marshal(&videoChannelConfig)
	if err != nil {
		logger.Println("json error there")
		return
	}
	f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
	defer func() {
		_ = f.Close()
	}()
	if err != nil {
		return
	}
	logger.Println(string(bs))
	if _, err = f.Write(bs); err != nil {
		return
	}
	var vc *VideoChannel
	_ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
		vc = NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember))
		zvch.VideoChannels[channelId] = vc
		return
	})
	zvch.sendZoneRequest(REMOVE_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{
		"userId":    channelMember,
		"channelId": channelId,
	})
	if channelMember != videoChannelConfig.Owner {
		bs, err = json.Marshal(map[string]any{
			"zoneId":    zvch.ZoneId,
			"channelId": channelId,
			"zoneHost":  NodeID,
		})
		if err != nil {
			return err
		}
		zvch.sendZoneRequest(CREATE_NOTIFICATION, "node", map[string]interface{}{
			"type":       "removed_from_video_channel",
			"title":      "Removed from video channel ⛔️",
			"body":       fmt.Sprintf("You have no longer access to the channel %s in zone %s", channelId, zvch.ZoneName),
			"isPushed":   true,
			"payload":    string(bs),
			"recipients": []string{channelMember},
		})
	}
	done, e := zvch.sendDataChannelMessage(REMOVED_FROM_VIDEO_CHANNEL, "node", channelMember, map[string]interface{}{
		"channelId": channelId,
		"userId":    channelMember,
	})
	select {
	case <-done:
	case err = <-e:
		logger.Println(err)
	}
broadcastLoop:
	for _, member := range vc.Members {
		if member == channelMember {
			continue broadcastLoop
		}
		done, e := zvch.sendDataChannelMessage(VIDEO_CHANNEL_MEMBER_REMOVED, "node", member, map[string]interface{}{
			"userId":    channelMember,
			"channelId": vc.ID,
		})
		select {
		case <-done:
		case err = <-e:
			logger.Println(err)
		}
	}

	return
}

func (zvch *ZoneVideoChannelsHandler) SetAllPublicVideoChannelForUser(userId string) (err error) {
	videoChannels, err := zvch.ListVideoChannels()
	if err != nil {
		return
	}
	for _, videoChannel := range videoChannels {
		if videoChannel.ChannelType == PUBLIC || videoChannel.ChannelType == BROADCAST {
			if err = zvch.AddVideoChannelsMembers(videoChannel.ID, []interface{}{userId}); err != nil {
				logger.Println(err)
			}
			continue
		}
	}
	return
}

func (zvch *ZoneVideoChannelsHandler) RemoveUserFromAllVideoChannels(userId string) (err error) {
	videoChannels, err := zvch.ListVideoChannels()
	if err != nil {
		return
	}
	for _, videoChannel := range videoChannels {
		if err = zvch.RemoveVideoChannelMember(videoChannel.ID, userId); err != nil {
			continue
		}
	}
	return
}

func (zvch *ZoneVideoChannelsHandler) RemoveAllUserVideoChannels(userId string) (err error) {
	videoChannels, err := zvch.ListVideoChannels()
	if err != nil {
		return
	}
	for _, videoChannel := range videoChannels {
		if videoChannel.Owner == userId {
			if derr := zvch.DeleteVideoChannel(videoChannel.ID); derr != nil {
				logger.Println("**************", derr)
			}
		}
	}
	return
}

func (zvch *ZoneVideoChannelsHandler) JoinVideoChannel(channelId string, userId string, sdp string) (err error) {
	err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
		if _, ok := zvch.VideoChannels[channelId]; !ok {
			err = fmt.Errorf("no video channel with corresponding id")
			return
		}
		videoChannel := zvch.VideoChannels[channelId]
		videoChannel.CurrentMembersId = append(videoChannel.CurrentMembersId, userId)
		videoChannel.CurrentMembers[userId] = &VideoChannelMember{}
		signalMembers := func(members []string) {
			for _, u := range members {
				done, e := zvch.sendDataChannelMessage(USER_JOINED_VIDEO_CHANNEL, "node", u, map[string]interface{}{
					"userId":    userId,
					"channelId": channelId,
				})
				select {
				case <-done:
				case err := <-e:
					logger.Println(err)
				}
			}
		}
		if videoChannel.ChannelType == PRIVATE {
			signalMembers(videoChannel.Members)
		} else {
			signalMembers(zvch.ZoneMembersId)
		}
		d, e := videoChannel.HandleOffer(context.Background(), channelId, userId, sdp, zvch.HostId, zvch.sendDataChannelMessage, zvch.signalCandidate)
		select {
		case <-d:
		case err = <-e:
		}
		return
	})
	return
}

func (zvch *ZoneVideoChannelsHandler) LeaveVideoChannel(channelId string, userId string) (err error) {
	err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
		if _, ok := zvch.VideoChannels[channelId]; !ok {
			err = fmt.Errorf("no video channel with corresponding id")
			return
		}
		videoChannel := zvch.VideoChannels[channelId]
		var index int
		var contain bool
		for i, v := range videoChannel.CurrentMembersId {
			if v == userId {
				index = i
				contain = true
			}
		}
		if !contain {
			err = fmt.Errorf("this channel does not contain the provided user Id")
			return
		}
		defer videoChannel.HandleLeavingMember(userId)
		if len(videoChannel.CurrentMembersId) <= 1 {
			videoChannel.CurrentMembersId = make([]string, 0)
		} else {
			videoChannel.CurrentMembersId = append(videoChannel.CurrentMembersId[:index], videoChannel.CurrentMembersId[index+1:]...)
		}
		delete(videoChannel.CurrentMembers, userId)
		signalMembers := func(members []string) {
			for _, u := range members {
				done, e := zvch.sendDataChannelMessage(USER_LEFT_VIDEO_CHANNEL, "node", u, map[string]interface{}{
					"userId":    userId,
					"channelId": channelId,
				})
				select {
				case <-done:
				case err := <-e:
					logger.Println(err)
				}
			}
		}
		if videoChannel.ChannelType == PRIVATE {
			signalMembers(videoChannel.Members)
		} else {
			signalMembers(zvch.ZoneMembersId)
		}
		return
	})
	return
}

func (zvch *ZoneVideoChannelsHandler) handleDataChannel(ctx context.Context, dc *DataChannel) (catched bool) {
	var label string
	_ = atomicallyExecute(dc.l, func() (err error) {
		label = dc.DataChannel.Label()
		return
	})
	if strings.Contains(label, "video_channel_data") {
		command := strings.Split(label, "|")
		catched = true
		_ = atomicallyExecute(zvch.VideoChanDataChannelsFlag, func() (err error) {
			zvch.VideoChanDataChannels[command[1]] = dc
			return
		})
		dc.DataChannel.OnClose(func() {
			fmt.Println("closing gratefully video channel dc...")
			_ = atomicallyExecute(zvch.VideoChanDataChannelsFlag, func() (err error) {
				delete(zvch.VideoChanDataChannels, command[1])
				fmt.Println("dc closed gracefully...")
				return
			})
		})
		dc.DataChannel.OnError(func(err error) {
			fmt.Println("error in video channel dc...")
			_ = atomicallyExecute(zvch.VideoChanDataChannelsFlag, func() (err error) {
				delete(zvch.VideoChanDataChannels, command[1])
				fmt.Println("dc closed on error...")
				return
			})
		})
	}
	return
}

func (zvch *ZoneVideoChannelsHandler) handleZoneRequest(ctx context.Context, req *ZoneRequest) (err error) {
	switch req.ReqType {
	case LEAVE_ZONE:
		logger.Println("*-----------------handling leaving zone---------------*")
		if err = verifyFieldsString(req.Payload, "userId"); err != nil {
			return
		}
		for _, vc := range zvch.VideoChannels {
			var contain bool
			var id string
			for _, member := range vc.CurrentMembersId {
				if member == req.Payload["userId"].(string) {
					id = member
					contain = true
					logger.Printf("*------------------id is %s--------------*\n", id)
					break
				}
			}
			if contain {
				err = zvch.LeaveVideoChannel(vc.ID, id)
				break
			}
		}
	case string(REMOVED_ZONE_AUTHORIZED_MEMBER):
		if err = verifyFieldsString(req.Payload, "userId"); err != nil {
			return
		}
		var index int
		var found bool
		for i, m := range zvch.ZoneMembersId {
			if m == req.Payload["userId"].(string) {
				index = i
				found = true
				break
			}
		}
		if !found {
			err = fmt.Errorf("no such member in zone")
			return
		}
		zvch.ZoneMembersId = append(zvch.ZoneMembersId[:index], zvch.ZoneMembersId[index+1:]...)
		if err = zvch.RemoveAllUserVideoChannels(req.Payload["userId"].(string)); err != nil {
			logger.Println("****______________", err)
		}
		err = zvch.RemoveUserFromAllVideoChannels(req.Payload["userId"].(string))
	case string(NEW_AUTHORIZED_ZONE_MEMBER):
		if err = verifyFieldsString(req.Payload, "userId"); err != nil {
			return
		}
		var contain bool
		for _, m := range zvch.ZoneMembersId {
			if m == req.Payload["userId"].(string) {
				contain = true
				break
			}
		}
		if !contain {
			zvch.ZoneMembersId = append(zvch.ZoneMembersId, req.Payload["userId"].(string))
		}
		err = zvch.SetAllPublicVideoChannelForUser(req.Payload["userId"].(string))
	case JOIN_VIDEO_CHANNEL:
		if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil {
			return
		}
		err = zvch.JoinVideoChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string), req.Payload["sdp"].(string))
	case LEAVE_VIDEO_CHANNEL:
		if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil {
			return
		}
		err = zvch.LeaveVideoChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string))
	case ADD_VIDEO_CHANNEL_MEMBERS:
		if err = verifyFieldsString(req.Payload, "channelId"); err != nil {
			return
		}
		if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil {
			return
		}
		err = zvch.AddVideoChannelsMembers(req.Payload["channelId"].(string), req.Payload["members"].([]interface{}))
	case REMOVE_VIDEO_CHANNEL_MEMBER:
		if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil {
			return
		}
		err = zvch.RemoveVideoChannelMember(req.Payload["channelId"].(string), req.Payload["userId"].(string))
		if err != nil {
			logger.Println("an error occured in add known videoChannel", err)
			return
		}
	case EDIT_VIDEO_CHANNEL_NAME:
		if err = verifyFieldsString(req.Payload, "channelId", "newVideoChannelId"); err != nil {
			return
		}
		if err = zvch.EditVideoChannelName(req.Payload["channelId"].(string), req.Payload["newVideoChannelId"].(string)); err != nil {
			return
		}
	case EDIT_VIDEO_CHANNEL_TYPE:
		if err = verifyFieldsString(req.Payload, "channelId", "channelType"); err != nil {
			return
		}
		if err = zvch.EditVideoChannelType(req.Payload["channelId"].(string), req.Payload["channelType"].(string)); err != nil {
			return
		}
	case DELETE_VIDEO_CHANNEL:
		if err = verifyFieldsString(req.Payload, "channelId"); err != nil {
			return
		}
		err = zvch.DeleteVideoChannel(req.Payload["channelId"].(string))
	case CREATE_VIDEO_CHANNEL:
		if err = verifyFieldsString(req.Payload, "channelId", "owner", "channelType"); err != nil {
			return
		}
		if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil {
			return
		}
		err = zvch.AddNewVideoChannel(req.Payload["channelId"].(string), req.Payload["owner"].(string), req.Payload["channelType"].(string), req.Payload["members"].([]interface{}))
	case GET_VIDEO_CHANNELS:
		if err = verifyFieldsSliceInterface(req.Payload, "channelsId"); err != nil {
			return
		}
		err = zvch.GetVideoChannels(req.From, req.Payload["channelsId"].([]interface{})...)
	case string(VIDEO_CHANNEL_WEBRTC_COUNTER_OFFER):
		logger.Println("handling video channel counter offer")
		if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil {
			return
		}
		err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
			if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok {
				err = fmt.Errorf("no channel corresponging the one requested")
				return
			}
			err = zvch.VideoChannels[req.Payload["channelId"].(string)].HandleCounterOffer(ctx, req.Payload["userId"].(string), zvch.sendDataChannelMessage)
			return
		})
	case string(VIDEO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER):
		if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil {
			return
		}
		err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
			if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok {
				err = fmt.Errorf("no channel corresponging the one requested")
				return
			}
			err = zvch.VideoChannels[req.Payload["channelId"].(string)].HandleRennegotiationOffer(req.Payload["userId"].(string), req.Payload["sdp"].(string), zvch.sendDataChannelMessage)
			return
		})
	case string(VIDEO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER):
		if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil {
			return
		}
		err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
			if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok {
				err = fmt.Errorf("no channel corresponging the one requested")
				return
			}
			err = zvch.VideoChannels[req.Payload["channelId"].(string)].HandleRennegotiationAnswer(req.Payload["userId"].(string), req.Payload["sdp"].(string))
			return
		})
	case string(VIDEO_CHANNEL_WEBRTC_CANDIDATE):
		logger.Println("handling video channel webrtc candidate")
		logger.Println(req.Payload)
		if err = verifyFieldsString(req.Payload, FROM, "candidate", "sdpMLineIndex", "sdpMid", "channelId", "userId"); err != nil {
			return
		}
		logger.Println(req.Payload)
		i, convErr := strconv.Atoi(req.Payload["sdpMLineIndex"].(string))
		if convErr != nil {
			return convErr
		}
		SDPMLineIndex := uint16(i)
		sdpMid := req.Payload["sdpMid"].(string)
		logger.Println(sdpMid, SDPMLineIndex)
		err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
			if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok {
				err = fmt.Errorf("no channel corresponging the one requested")
				return
			}
			err = zvch.VideoChannels[req.Payload["channelId"].(string)].AddCandidate(&webrtc.ICECandidateInit{
				Candidate:     req.Payload["candidate"].(string),
				SDPMid:        &sdpMid,
				SDPMLineIndex: &SDPMLineIndex,
			}, req.Payload["userId"].(string))
			return
		})
	default:
		logger.Println("video channel handler still in process of implementation")
	}
	return
}
