package localserver

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

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

const (
	JOIN_AUDIO_CHANNEL          = "join_audio_channel"
	LEAVE_AUDIO_CHANNEL         = "leave_audio_channel"
	LIST_AUDIO_CHANNELS         = "list_audio_channels"
	GET_AUDIO_CHANNELS          = "get_audio_channels"
	GET_AUDIO_CHANNEL           = "get_audio_channel"
	CREATE_AUDIO_CHANNEL        = "create_audio_channel"
	DELETE_AUDIO_CHANNEL        = "delete_audio_channel"
	EDIT_AUDIO_CHANNEL_TYPE     = "edit_audio_channel_type"
	EDIT_AUDIO_CHANNEL_NAME     = "edit_audio_channel_name"
	ADD_AUDIO_CHANNEL_MEMBERS   = "add_audio_channel_members"
	REMOVE_AUDIO_CHANNEL_MEMBER = "remove_audio_channel_member"
)

const (
	USER_JOINED_AUDIO_CHANNEL    = "user_joined_audio_channel"
	USER_LEFT_AUDIO_CHANNEL      = "user_left_audio_channel"
	AUDIO_CHANNNEL_NAME_EDITED   = "audio_channel_name_edited"
	AUDIO_CHANNNEL_TYPE_EDITED   = "audio_channel_type_edited"
	AUDIO_CHANNEL_MEMBER_REMOVED = "audio_channel_member_removed"
	AUDIO_CHANNEL_MEMBER_ADDED   = "audio_channel_member_added"
	ADDED_IN_AUDIO_CHANNEL       = "added_in_audio_channel"
	REMOVED_FROM_AUDIO_CHANNEL   = "removed_from_audio_channel"
)

type AudioChannelMember struct {
}

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

type ZoneAudioChannelsHandler struct {
	HostId                    string
	ZoneName                  string
	ZoneId                    string
	ZoneMembersId             []string
	DataChannels              map[string]*DataChannel
	AudioChanDataChannels     map[string]*DataChannel
	AudioChanDataChannelsFlag *uint32
	DataChannelsFlag          *uint32
	AudioChannelsFlag         *uint32
	AudioChannels             map[string]*AudioChannel
	reqChans                  []chan<- *ZoneRequest
}

const LOBBY string = "lobby"

func NewZoneAudioChannelsHandler(hostId string, zoneId string, owner string, authorizedMembers []string, dataChannels map[string]*DataChannel, dataChannelFlag *uint32) (zoneAudioChannelsHandler *ZoneAudioChannelsHandler, err error) {
	var dirs []fs.DirEntry
	dirs, err = os.ReadDir(filepath.Join(dataPath, "data", "zones", zoneId, "audioChannels"))
	if err != nil {
		if os.IsNotExist(err) {
			logger.Printf("creating audioChannels directory for zone %s...\n", zoneId)
			mkdirErr := os.MkdirAll(filepath.Join(dataPath, "data", "zones", zoneId, "audioChannels", LOBBY), 0700)
			if mkdirErr != nil {
				return nil, mkdirErr
			}
			file, ferr := os.Create(filepath.Join(dataPath, "data", "zones", zoneId, "audioChannels", LOBBY, "audioChannelConfig.json"))
			if ferr != nil {
				return nil, ferr
			}
			baseConfig := AudioChannelConfig{
				ID:               LOBBY,
				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
			}
			_ = file.Close()
			dirs, err = os.ReadDir(filepath.Join(dataPath, "data", "zones", zoneId, "audioChannels"))
			if err != nil {
				return nil, err
			}
		} else {
			return
		}
	}
	audioChannels := make(map[string]*AudioChannel)
	for _, audioChannel := range dirs {
		if strings.HasPrefix(audioChannel.Name(), ".") {
			continue
		}
		var bs []byte
		bs, err = os.ReadFile(filepath.Join(dataPath, "data", "zones", zoneId, "audioChannels", audioChannel.Name(), "audioChannelConfig.json"))
		if err != nil {
			return nil, err
		}
		logger.Println(string(bs))
		var acc AudioChannelConfig
		if err = json.Unmarshal(bs, &acc); err != nil {
			return nil, err
		}
		logger.Println("audioChannels data :", acc.ID, acc.ChannelType, acc.Owner, acc.Members)
		ac := NewAudioChannel(acc.ID, acc.Owner, acc.ChannelType, acc.Members, make([]string, 0), make(map[string]*AudioChannelMember))
		audioChannels[acc.ID] = ac
	}
	audioChannelFlag := uint32(0)
	audioDCFlag := uint32(0)
	zoneAudioChannelsHandler = &ZoneAudioChannelsHandler{
		HostId:                    hostId,
		ZoneId:                    zoneId,
		ZoneMembersId:             authorizedMembers,
		DataChannels:              dataChannels,
		AudioChanDataChannels:     make(map[string]*DataChannel),
		AudioChanDataChannelsFlag: &audioDCFlag,
		DataChannelsFlag:          dataChannelFlag,
		AudioChannels:             audioChannels,
		AudioChannelsFlag:         &audioChannelFlag,
	}
	return
}

