package webrtc

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/AlexxIT/go2rtc/pkg/core"
	"github.com/AlexxIT/go2rtc/pkg/tcp"
	"github.com/AlexxIT/go2rtc/pkg/webrtc"
	pion "github.com/pion/webrtc/v4"
)

// This package handles the Milestone WebRTC session lifecycle, including authentication,
// session creation, and session update with an SDP answer. It is designed to be used with
// a specific URL format that encodes session parameters. For example:
// webrtc:https://milestone-host/api#format=milestone#username=User#password=TestPassword#cameraId=a539f254-af05-4d67-a1bb-cd9b3c74d122
//
// https://github.com/milestonesys/mipsdk-samples-protocol/tree/main/WebRTC_JavaScript

type milestoneAPI struct {
	url       string
	query     url.Values
	token     string
	sessionID string
}

func (m *milestoneAPI) GetToken() error {
	data := url.Values{
		"client_id":  {"GrantValidatorClient"},
		"grant_type": {"password"},
		"username":   m.query["username"],
		"password":   m.query["password"],
	}

	req, err := http.NewRequest("POST", m.url+"/IDP/connect/token", strings.NewReader(data.Encode()))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	// support httpx protocol
	res, err := tcp.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return errors.New("milesone: authentication failed: " + res.Status)
	}

	var payload map[string]interface{}
	if err = json.NewDecoder(res.Body).Decode(&payload); err != nil {
		return err
	}

	token, ok := payload["access_token"].(string)
	if !ok {
		return errors.New("milesone: token not found in the response")
	}

	m.token = token

	return nil
}

func parseFloat(s string) float64 {
	if s == "" {
		return 0
	}
	f, _ := strconv.ParseFloat(s, 64)
	return f
}

func (m *milestoneAPI) GetOffer() (string, error) {
	request := struct {
		CameraId         string `json:"cameraId"`
		StreamId         string `json:"streamId,omitempty"`
		PlaybackTimeNode struct {
			PlaybackTime string  `json:"playbackTime,omitempty"`
			SkipGaps     bool    `json:"skipGaps,omitempty"`
			Speed        float64 `json:"speed,omitempty"`
		} `json:"playbackTimeNode,omitempty"`
		//ICEServers []string `json:"iceServers,omitempty"`
		//Resolution string   `json:"resolution,omitempty"`
	}{
		CameraId: m.query.Get("cameraId"),
		StreamId: m.query.Get("streamId"),
	}
	request.PlaybackTimeNode.PlaybackTime = m.query.Get("playbackTime")
	request.PlaybackTimeNode.SkipGaps = m.query.Has("skipGaps")
	request.PlaybackTimeNode.Speed = parseFloat(m.query.Get("speed"))

	data, err := json.Marshal(request)
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest("POST", m.url+"/REST/v1/WebRTC/Session", bytes.NewBuffer(data))
	if err != nil {
		return "", err
	}
	req.Header.Set("Authorization", "Bearer "+m.token)
	req.Header.Set("Content-Type", "application/json")

	res, err := tcp.Do(req)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return "", errors.New("milesone: create session: " + res.Status)
	}

	var response struct {
		SessionId string `json:"sessionId"`
		OfferSDP  string `json:"offerSDP"`
	}
	if err = json.NewDecoder(res.Body).Decode(&response); err != nil {
		return "", err
	}

	var offer pion.SessionDescription
	if err = json.Unmarshal([]byte(response.OfferSDP), &offer); err != nil {
		return "", err
	}

	m.sessionID = response.SessionId

	return offer.SDP, nil
}

func (m *milestoneAPI) SetAnswer(sdp string) error {
	answer := pion.SessionDescription{
		Type: pion.SDPTypeAnswer,
		SDP:  sdp,
	}
	data, err := json.Marshal(answer)
	if err != nil {
		return err
	}

	request := struct {
		AnswerSDP string `json:"answerSDP"`
	}{
		AnswerSDP: string(data),
	}
	if data, err = json.Marshal(request); err != nil {
		return err
	}

	req, err := http.NewRequest("PATCH", m.url+"/REST/v1/WebRTC/Session/"+m.sessionID, bytes.NewBuffer(data))
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+m.token)
	req.Header.Set("Content-Type", "application/json")

	res, err := tcp.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return errors.New("milesone: patch session: " + res.Status)
	}

	return nil
}

func milestoneClient(rawURL string, query url.Values) (core.Producer, error) {
	mc := &milestoneAPI{url: rawURL, query: query}
	if err := mc.GetToken(); err != nil {
		return nil, err
	}

	api, err := webrtc.NewAPI()
	if err != nil {
		return nil, err
	}

	conf := pion.Configuration{}
	pc, err := api.NewPeerConnection(conf)
	if err != nil {
		return nil, err
	}

	prod := webrtc.NewConn(pc)
	prod.FormatName = "webrtc/milestone"
	prod.Mode = core.ModeActiveProducer
	prod.Protocol = "http"
	prod.URL = rawURL

	offer, err := mc.GetOffer()
	if err != nil {
		return nil, err
	}

	if err = prod.SetOffer(offer); err != nil {
		return nil, err
	}

	answer, err := prod.GetAnswer()
	if err != nil {
		return nil, err
	}

	if err = mc.SetAnswer(answer); err != nil {
		return nil, err
	}

	return prod, nil
}
