package http

import (
	"errors"
	"net"
	"net/http"
	"net/url"
	"strings"

	"github.com/AlexxIT/go2rtc/internal/api"
	"github.com/AlexxIT/go2rtc/internal/streams"
	"github.com/AlexxIT/go2rtc/pkg/core"
	"github.com/AlexxIT/go2rtc/pkg/hls"
	"github.com/AlexxIT/go2rtc/pkg/image"
	"github.com/AlexxIT/go2rtc/pkg/magic"
	"github.com/AlexxIT/go2rtc/pkg/mpjpeg"
	"github.com/AlexxIT/go2rtc/pkg/pcm"
	"github.com/AlexxIT/go2rtc/pkg/tcp"
)

func Init() {
	streams.HandleFunc("http", handleHTTP)
	streams.HandleFunc("https", handleHTTP)
	streams.HandleFunc("httpx", handleHTTP)

	streams.HandleFunc("tcp", handleTCP)

	api.HandleFunc("api/stream", apiStream)
}

func handleHTTP(rawURL string) (core.Producer, error) {
	rawURL, rawQuery, _ := strings.Cut(rawURL, "#")

	// first we get the Content-Type to define supported producer
	req, err := http.NewRequest("GET", rawURL, nil)
	if err != nil {
		return nil, err
	}

	if rawQuery != "" {
		query := streams.ParseQuery(rawQuery)

		for _, header := range query["header"] {
			key, value, _ := strings.Cut(header, ":")
			req.Header.Add(key, strings.TrimSpace(value))
		}
	}

	prod, err := do(req)
	if err != nil {
		return nil, err
	}

	if info, ok := prod.(core.Info); ok {
		info.SetProtocol("http")
		info.SetRemoteAddr(req.URL.Host) // TODO: rewrite to net.Conn
		info.SetURL(rawURL)
	}

	return prod, nil
}

func do(req *http.Request) (core.Producer, error) {
	res, err := tcp.Do(req)
	if err != nil {
		return nil, err
	}

	if res.StatusCode != http.StatusOK {
		return nil, errors.New(res.Status)
	}

	// 1. Guess format from content type
	ct := res.Header.Get("Content-Type")
	if i := strings.IndexByte(ct, ';'); i > 0 {
		ct = ct[:i]
	}

	var ext string
	if i := strings.LastIndexByte(req.URL.Path, '.'); i > 0 {
		ext = req.URL.Path[i+1:]
	}

	switch {
	case ct == "application/vnd.apple.mpegurl" || ext == "m3u8":
		return hls.OpenURL(req.URL, res.Body)
	case ct == "image/jpeg":
		return image.Open(res)
	case ct == "multipart/x-mixed-replace":
		return mpjpeg.Open(res.Body)
	//https://www.iana.org/assignments/media-types/audio/basic
	case ct == "audio/basic":
		return pcm.Open(res.Body)
	}

	return magic.Open(res.Body)
}

func handleTCP(rawURL string) (core.Producer, error) {
	u, err := url.Parse(rawURL)
	if err != nil {
		return nil, err
	}

	conn, err := net.DialTimeout("tcp", u.Host, core.ConnDialTimeout)
	if err != nil {
		return nil, err
	}

	return magic.Open(conn)
}

func apiStream(w http.ResponseWriter, r *http.Request) {
	dst := r.URL.Query().Get("dst")
	stream := streams.Get(dst)
	if stream == nil {
		http.Error(w, api.StreamNotFound, http.StatusNotFound)
		return
	}

	client, err := magic.Open(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	stream.AddProducer(client)
	defer stream.RemoveProducer(client)

	if err = client.Start(); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}