type SendDCMessageFunc = func(reqType string, from string, to string, payload map[string]interface{}) (<-chan struct{}, <-chan error)

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

func (zach *ZoneAudioChannelsHandler) 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(zach.AudioChanDataChannelsFlag, func() (err error) {
			if _, ok := zach.AudioChanDataChannels[to]; ok {
				bs, jsonErr := json.Marshal(&ZoneResponse{
					Type:    reqType,
					From:    zach.HostId,
					To:      to,
					Payload: payload,
				})
				if jsonErr != nil {
					return jsonErr
				}
				<-time.After(20 * time.Millisecond)
				err = zach.AudioChanDataChannels[to].DataChannel.SendText(string(bs))
			}
			return
		}); err != nil {
			errCh <- err
			return
		}
		done <- struct{}{}
	}()
	return done, errCh
}

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

func (zach *ZoneAudioChannelsHandler) 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)
	zach.reqChans = append(zach.reqChans, reqChan)
	go func() {
		for {
			select {
			case <-ctx.Done():
				done <- struct{}{}
				return
			case req := <-publisher:
				if err := zach.handleZoneRequest(ctx, req); err != nil {
					errCh <- err
				}
			}
		}
	}()
	return
}

func (zach *ZoneAudioChannelsHandler) signalCandidate(from string, to string, candidate *webrtc.ICECandidate) (err error) {
	d, e := zach.sendDataChannelMessage(string(AUDIO_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 (zach *ZoneAudioChannelsHandler) GetAudioChannels(userId string, channelsId ...interface{}) (err error) {
	audioChannels := make([]*AudioChannel, 0, len(channelsId))
	for _, id := range channelsId {
		if _, ok := id.(string); !ok {
			err = fmt.Errorf("id of wrong type")
			return
		}
		logger.Println("audioChannel from get audioChannels", id.(string))
		_ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
			if _, ok := zach.AudioChannels[id.(string)]; ok {
				logger.Println(zach.AudioChannels[id.(string)])
				audioChannels = append(audioChannels, zach.AudioChannels[id.(string)])
			}
			return
		})
	}
	done, e := zach.sendDataChannelMessage("get_audio_channels_response", "node", userId, map[string]interface{}{
		"audioChannels": audioChannels,
	})
	select {
	case <-done:
		fmt.Println("done")
		fmt.Println(audioChannels)
	case err = <-e:
	}
	return
}

func (zach *ZoneAudioChannelsHandler) ListAudioChannels() (audioChannels []*AudioChannel, err error) {
	audioChannels = make([]*AudioChannel, 0)
	err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
		for _, audioChannel := range zach.AudioChannels {
			audioChannels = append(audioChannels, audioChannel)
		}
		return
	})
	return
}

func (zach *ZoneAudioChannelsHandler) AddNewAudioChannel(channelName string, owner string, channelType string, members []interface{}) (err error) {
	if channelName == "" {
		err = fmt.Errorf("not a valid channel name provided")
		return
	}
	if _, ok := zach.AudioChannels[channelName]; ok {
		err = fmt.Errorf("an audio channel with this name already exist")
		return
	}
	mkdirErr := os.MkdirAll(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", channelName), 0700)
	if mkdirErr != nil {
		return mkdirErr
	}
	file, ferr := os.Create(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", channelName, "audioChannelConfig.json"))
	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 := &AudioChannelConfig{
		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
	}
	err = file.Close()
	if err != nil {
		return err
	}
	var acc AudioChannelConfig
	if err = json.Unmarshal(bs, &acc); err != nil {
		return err
	}
	ac := NewAudioChannel(acc.ID, acc.Owner, acc.ChannelType, acc.Members, acc.CurrentMembersId, make(map[string]*AudioChannelMember))
	_ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
		zach.AudioChannels[ac.ID] = ac
		return
	})
	newAudioChannelForMembers := func(members []string) (err error) {
		for _, member := range members {
			zach.sendZoneRequest(ADD_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{
				"userId":    member,
				"channelId": channelName,
			})
			done, e := zach.sendDataChannelMessage("get_audio_channels_response", "node", member, map[string]interface{}{
				"audioChannels": []*AudioChannel{zach.AudioChannels[channelName]},
			})
			select {
			case <-done:
			case err = <-e:
				return
			}
		}
		return
	}
	switch ac.ChannelType {
	case BROADCAST:
		fallthrough
	case PUBLIC:
		err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
			err = newAudioChannelForMembers(zach.ZoneMembersId)
			return
		})
	case PRIVATE:
		err = newAudioChannelForMembers(ac.Members)
	}
	return
}

