package localserver

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"

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

type GrpcRequestType string

const (
	PEER_CONNECTION_REQUEST GrpcRequestType = "peer_connection_request"
)

const (
	OFFER                                     ReqType = "offer"
	ANSWER                                    ReqType = "answer"
	COUNTER_OFFER                             ReqType = "webrtc_counter_offer"
	JOIN_HOSTED_SQUAD                         ReqType = "join_hosted_squad"
	HOSTED_SQUAD_ACCESS_DENIED                ReqType = "hosted_squad_access_denied"
	HOSTED_SQUAD_STOP_CALL                    ReqType = "hosted_squad_stop_call"
	HOSTED_SQUAD_ACCESS_GRANTED               ReqType = "hosted_squad_access_granted"
	LEAVE_HOSTED_SQUAD                        ReqType = "leave_hosted_squad"
	INCOMING_MEMBER_HOSTED                    ReqType = "incoming_member_hosted"
	LEAVING_MEMBER_HOSTED                     ReqType = "leaving_member_hosted"
	HOSTED_SQUAD_WEBRTC_OFFER                 ReqType = "hosted_squad_offer"
	HOSTED_SQUAD_WEBRTC_ANSWER                ReqType = "hosted_squad_answer"
	HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_OFFER  ReqType = "hosted_squad_rennegotiation_offer"
	HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_ANSWER ReqType = "hosted_squad_rennegotiation_answer"
	HOSTED_SQUAD_WEBRTC_COUNTER_OFFER         ReqType = "hosted_squad_webrtc_counter_offer"
	HOSTED_SQUAD_WEBRTC_CANDIDATE             ReqType = "hosted_squad_webrtc_candidate"
	HOSTED_SQUAD_REMOVE_VIDEO                 ReqType = "hosted_squad_remove_video"
	GET_HOSTED_SQUAD_TRACKS                   ReqType = "hosted_squad_get_tracks"
	NEW_HOSTED_SQUAD                                  = "new_hosted_squad"
)

type WebRTCGrpcMiddleware struct {
	Manager *WebRTCCallManager
	stream  SignalingService_LinkClient
}

func NewWebRTCGrpcMiddleware(manager *WebRTCCallManager) (webrtcGrpcMiddleware *WebRTCGrpcMiddleware) {
	webrtcGrpcMiddleware = &WebRTCGrpcMiddleware{
		Manager: manager,
	}
	return
}

func validateRequest(req map[string]string, entries ...string) (err error) {
	for _, entry := range entries {
		if _, ok := req[entry]; !ok {
			err = fmt.Errorf("no field %s in req payload", entry)
			return
		}
	}
	return
}

func (wgm *WebRTCGrpcMiddleware) signalCandidate(to string, candidate *webrtc.ICECandidate) (err error) {
	bs, err := json.Marshal(map[string]string{
		"from":          wgm.Manager.ID,
		"to":            to,
		"candidate":     candidate.ToJSON().Candidate,
		"sdpMid":        *candidate.ToJSON().SDPMid,
		"sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)),
	})
	if err != nil {
		return
	}
	err = wgm.stream.Send(&SignalingMessage{
		Type:    string(HOSTED_SQUAD_WEBRTC_CANDIDATE),
		From:    wgm.Manager.ID,
		To:      to,
		Payload: bs,
	})
	return
}

func (wgm *WebRTCGrpcMiddleware) Process(ctx context.Context, req *SignalingMessage, stream SignalingService_LinkClient) (err error) {
	done, errCh := make(chan struct{}), make(chan error)
	go func() {
		defer func() {
			done <- struct{}{}
		}()
		var payload map[string]string
		if e := json.Unmarshal(req.Payload, &payload); err != nil {
			errCh <- e
			return
		}
		switch req.Type {
		case NEW_HOSTED_SQUAD:
			if err := validateRequest(payload, "ID"); err != nil {
				errCh <- err
				return
			}
			logger.Println("new squad incoming")
			wgm.Manager.SquadMapMux.Lock()
			wgm.Manager.Squads[payload["ID"]] = &Squad{
				ID:      payload["ID"],
				Members: []string{},
			}
			wgm.Manager.SquadMapMux.Unlock()
		case string(HOSTED_SQUAD_STOP_CALL):
			logger.Println("quit squad called")
			if err := validateRequest(payload, "squadId"); err != nil {
				errCh <- err
				return
			}
			wgm.Manager.HandleLeavingMember(req.GetFrom(), payload["squadId"])
			done <- struct{}{}
		case string(PEER_CONNECTION_REQUEST):
			logger.Println("creating offer for peer")
			if err := wgm.Manager.CreateOffer(ctx, req.GetFrom(), req.GetTo(), wgm.signalCandidate); err != nil {
				errCh <- err
				return
			}
		case string(HOSTED_SQUAD_WEBRTC_OFFER):
			if err := validateRequest(payload, SDP, SQUAD_ID); err != nil {
				errCh <- err
				return
			}
			if err := wgm.Manager.HandleOffer(ctx, req.GetFrom(), req.GetTo(), payload, wgm.signalCandidate); err != nil {
				errCh <- err
				return
			}
		case string(HOSTED_SQUAD_WEBRTC_ANSWER):
			if err := validateRequest(payload, SDP); err != nil {
				errCh <- err
				return
			}
			if err := wgm.Manager.HandleAnswer(ctx, req.GetFrom(), req.GetTo(), payload); err != nil {
				errCh <- err
				return
			}
		case string(HOSTED_SQUAD_WEBRTC_COUNTER_OFFER):
			if err := wgm.Manager.HandleCounterOffer(ctx, req.GetFrom(), req.GetTo(), payload); err != nil {
				errCh <- err
				return
			}
		case string(HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_ANSWER):
			logger.Println("received negotiation answer")
			if err := validateRequest(payload, SDP); err != nil {
				errCh <- err
				return
			}
			if err := wgm.Manager.HandleRennegotiationAnswer(req.GetFrom(), payload[SDP]); err != nil {
				errCh <- err
				return
			}
		case string(HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_OFFER):
			logger.Println("received negotiation offer")
			if err := validateRequest(payload, SDP); err != nil {
				errCh <- err
				return
			}
			if err := wgm.Manager.HandleRennegotiationOffer(req.GetFrom(), payload[SDP]); err != nil {
				errCh <- err
				return
			}
		case string(HOSTED_SQUAD_WEBRTC_CANDIDATE):
			if err := validateRequest(payload, "candidate", "sdpMLineIndex", "sdpMid"); err != nil {
				errCh <- err
				return
			}
			logger.Println(payload)
			i, err := strconv.Atoi(payload["sdpMLineIndex"])
			if err != nil {
				errCh <- err
				return
			}
			SDPMLineIndex := uint16(i)
			sdpMid := payload["sdpMid"]
			logger.Println(sdpMid, SDPMLineIndex)
			if err := wgm.Manager.AddCandidate(&webrtc.ICECandidateInit{
				Candidate:     payload["candidate"],
				SDPMid:        &sdpMid,
				SDPMLineIndex: &SDPMLineIndex,
			}, req.GetFrom()); err != nil {
				errCh <- err
				return
			}
		default:
		}
	}()
	select {
	case <-ctx.Done():
		err = ctx.Err()
		return
	case <-done:
		return
	case err = <-errCh:
		return
	}
}
