package server

import (
	"bytes"
	"embed"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"
	"github.com/pion/sdp/v3"
	"github.com/pion/webrtc/v4"
)

//go:embed static
var static embed.FS

type Server struct {
	streams *StreamStore
	config  *Config
	http.Server
}

func NewServer(config *Config) *Server {
	srv := &Server{
		streams: NewStreamStore(config),
		config:  config,
	}

	r := chi.NewRouter()
	r.Use(corsMiddleware)
	r.Get("/", srv.StaticHandler)
	r.Get("/{name}", srv.StaticHandler)
	r.Post("/whip", http.HandlerFunc(srv.WhipHandler))
	r.Get("/whip", srv.ListHandler)
	r.Options("/whip", srv.OptionsHandler)
	r.Delete("/whip/{streamKey}", srv.DeleteHandler)
	r.Patch("/whip/{streamKey}", srv.PatchHandler)
	r.Post("/whip/{streamKey}", srv.PostOfferHandler)
	r.Get("/stats", srv.streams.StatsHandler)
	r.Get("/api/siteinfo", srv.InfoHandler)

	srv.Handler = r

	return srv
}

func corsMiddleware(next http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Access-Control-Allow-Origin", "*")
		next.ServeHTTP(w, r)
	}
	return http.HandlerFunc(fn)
}

func (s *Server) InfoHandler(w http.ResponseWriter, r *http.Request) {
	var infoResponse struct {
		SiteName string `json:"siteName"`
	}
	infoResponse.SiteName = s.config.SiteName

	if err := json.NewEncoder(w).Encode(&infoResponse); err != nil {
		slog.Warn("Error writing info response")
	}
}

func (s *Server) OptionsHandler(w http.ResponseWriter, r *http.Request) {
	slog.Info("Got OPTIONS")
}

func (s *Server) PatchHandler(w http.ResponseWriter, r *http.Request) {
	slog.Info("Got PATCH!")
}

func (s *Server) StaticHandler(w http.ResponseWriter, r *http.Request) {
	name := chi.URLParam(r, "name")
	if name == "" {
		name = "index.html"
	}

	path := fmt.Sprintf("static/%s", name)

	if strings.Contains(name, ".js") {
		w.Header().Add("Content-Type", "text/javascript")
	}
	if strings.Contains(name, ".css") {
		w.Header().Add("Content-Type", "text/css")
	}

	f, err := static.Open(path)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	defer f.Close()

	io.Copy(w, f)
}

func (s *Server) PostOfferHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	streamKey := chi.URLParam(r, "streamKey")

	stream, err := s.streams.Get(streamKey)
	if err != nil {
		slog.Warn("Unable to fetch stream.", "error", err, "stream_key", streamKey)
		w.WriteHeader(http.StatusNotFound)
		return
	}

	var buf bytes.Buffer
	io.Copy(&buf, r.Body)

	offer := &webrtc.SessionDescription{Type: webrtc.SDPTypeOffer, SDP: buf.String()}

	answer, err := stream.AddListener(offer)
	if err != nil {
		slog.Warn("Unable to add peer.", "error", err, "stream_key", streamKey, "buf", buf.String())
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	io.WriteString(w, answer.SDP)

	slog.Info("Got listener for stream.", "stream_key", streamKey)
}

func (s *Server) DeleteHandler(w http.ResponseWriter, r *http.Request) {
	streamKey := chi.URLParam(r, "streamKey")
	s.streams.Delete(streamKey)
	slog.Info("Deleted stream.", "streamKey", streamKey)
	w.WriteHeader(http.StatusOK)
}

func (s *Server) ListHandler(w http.ResponseWriter, r *http.Request) {
	type StreamInfo struct {
		StreamKey string `json:"streamKey"`
		ViewCount int    `json:"viewCount"`
	}

	infos := []StreamInfo{}
	for key, stream := range s.streams.Streams {
		infos = append(infos, StreamInfo{StreamKey: key, ViewCount: len(stream.viewers)})
	}

	enc := json.NewEncoder(w)
	enc.Encode(&infos)
}

func (s *Server) WhipHandler(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	streamKey, err := streamKeyFromHeader(r.Header.Get("Authorization"))
	if err != nil {
		slog.Warn("Incoming request with invalid auth header.")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	var buf bytes.Buffer

	io.Copy(&buf, r.Body)

	offer := &webrtc.SessionDescription{Type: webrtc.SDPTypeOffer, SDP: buf.String()}

	offerSdp := sdp.SessionDescription{}
	if err := offerSdp.Unmarshal(buf.Bytes()); err != nil {
		panic(err)
	}

	slog.Info("Got SDP.", "id", offerSdp.Origin.SessionID, "ip", offerSdp.Origin.UnicastAddress)

	answer, err := s.streams.Add(streamKey, offer)
	if err != nil {
		slog.Error("Error adding stream.", "error", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Header().Add("Location", fmt.Sprintf("/whip/%s", streamKey))
	w.Header().Add("Link", "stun:stun.l.google.com:19302; rel=\"ice-server\";")
	w.WriteHeader(http.StatusCreated)
	if _, err := io.WriteString(w, answer.SDP); err != nil {
		slog.Error("Error writing response.", "error", err)
	}
	slog.Info("Wrote answer.")
}

func streamKeyFromHeader(header string) (string, error) {
	split := strings.Split(header, " ")
	if len(split) != 2 {
		return "", fmt.Errorf("invalid header")
	}

	return split[1], nil
}