func (zach *ZoneAudioChannelsHandler) DeleteAudioChannel(channelId string) (err error) {
	if err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
		defer delete(zach.AudioChannels, channelId)
		if _, ok := zach.AudioChannels[channelId]; !ok {
			err = fmt.Errorf("no corresponding audio channel")
			return
		}
		removeKnownAudioChannels := func(members []string) (err error) {
			for _, member := range members {
				zach.sendZoneRequest(REMOVE_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{
					"userId":    member,
					"channelId": channelId,
				})
				done, e := zach.sendDataChannelMessage(REMOVED_FROM_AUDIO_CHANNEL, "node", member, map[string]interface{}{
					"channelId": channelId,
					"userId":    member,
				})
				select {
				case <-done:
					continue
				case err = <-e:
					return
				}
			}
			return
		}
		switch zach.AudioChannels[channelId].ChannelType {
		case BROADCAST:
			fallthrough
		case PUBLIC:
			err = removeKnownAudioChannels(zach.ZoneMembersId)
		case PRIVATE:
			err = removeKnownAudioChannels(zach.AudioChannels[channelId].Members)
		}
		return
	}); err != nil {
		return
	}
	if err = os.RemoveAll(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", channelId)); err != nil {
		return
	}
	return
}

func (zach *ZoneAudioChannelsHandler) EditAudioChannelName(channelId string, newAudioChannelId string) (err error) {
	if _, ok := zach.AudioChannels[channelId]; !ok {
		err = fmt.Errorf("no coresponding audioChannel")
		return
	}
	bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.json"))
	if err != nil {
		return
	}
	var audioChannelConfig AudioChannelConfig
	if err = json.Unmarshal(bs, &audioChannelConfig); err != nil {
		return
	}
	audioChannelConfig.ID = newAudioChannelId
	bs, err = json.Marshal(&audioChannelConfig)
	if err = os.Rename(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", channelId), filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", newAudioChannelId)); err != nil {
		return
	}
	f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", newAudioChannelId, "audioChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
	defer func() {
		if e := f.Close(); e != nil {
			logger.Println(e)
		}
	}()
	if err != nil {
		return err
	}
	if _, err = f.Write(bs); err != nil {
		return err
	}
	audioChannel := NewAudioChannel(audioChannelConfig.ID, audioChannelConfig.Owner, audioChannelConfig.ChannelType, audioChannelConfig.Members, make([]string, 0), make(map[string]*AudioChannelMember))
	_ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
		defer delete(zach.AudioChannels, channelId)
		zach.AudioChannels[newAudioChannelId] = audioChannel
		updateKnownAudioChannels := func(members []string) (err error) {
			for _, member := range members {
				zach.sendZoneRequest(ADD_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{
					"userId":    member,
					"channelId": newAudioChannelId,
				})
				zach.sendZoneRequest(REMOVE_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{
					"userId":    member,
					"channelId": channelId,
				})
				done, e := zach.sendDataChannelMessage(AUDIO_CHANNNEL_NAME_EDITED, "node", member, map[string]interface{}{
					"formerAudioChannelId": channelId,
					"newAudioChannelId":    newAudioChannelId,
				})
				select {
				case <-done:
				case channelErr := <-e:
					logger.Println(channelErr)
				}
			}
			return
		}
		switch audioChannel.ChannelType {
		case BROADCAST:
			fallthrough
		case PUBLIC:
			err = updateKnownAudioChannels(zach.ZoneMembersId)
		case PRIVATE:
			err = updateKnownAudioChannels(audioChannel.Members)
		}
		return
	})
	return
}

