package localserver

import (
	context "context"
	"encoding/json"
	"fmt"
	"io"
	sync "sync"

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

type WebrtcFsManager struct {
	stream                  SignalingService_LinkClient
	DatachannelManager      DataChannelManager
	LocalSD                 map[string]*webrtc.SessionDescription
	RTCPeerConnections      map[string]*webrtc.PeerConnection
	DataChannels            map[string]*webrtc.DataChannel
	PendingCandidates       map[string][]*webrtc.ICECandidate
	CandidateChannel        chan *IncomingCandidate
	CandidateMux            *sync.RWMutex
	RTCPeerConnectionMapMux *sync.RWMutex
	DataChannelMapMux       *sync.RWMutex
	LocalSDMapMux           *sync.RWMutex
}

func NewWebrtcFsManager(dataChannelManager DataChannelManager) (webrtcFsManager *WebrtcFsManager, err error) {
	webrtcFsManager = &WebrtcFsManager{
		DatachannelManager:      dataChannelManager,
		DataChannels:            make(map[string]*webrtc.DataChannel),
		PendingCandidates:       make(map[string][]*webrtc.ICECandidate),
		LocalSD:                 make(map[string]*webrtc.SessionDescription),
		RTCPeerConnections:      make(map[string]*webrtc.PeerConnection),
		RTCPeerConnectionMapMux: &sync.RWMutex{},
		LocalSDMapMux:           &sync.RWMutex{},
		CandidateMux:            &sync.RWMutex{},
		DataChannelMapMux:       &sync.RWMutex{},
	}
	return
}

func (wf *WebrtcFsManager) sendSignalingMessage(messageType, from, to string, payload map[string]interface{}) (err error) {
	bs, err := json.Marshal(payload)
	if err != nil {
		return
	}
	err = wf.stream.Send(&SignalingMessage{
		Type:    messageType,
		From:    from,
		To:      to,
		Payload: bs,
	})
	return
}

func (wf *WebrtcFsManager) CreateOffer(ctx context.Context, target string, from string, cb OnICECandidateFunc) (err error) {
	peerConnection, err := wf.createPeerConnection(target, from, webrtc.SDPTypeOffer, cb)
	if err != nil {
		return
	}
	rawOffer, err := peerConnection.CreateOffer(nil)
	if err != nil {
		return
	}
	if err = peerConnection.SetLocalDescription(rawOffer); err != nil {
		return
	}
	wf.RTCPeerConnectionMapMux.Lock()
	logger.Println("adding for target", target)
	wf.RTCPeerConnections[target] = peerConnection
	wf.RTCPeerConnectionMapMux.Unlock()
	err = wf.sendSignalingMessage(string(WEBRTC_OFFER_FS), "lolo_local_serv", target, map[string]any{
		"to":   target,
		"from": "lolo_local_serv",
		"sdp":  rawOffer.SDP,
	})
	return
}

func (wf *WebrtcFsManager) HandleOffer(ctx context.Context, req map[string]string, cb OnICECandidateFunc) (err error) {
	done, errCh := make(chan struct{}), make(chan error)
	go func() {
		peerConnection, err := wf.createPeerConnection(req[FROM], req[TO], webrtc.SDPTypeAnswer, cb)
		if err != nil {
			errCh <- err
			return
		}
		wf.RTCPeerConnectionMapMux.Lock()
		wf.RTCPeerConnections[req[FROM]] = peerConnection
		wf.RTCPeerConnectionMapMux.Unlock()
		offer := webrtc.SessionDescription{
			Type: webrtc.SDPTypeOffer,
			SDP:  req[SDP],
		}
		if err = peerConnection.SetRemoteDescription(offer); err != nil {
			errCh <- err
			return
		}
		rawAnswer, err := peerConnection.CreateAnswer(nil)
		if err != nil {
			errCh <- err
			return
		}
		wf.LocalSDMapMux.Lock()
		wf.LocalSD[req[FROM]] = &rawAnswer
		wf.LocalSDMapMux.Unlock()
		// if err = wf.stream.Send(&Request{
		// 	Type:  string(WEBRTC_ANSWER_FS),
		// 	From:  "lolo_local_serv",
		// 	Token: "none",
		// 	Payload: map[string]string{
		// 		"to":   req[FROM],
		// 		"from": "lolo_local_serv",
		// 		"sdp":  rawAnswer.SDP,
		// 	},
		// }); err != nil {
		// 	errCh <- err
		// 	return
		// }
		done <- struct{}{}
	}()
	select {
	case <-done:
		return
	case err = <-errCh:
		return
	case <-ctx.Done():
		err = ctx.Err()
		return
	}
}

func (wf *WebrtcFsManager) HandleAnswer(ctx context.Context, req map[string]string) (err error) {
	wf.RTCPeerConnectionMapMux.RLock()
	defer wf.RTCPeerConnectionMapMux.RUnlock()
	defer func() {
		if r := recover(); err != nil {
			logger.Printf("recover from panic : %v\n", r)
		}
	}()
	if _, ok := wf.RTCPeerConnections[req[FROM]]; !ok {
		err = fmt.Errorf("no corresponding peer connection for id : %s", req[FROM])
		return
	}
	peerConnnection := wf.RTCPeerConnections[req[FROM]]
	if err = peerConnnection.SetRemoteDescription(webrtc.SessionDescription{
		Type: webrtc.SDPTypeAnswer,
		SDP:  req[SDP],
	}); err != nil {
		return
	}
	// if err = wf.stream.Send(&Request{
	// 	Type:  string(WEBRTC_COUNTER_OFFER_FS),
	// 	From:  "lolo_local_serv",
	// 	Token: "none",
	// 	Payload: map[string]string{
	// 		"from": "lolo_local_serv",
	// 		"to":   req[FROM],
	// 	},
	// }); err != nil {
	// 	return
	// }
	wf.CandidateMux.RLock()
	for range wf.PendingCandidates[req[FROM]] {
		logger.Println("sending candidate to", req[FROM])
		// if err = wf.stream.Send(&Request{
		// 	Type:  string(WEBRTC_CANDIDATE_FS),
		// 	From:  "lolo_local_serv",
		// 	Token: "none",
		// 	Payload: map[string]string{
		// 		"from":          "lolo_local_serv",
		// 		"to":            req[FROM],
		// 		"candidate":     candidate.ToJSON().Candidate,
		// 		"sdpMid":        *candidate.ToJSON().SDPMid,
		// 		"sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)),
		// 	},
		// }); err != nil {
		// 	return
		// }
	}
	wf.CandidateMux.RUnlock()
	wf.CandidateMux.Lock()
	delete(wf.PendingCandidates, req[FROM])
	wf.CandidateMux.Unlock()
	wf.LocalSDMapMux.Lock()
	delete(wf.LocalSD, req[FROM])
	wf.LocalSDMapMux.Unlock()
	return
}

func (wf *WebrtcFsManager) HandleCounterOffer(ctx context.Context, req map[string]string) (err error) {
	wf.RTCPeerConnectionMapMux.RLock()
	if _, ok := wf.RTCPeerConnections[req[FROM]]; !ok {
		err = fmt.Errorf("no field corresponding peer connection for id %s", req[FROM])
		return
	}
	connection := wf.RTCPeerConnections[req[FROM]]
	wf.RTCPeerConnectionMapMux.RUnlock()
	wf.LocalSDMapMux.RLock()
	if err = connection.SetLocalDescription(*wf.LocalSD[req[FROM]]); err != nil {
		return
	}
	wf.LocalSDMapMux.RUnlock()
	wf.CandidateMux.RLock()
	for range wf.PendingCandidates[req[FROM]] {
		// logger.Println("sending candidate to", req[FROM])
		// if err = wf.stream.Send(&Request{
		// 	Type:  string(WEBRTC_CANDIDATE_FS),
		// 	From:  "lolo_local_serv",
		// 	Token: "none",
		// 	Payload: map[string]string{
		// 		"from":          "lolo_local_serv",
		// 		"to":            req[FROM],
		// 		"candidate":     candidate.ToJSON().Candidate,
		// 		"sdpMid":        *candidate.ToJSON().SDPMid,
		// 		"sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)),
		// 	},
		// }); err != nil {
		// 	return
		// }
	}
	wf.CandidateMux.RUnlock()
	return
}

func (wf *WebrtcFsManager) createPeerConnection(target string, from string, peerType webrtc.SDPType, cb OnICECandidateFunc) (peerConnection *webrtc.PeerConnection, err error) {
	defer func() {
		if r := recover(); err != nil {
			logger.Printf("recover from panic : %v\n", r)
		}
	}()
	s := webrtc.SettingEngine{}
	s.DetachDataChannels()

	api := webrtc.NewAPI(webrtc.WithSettingEngine(s))

	config := webrtc.Configuration{
		ICEServers: []webrtc.ICEServer{
			{
				URLs: []string{"stun:stun.l.google.com:19302", "stun:stunserver.org:3478", "stun:stun.l.google.com:19302?transport=tcp"},
			},
		},
		SDPSemantics: webrtc.SDPSemanticsUnifiedPlan,
	}
	peerConnection, err = api.NewPeerConnection(config)
	if err != nil {
		return
	}
	if peerType == webrtc.SDPTypeOffer {
		maxRetransmits := uint16(100)
		channel, err := peerConnection.CreateDataChannel("data", &webrtc.DataChannelInit{
			MaxRetransmits: &maxRetransmits,
		})
		if err != nil {
			return nil, err
		}
		channel.OnOpen(func() {
			logger.Println("channel opened")
			if chanErr := channel.SendText("yooo man this is open"); chanErr != nil {
				logger.Println(chanErr)
			}
		})
		channel.OnMessage(func(msg webrtc.DataChannelMessage) {
			logger.Printf("new message %s\n", string(msg.Data))
			done, errCh := wf.DatachannelManager.HandleMessage(&DatachannelMessage{
				From:    target,
				Type:    "test",
				Payload: &DatachannelMessagePayload{},
			}, channel)
			select {
			case <-done:
				//logger.Println("done with success")
			case e := <-errCh:
				logger.Println(e)
				logger.Println("this is impossible")
			}
		})
		wf.DataChannelMapMux.Lock()
		wf.DataChannels[target] = channel
		wf.DataChannelMapMux.Unlock()
	}
	peerConnection.OnICEConnectionStateChange(func(is webrtc.ICEConnectionState) {
		logger.Printf("ICE connection state has changed %s\n", is.String())
		if is == webrtc.ICEConnectionStateDisconnected || is == webrtc.ICEConnectionStateFailed {
			logger.Println(is)
		}
	})
	peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) {
		dc.OnOpen(func() {
			logger.Printf("got a new open datachannel %s\n", dc.Label())
			dataChann, err := dc.Detach()
			if err != nil {
				logger.Println(err)
				return
			}
			for {
				var x []byte = make([]byte, 2<<15)
				n, _, err := dataChann.ReadDataChannel(x)
				if err != nil {
					logger.Println(err)
					if err == io.EOF {
						return
					}
					continue
				}
				go func(msg []byte) {
					var dataChannelMessage DatachannelMessage
					if unmarshalErr := json.Unmarshal(msg, &dataChannelMessage); unmarshalErr != nil {
						logger.Println(unmarshalErr)
						return
					}
					done, errCh := wf.DatachannelManager.HandleMessage(&DatachannelMessage{
						From:    dataChannelMessage.From,
						Type:    dataChannelMessage.Type,
						Payload: dataChannelMessage.Payload,
					}, dc)
					select {
					case <-done:
						//logger.Println("done with success")
					case e := <-errCh:
						logger.Println(e)
						logger.Println("this is impossible")
					}
				}(x[:n])
			}
		})
		// dc.OnMessage(func(msg webrtc.DataChannelMessage) {
		// 	var dataChannelMessage DatachannelMessage
		// 	if unmarshalErr := json.Unmarshal(msg.Data, &dataChannelMessage); unmarshalErr != nil {
		// 		logger.Println(unmarshalErr)
		// 		return
		// 	}
		// 	done, errCh := wf.DatachannelManager.HandleMessage(&DatachannelMessage{
		// 		From:    dataChannelMessage.From,
		// 		Type:    dataChannelMessage.Type,
		// 		Payload: dataChannelMessage.Payload,
		// 	}, dc)
		// 	select {
		// 	case <-done:
		// 		//logger.Println("done with success")
		// 	case e := <-errCh:
		// 		logger.Println(e)
		// 		logger.Println("this is impossible")
		// 	}
		// })
	})
	peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) {
		if i == nil {
			return
		}
		wf.CandidateMux.Lock()
		defer wf.CandidateMux.Unlock()
		desc := peerConnection.RemoteDescription()
		if desc == nil {
			wf.PendingCandidates[target] = append(wf.PendingCandidates[target], i)
		} else {
			logger.Println(i)
			if iceCandidateErr := cb(target, i); iceCandidateErr != nil {
				logger.Println(iceCandidateErr)
			}
		}
	})
	peerConnection.OnNegotiationNeeded(func() {
		if peerConnection.SignalingState() != webrtc.SignalingStateHaveLocalOffer && peerConnection.SignalingState() != webrtc.SignalingStateHaveRemoteOffer {
			wf.RTCPeerConnectionMapMux.Lock()
			defer wf.RTCPeerConnectionMapMux.Unlock()
			for _, connection := range wf.RTCPeerConnections {
				if connection.SignalingState() != webrtc.SignalingStateHaveLocalOffer && connection.SignalingState() != webrtc.SignalingStateHaveRemoteOffer {
					localSd, err := connection.CreateOffer(nil)
					if err != nil {
						logger.Println(err)
						return
					}
					if err = connection.SetLocalDescription(localSd); err != nil {
						logger.Println(err)
						return
					}
					// if err = wf.stream.Send(&Request{
					// 	Type:  string(WEBRTC_RENNEGOTIATION_OFFER_FS),
					// 	From:  "lolo_local_serv",
					// 	Token: "",
					// 	Payload: map[string]string{
					// 		"to":  id,
					// 		"sdp": localSd.SDP,
					// 	},
					// }); err != nil {
					// 	logger.Println(err)
					// 	return
					// }
				}
			}
		}
	})
	return
}

