package localserver

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

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

type SquadVideoChannelsHandler struct {
	SquadId                   string
	SquadName                 string
	HostId                    string
	SquadMembersId            []string
	DataChannels              map[string]*DataChannel
	VideoChanDataChannels     map[string]*DataChannel
	VideoChanDataChannelsFlag *uint32
	DataChannelsFlag          *uint32
	VideoChannelsFlag         *uint32
	VideoChannel              *VideoChannel
	reqChans                  []chan<- *SquadRequest
}

func NewSquadVideoChannelsHandler(hostId string, squadId string, owner string, authorizedMembers []string, dataChannels map[string]*DataChannel, dataChannelFlag *uint32) (squadVideoChannelsHandler *SquadVideoChannelsHandler, err error) {
	_, err = os.ReadDir(filepath.Join(dataPath, "data", "squads", squadId, "videoChannel"))
	if err != nil {
		if os.IsNotExist(err) {
			// logger.Printf("creating videoChannels directory for squad %s...\n", squadId)
			mkdirErr := os.MkdirAll(filepath.Join(dataPath, "data", "squads", squadId, "videoChannel"), 0700)
			if mkdirErr != nil {
				return nil, mkdirErr
			}
			file, ferr := os.Create(filepath.Join(dataPath, "data", "squads", squadId, "videoChannel", "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
			}
			_, err = os.ReadDir(filepath.Join(dataPath, "data", "squads", squadId, "videoChannel"))
			if err != nil {
				return nil, err
			}
		} else {
			return
		}
	}

	var bs []byte
	bs, err = os.ReadFile(filepath.Join(dataPath, "data", "squads", squadId, "videoChannel", "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))
	videoChannelFlag := uint32(0)
	videoChanDCFlag := uint32(0)
	squadVideoChannelsHandler = &SquadVideoChannelsHandler{
		SquadId:                   squadId,
		HostId:                    hostId,
		SquadMembersId:            authorizedMembers,
		DataChannels:              dataChannels,
		VideoChanDataChannels:     make(map[string]*DataChannel),
		VideoChanDataChannelsFlag: &videoChanDCFlag,
		DataChannelsFlag:          dataChannelFlag,
		VideoChannel:              vc,
		VideoChannelsFlag:         &videoChannelFlag,
	}
	return
}

func (zvch *SquadVideoChannelsHandler) 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(&SquadResponse{
					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 *SquadVideoChannelsHandler) Init(ctx context.Context, authorizedMembers []string) (err error) {
	return
}

func (zvch *SquadVideoChannelsHandler) Subscribe(ctx context.Context, publisher <-chan *SquadRequest) (reqChan chan *SquadRequest, done chan struct{}, errCh chan error) {
	reqChan, done, errCh = make(chan *SquadRequest), 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.handleSquadRequest(ctx, req); err != nil {
					errCh <- err
				}
			}
		}
	}()
	return
}

func (zvch *SquadVideoChannelsHandler) 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 *SquadVideoChannelsHandler) JoinVideoChannel(channelId string, userId string, sdp string) (err error) {
	err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
		videoChannel := zvch.VideoChannel
		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 <-e:
					// logger.Println(err)
				}
			}
		}

		signalMembers(zvch.SquadMembersId)
		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 *SquadVideoChannelsHandler) LeaveVideoChannel(channelId string, userId string) (err error) {
	err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) {
		videoChannel := zvch.VideoChannel
		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 <-e:
					// logger.Println(err)
				}
			}
		}
		signalMembers(zvch.SquadMembersId)
		return
	})
	return
}

func (zvch *SquadVideoChannelsHandler) 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.OnOpen(func() {
			fmt.Println("datachann in squad video chann fking created")
			bs, err := json.Marshal(map[string]any{
				"type": "init_video_channel",
				"from": NodeID,
				"to":   command[1],
				"payload": map[string]any{
					"videoChannel": zvch.VideoChannel,
				},
			})
			if err != nil {
				fmt.Println(err)
			}
			_ = dc.DataChannel.SendText(string(bs))
		})
		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 *SquadVideoChannelsHandler) handleSquadRequest(ctx context.Context, req *SquadRequest) (err error) {
	switch req.ReqType {
	case LEAVE_ZONE:
		// logger.Println("*-----------------handling leaving squad---------------*")
		if err = VerifyFieldsString(req.Payload, "userId"); err != nil {
			return
		}
		vc := zvch.VideoChannel
		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_SQUAD_AUTHORIZED_MEMBER):
		if err = VerifyFieldsString(req.Payload, "userId"); err != nil {
			return
		}
		var index int
		var found bool
		for i, m := range zvch.SquadMembersId {
			if m == req.Payload["userId"].(string) {
				index = i
				found = true
				break
			}
		}
		if !found {
			err = fmt.Errorf("no such member in squad")
			return
		}
		zvch.SquadMembersId = append(zvch.SquadMembersId[:index], zvch.SquadMembersId[index+1:]...)
	case string(NEW_AUTHORIZED_SQUAD_MEMBER):
		if err = VerifyFieldsString(req.Payload, "userId"); err != nil {
			return
		}
		var contain bool
		for _, m := range zvch.SquadMembersId {
			if m == req.Payload["userId"].(string) {
				contain = true
				break
			}
		}
		if !contain {
			zvch.SquadMembersId = append(zvch.SquadMembersId, req.Payload["userId"].(string))
		}
	case JOIN_VIDEO_CHANNEL:
		fmt.Println("wuwuuwuwwuuwuw i got this")
		fmt.Println(req.Payload)
		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 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) {
			err = zvch.VideoChannel.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) {
			err = zvch.VideoChannel.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) {
			err = zvch.VideoChannel.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) {
			fmt.Println("uwuwuw adding candidate", req.Payload["candidate"].(string))
			err = zvch.VideoChannel.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
}