func (zach *ZoneAudioChannelsHandler) EditAudioChannelType(channelId string, channelType string) (err error) {
	if _, ok := zach.AudioChannels[channelId]; !ok {
		err = fmt.Errorf("no coresponding audioChannel")
		return
	}
	bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.json"))
	if err != nil {
		return
	}
	var audioChannelConfig AudioChannelConfig
	if err = json.Unmarshal(bs, &audioChannelConfig); err != nil {
		return
	}
	audioChannelConfig.ChannelType = channelType
	bs, err = json.Marshal(&audioChannelConfig)
	f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.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
	}
	audioChannel := NewAudioChannel(audioChannelConfig.ID, audioChannelConfig.Owner, audioChannelConfig.ChannelType, audioChannelConfig.Members, make([]string, 0), make(map[string]*AudioChannelMember))
	switch channelType {
	case BROADCAST:
		fallthrough
	case PUBLIC:
		var members = []string{}
		_ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
			members = append(members, zach.ZoneMembersId...)
			return
		})
		for _, member := range zach.ZoneMembersId {
			if pubErr := zach.AddAudioChannelsMembers(channelId, []any{member}); pubErr != nil {
				logger.Println(pubErr)
			}
			zach.sendDataChannelMessage(AUDIO_CHANNNEL_TYPE_EDITED, "node", member, map[string]interface{}{
				"channelId":   channelId,
				"channelType": channelType,
			})
		}
	case PRIVATE:
		for _, member := range zach.ZoneMembersId {
			zach.sendDataChannelMessage(AUDIO_CHANNNEL_TYPE_EDITED, "node", member, map[string]interface{}{
				"channelId":   channelId,
				"channelType": channelType,
			})
		}

	}
	_ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
		zach.AudioChannels[channelId] = audioChannel
		return
	})
	return
}

func (zach *ZoneAudioChannelsHandler) AddAudioChannelsMembers(channelId string, members []interface{}) (err error) {
	if _, ok := zach.AudioChannels[channelId]; !ok {
		err = fmt.Errorf("no coresponding audioChannel")
		return
	}
	membersStr, err := ToStringSlice(members)
	if err != nil {
		return
	}
	bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.json"))
	if err != nil {
		return
	}
	var audioChannelConfig AudioChannelConfig
	if err = json.Unmarshal(bs, &audioChannelConfig); err != nil {
		return
	}
	logger.Printf("%s - %s - %s -%v\n", audioChannelConfig.ID, audioChannelConfig.ChannelType, audioChannelConfig.Owner, members)
	addedMembers := make([]string, 0)
memberLoop:
	for _, audioChannelMember := range membersStr {
		logger.Println("entering broadcast loop")

		for _, member := range audioChannelConfig.Members {
			if member == audioChannelMember {
				continue memberLoop
			}
		}
		audioChannelConfig.Members = append(audioChannelConfig.Members, audioChannelMember)
		addedMembers = append(addedMembers, audioChannelMember)
		logger.Println("sending zone request", ADD_KNOWN_AUDIO_CHANNEL)
		zach.sendZoneRequest(ADD_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{
			"userId":    audioChannelMember,
			"channelId": channelId,
		})
		logger.Println("--------------done")
	}
	bs, err = json.Marshal(&audioChannelConfig)
	f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.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
	}
	_ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
		zach.AudioChannels[channelId].Members = audioChannelConfig.Members
		ac := zach.AudioChannels[channelId]
		for _, member := range ac.Members {
			for _, m := range addedMembers {
				if member == m {
					if m != audioChannelConfig.Owner {
						go func() {
							bs, err = json.Marshal(map[string]any{
								"zoneId":    zach.ZoneId,
								"channelId": channelId,
								"zoneHost":  NodeID,
							})
							if err != nil {
								return
							}
							zach.sendZoneRequest(CREATE_NOTIFICATION, "node", map[string]interface{}{
								"type":       "added_in_audio_channel",
								"title":      "Added in audio channel 🎙",
								"body":       fmt.Sprintf("Added in channel %s in zone %s", channelId, zach.ZoneName),
								"isPushed":   true,
								"payload":    string(bs),
								"recipients": []string{m},
							})
						}()
					}
					done, e := zach.sendDataChannelMessage(ADDED_IN_AUDIO_CHANNEL, "node", member, map[string]interface{}{
						"audioChannel": ac,
					})
					select {
					case <-done:
					case err = <-e:
						logger.Println(err)
					}
				} else if _, ok := zach.DataChannels[member]; ok {
					done, e := zach.sendDataChannelMessage(AUDIO_CHANNEL_MEMBER_ADDED, "node", member, map[string]interface{}{
						"userId":    m,
						"channelId": ac.ID,
					})
					select {
					case <-done:
					case err = <-e:
						logger.Println(err)
					}
				}
			}
		}
		return
	})
	return
}