func (wf *WebrtcFsManager) HandleRennegotiationOffer(from string, dst string, sdp string) (err error) {
	wf.RTCPeerConnectionMapMux.Lock()
	defer wf.RTCPeerConnectionMapMux.Unlock()
	if _, ok := wf.RTCPeerConnections[from]; !ok {
		err = fmt.Errorf("no corresponding peer connection for id %s", from)
		return
	}
	if wf.RTCPeerConnections[from].SignalingState() != webrtc.SignalingStateStable {
		err = fmt.Errorf("rennego called in wrong state")
		return
	}
	if err = wf.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil {
		return
	}
	localSd, err := wf.RTCPeerConnections[from].CreateAnswer(nil)
	if err != nil {
		return
	}
	if err = wf.RTCPeerConnections[from].SetLocalDescription(localSd); err != nil {
		return
	}
	// if err = wf.stream.Send(&Request{
	// 	Type:  string(WEBRTC_RENNEGOTIATION_ANSWER_FS),
	// 	From:  "lolo_local_serv",
	// 	Token: "",
	// 	Payload: map[string]string{
	// 		"to":  from,
	// 		"sdp": localSd.SDP,
	// 	},
	// }); err != nil {
	// 	logger.Println(err)
	// 	return
	// }
	return
}

func (wf *WebrtcFsManager) HandleRennegotiationAnswer(from string, dst string, sdp string) (err error) {
	wf.RTCPeerConnectionMapMux.Lock()
	defer wf.RTCPeerConnectionMapMux.Unlock()
	if _, ok := wf.RTCPeerConnections[from]; !ok {
		err = fmt.Errorf("no corresponding peer connection for id %s", from)
		return
	}
	err = wf.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer})
	return
}

func (wf *WebrtcFsManager) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) {
	wf.RTCPeerConnectionMapMux.Lock()
	defer wf.RTCPeerConnectionMapMux.Unlock()
	err = wf.RTCPeerConnections[from].AddICECandidate(*candidate)
	return
}