func (zach *ZoneAudioChannelsHandler) RemoveAudioChannelMember(channelId string, channelMember string) (err error) {
	if _, ok := zach.AudioChannels[channelId]; !ok {
		err = fmt.Errorf("no coresponding audioChannel")
		return
	}
	bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.json"))
	if err != nil {
		return
	}
	var audioChannelConfig AudioChannelConfig
	if err = json.Unmarshal(bs, &audioChannelConfig); err != nil {
		logger.Println(string(bs))
		logger.Println("json error right here")
		return
	}
	if channelMember == audioChannelConfig.Owner {
		err = fmt.Errorf("you cannot remove the owner from the audioChannel")
		return
	}
	var index int
	var contain bool
	for i, member := range audioChannelConfig.Members {
		if member == channelMember {
			index = i
			contain = true
			break
		}
	}
	if !contain {
		err = fmt.Errorf("member %s not in the channel %s", channelMember, channelId)
		return
	}
	audioChannelConfig.Members = append(audioChannelConfig.Members[:index], audioChannelConfig.Members[index+1:]...)
	bs, err = json.Marshal(&audioChannelConfig)
	if err != nil {
		logger.Println("json error there")
		return
	}
	f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zach.ZoneId, "audioChannels", channelId, "audioChannelConfig.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
	}
	_ = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
		zach.AudioChannels[channelId].Members = audioChannelConfig.Members
		ac := zach.AudioChannels[channelId]
	broadcastLoop:
		for _, member := range ac.Members {
			if member == channelMember {
				continue broadcastLoop
			}
			done, e := zach.sendDataChannelMessage(AUDIO_CHANNEL_MEMBER_REMOVED, "node", member, map[string]interface{}{
				"userId":    channelMember,
				"channelId": ac.ID,
			})
			select {
			case <-done:
			case err = <-e:
				logger.Println(err)
			}
		}
		return
	})
	zach.sendZoneRequest(REMOVE_KNOWN_AUDIO_CHANNEL, "node", map[string]interface{}{
		"userId":    channelMember,
		"channelId": channelId,
	})
	if channelMember != audioChannelConfig.Owner {
		bs, err = json.Marshal(map[string]any{
			"zoneId":    zach.ZoneId,
			"channelId": channelId,
			"zoneHost":  NodeID,
		})
		if err != nil {
			return err
		}
		zach.sendZoneRequest(CREATE_NOTIFICATION, "node", map[string]interface{}{
			"type":       "removed_from_audio_channel",
			"title":      "Removed from audio channel ⛔️",
			"body":       fmt.Sprintf("You have no longer access to the channel %s in zone %s", channelId, zach.ZoneName),
			"isPushed":   true,
			"payload":    string(bs),
			"recipients": []string{channelMember},
		})
	}
	done, e := zach.sendDataChannelMessage(REMOVED_FROM_AUDIO_CHANNEL, "node", channelMember, map[string]interface{}{
		"channelId": channelId,
		"userId":    channelMember,
	})
	select {
	case <-done:
	case err = <-e:
		logger.Println(err)
	}
	return
}

func (zach *ZoneAudioChannelsHandler) SetAllPublicAudioChannelForUser(userId string) (err error) {
	audioChannels, err := zach.ListAudioChannels()
	if err != nil {
		return
	}
	for _, audioChannel := range audioChannels {
		if audioChannel.ChannelType == PUBLIC || audioChannel.ChannelType == BROADCAST {

			if err = zach.AddAudioChannelsMembers(audioChannel.ID, []interface{}{userId}); err != nil {
				logger.Println(err)
			}
			continue

		}
	}
	return
}

func (zach *ZoneAudioChannelsHandler) RemoveUserFromAllAudioChannels(userId string) (err error) {
	audioChannels, err := zach.ListAudioChannels()
	if err != nil {
		return
	}
	for _, audioChannel := range audioChannels {
		if err = zach.RemoveAudioChannelMember(audioChannel.ID, userId); err != nil {
			continue
		}
	}
	return
}

func (zach *ZoneAudioChannelsHandler) RemoveAllUserAudioChannels(userId string) (err error) {
	audioChannels, err := zach.ListAudioChannels()
	if err != nil {
		return
	}
	for _, audioChannel := range audioChannels {
		if audioChannel.Owner == userId {
			if derr := zach.DeleteAudioChannel(audioChannel.ID); derr != nil {
				logger.Println("**************", derr)
			}
		}
	}
	return
}

func (zach *ZoneAudioChannelsHandler) JoinAudioChannel(channelId string, userId string, sdp string) (err error) {
	err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
		if _, ok := zach.AudioChannels[channelId]; !ok {
			err = fmt.Errorf("no audio channel with corresponding id")
			return
		}
		audioChannel := zach.AudioChannels[channelId]
		audioChannel.CurrentMembersId = append(audioChannel.CurrentMembersId, userId)
		audioChannel.CurrentMembers[userId] = &AudioChannelMember{}
		signalMembers := func(members []string) {
			for _, u := range members {
				done, e := zach.sendDataChannelMessage(USER_JOINED_AUDIO_CHANNEL, "node", u, map[string]interface{}{
					"userId":    userId,
					"channelId": channelId,
				})
				select {
				case <-done:
				case err := <-e:
					logger.Println(err)
				}
			}
		}
		if audioChannel.ChannelType == PRIVATE {
			signalMembers(audioChannel.Members)
		} else {
			signalMembers(zach.ZoneMembersId)
		}
		d, e := audioChannel.HandleOffer(context.Background(), channelId, userId, sdp, zach.HostId, zach.sendDataChannelMessage, zach.signalCandidate)
		select {
		case <-d:
		case err = <-e:
		}
		return
	})
	return
}

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

func (zach *ZoneAudioChannelsHandler) 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, "audio_channel_data") {
		command := strings.Split(label, "|")
		catched = true
		_ = atomicallyExecute(zach.AudioChanDataChannelsFlag, func() (err error) {
			zach.AudioChanDataChannels[command[1]] = dc
			return
		})
		dc.DataChannel.OnClose(func() {
			fmt.Println("closing gratefully audio channel dc...")
			_ = atomicallyExecute(zach.AudioChanDataChannelsFlag, func() (err error) {
				delete(zach.AudioChanDataChannels, command[1])
				return
			})
		})
		dc.DataChannel.OnError(func(err error) {
			fmt.Println("error in audio channel dc...")
			_ = atomicallyExecute(zach.AudioChanDataChannelsFlag, func() (err error) {
				delete(zach.AudioChanDataChannels, command[1])
				return
			})
		})
	}
	return
}

func (zach *ZoneAudioChannelsHandler) 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 _, ac := range zach.AudioChannels {
			var contain bool
			var id string
			for _, member := range ac.CurrentMembersId {
				if member == req.Payload["userId"].(string) {
					id = member
					contain = true
					logger.Printf("*------------------id is %s--------------*\n", id)
					break
				}
			}
			if contain {
				err = zach.LeaveAudioChannel(ac.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 zach.ZoneMembersId {
			if m == req.Payload["userId"].(string) {
				index = i
				found = true
				break
			}
		}
		if !found {
			err = fmt.Errorf("no such member in zone")
			return
		}
		zach.ZoneMembersId = append(zach.ZoneMembersId[:index], zach.ZoneMembersId[index+1:]...)
		if err = zach.RemoveAllUserAudioChannels(req.Payload["userId"].(string)); err != nil {
			logger.Println("****______________", err)
		}
		err = zach.RemoveUserFromAllAudioChannels(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 zach.ZoneMembersId {
			if m == req.Payload["userId"].(string) {
				contain = true
				break
			}
		}
		if !contain {
			zach.ZoneMembersId = append(zach.ZoneMembersId, req.Payload["userId"].(string))
		}
		err = zach.SetAllPublicAudioChannelForUser(req.Payload["userId"].(string))
	case JOIN_AUDIO_CHANNEL:
		if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil {
			return
		}
		err = zach.JoinAudioChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string), req.Payload["sdp"].(string))
	case LEAVE_AUDIO_CHANNEL:
		if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil {
			return
		}
		err = zach.LeaveAudioChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string))
	case ADD_AUDIO_CHANNEL_MEMBERS:
		if err = verifyFieldsString(req.Payload, "channelId"); err != nil {
			return
		}
		if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil {
			return
		}
		err = zach.AddAudioChannelsMembers(req.Payload["channelId"].(string), req.Payload["members"].([]interface{}))
	case REMOVE_AUDIO_CHANNEL_MEMBER:
		if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil {
			return
		}
		err = zach.RemoveAudioChannelMember(req.Payload["channelId"].(string), req.Payload["userId"].(string))
		if err != nil {
			logger.Println("an error occured in add known audioChannel", err)
			return
		}
	case EDIT_AUDIO_CHANNEL_NAME:
		if err = verifyFieldsString(req.Payload, "channelId", "newAudioChannelId"); err != nil {
			return
		}
		if err = zach.EditAudioChannelName(req.Payload["channelId"].(string), req.Payload["newAudioChannelId"].(string)); err != nil {
			return
		}
	case EDIT_AUDIO_CHANNEL_TYPE:
		if err = verifyFieldsString(req.Payload, "channelId", "channelType"); err != nil {
			return
		}
		if err = zach.EditAudioChannelType(req.Payload["channelId"].(string), req.Payload["channelType"].(string)); err != nil {
			return
		}
	case DELETE_AUDIO_CHANNEL:
		if err = verifyFieldsString(req.Payload, "channelId"); err != nil {
			return
		}
		err = zach.DeleteAudioChannel(req.Payload["channelId"].(string))
	case CREATE_AUDIO_CHANNEL:
		if err = verifyFieldsString(req.Payload, "channelId", "owner", "channelType"); err != nil {
			return
		}
		if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil {
			return
		}
		err = zach.AddNewAudioChannel(req.Payload["channelId"].(string), req.Payload["owner"].(string), req.Payload["channelType"].(string), req.Payload["members"].([]interface{}))
	case GET_AUDIO_CHANNELS:
		if err = verifyFieldsSliceInterface(req.Payload, "channelsId"); err != nil {
			return
		}
		err = zach.GetAudioChannels(req.From, req.Payload["channelsId"].([]interface{})...)
	case string(AUDIO_CHANNEL_WEBRTC_COUNTER_OFFER):
		logger.Println("handling audio channel counter offer")
		if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil {
			return
		}
		err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
			if _, ok := zach.AudioChannels[req.Payload["channelId"].(string)]; !ok {
				err = fmt.Errorf("no channel corresponging the one requested")
				return
			}
			err = zach.AudioChannels[req.Payload["channelId"].(string)].HandleCounterOffer(ctx, req.Payload["userId"].(string), zach.sendDataChannelMessage)
			return
		})
	case string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER):
		if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil {
			return
		}
		err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
			if _, ok := zach.AudioChannels[req.Payload["channelId"].(string)]; !ok {
				err = fmt.Errorf("no channel corresponging the one requested")
				return
			}
			err = zach.AudioChannels[req.Payload["channelId"].(string)].HandleRennegotiationOffer(req.Payload["userId"].(string), req.Payload["sdp"].(string), zach.sendDataChannelMessage)
			return
		})
	case string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER):
		if err = verifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil {
			return
		}
		err = atomicallyExecute(zach.AudioChannelsFlag, func() (err error) {
			if _, ok := zach.AudioChannels[req.Payload["channelId"].(string)]; !ok {
				err = fmt.Errorf("no channel corresponging the one requested")
				return
			}
			err = zach.AudioChannels[req.Payload["channelId"].(string)].HandleRennegotiationAnswer(req.Payload["userId"].(string), req.Payload["sdp"].(string))
			return
		})
	case string(AUDIO_CHANNEL_WEBRTC_CANDIDATE):
		logger.Println("handling audio 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(zach.AudioChannelsFlag, func() (err error) {
			if _, ok := zach.AudioChannels[req.Payload["channelId"].(string)]; !ok {
				err = fmt.Errorf("no channel corresponging the one requested")
				return
			}
			err = zach.AudioChannels[req.Payload["channelId"].(string)].AddCandidate(&webrtc.ICECandidateInit{
				Candidate:     req.Payload["candidate"].(string),
				SDPMid:        &sdpMid,
				SDPMLineIndex: &SDPMLineIndex,
			}, req.Payload["userId"].(string))
			return
		})
		return
	default:
		logger.Println("audio channel handler still in process of implementation")
	}
	return
}
